carl-gustav.dev

Notes on systems, language, and craft.

AI & Autonomous Systems

Agent Authenticator: TOTP for AI Agents

Agent Authenticator is an MCP server that returns current TOTP codes to AI agents without exposing the underlying seed.

Agent Authenticator exists because 2FA was still a manual step in otherwise-automated workflows at Forge Nord.

The problem was simple. The agent could handle the UI, submit the login form, and continue once authenticated. But when the site asked for a TOTP code, the workflow stopped and a human had to read six digits from a phone.

The obvious workaround is to give the agent the TOTP seed and let it generate codes directly. Forge Nord did not want to do that. The seed is a long-lived secret. The current TOTP code is not. Those should not be treated as the same thing.

So Forge Nord built Agent Authenticator: an MCP server that stores TOTP seeds in an encrypted local vault and only exposes operations that return the current code or manage accounts. There is no API for reading the underlying seed back out through MCP.

Design

The project is intentionally narrow.

  • store TOTP seeds encrypted at rest
  • return the current code on demand
  • keep the default transport local
  • work with existing MCP clients such as Claude Code and Cursor

That is the whole scope. This is not a password manager, not a general secrets product, and not a broader auth system.

Tool surface

The MCP interface is small:

  • list accounts
  • inspect non-secret account metadata
  • generate the current TOTP code
  • add an account from a seed or otpauth:// URI
  • remove an account

The main constraint is deliberate: the model can request a code, but it cannot retrieve the seed that generates future codes.

What needed fixing before release

The first internal version worked, but it was not ready to publish as-is. A few things needed cleanup first.

Concurrent vault writes

The vault already used file locking, but not across the full read-modify-write sequence. That is fine until multiple processes touch the same file. The release version wraps the full mutation path so writes are atomic.

First-run key handling

Printing key material to standard output is a bad default in general, and especially awkward when the primary transport is MCP over stdio. The first-run flow was reworked so key handling is quieter and less likely to leak into logs or transcripts.

HTTP defaults

If a tool can bind to the network, the default matters more than the warning text in the README. The HTTP mode now defaults to loopback-only and requires an explicit opt-in for remote binding.

Why I think the pattern matters

The implementation is small, but the interface pattern is useful.

When building agent infrastructure, the better question is usually not “what secret does the model need?” but “what is the narrowest useful operation to expose?”

In this case the answer was straightforward:

  • not the TOTP seed
  • just the current TOTP code

I think the same pattern applies elsewhere:

  • not the database password, but a narrow query interface
  • not the cloud login, but a deployment action
  • not a general secrets manager, but one explicit capability

That is the kind of boundary that is easier to audit and easier to operate.

Open source release

Before publishing the repo, I added a few things that internal tools often skip:

  • hardened vault write handling
  • cleaner key setup
  • a proper CLI with serve, doctor, and keygen
  • CI and release automation
  • clearer security documentation

The code is on GitHub.

Written by Carl-Gustav Öberg

I'm Carl-Gustav Öberg, founder of Forge Nord. I build AI systems, run infrastructure, and write about what I learn along the way.