What Agent Passkey is, and the steps from “I hold a passkey for this site” to “I am signed in”.

View as Markdown

Agent Passkey

Agent Passkey holds a person's passkeys and signs WebAuthn assertions with them, so an agent can sign in as that person without ever holding a secret.

The private key is generated inside the service and never leaves it. An agent does not get the key, a cookie, a password or a recovery code. It gets one signature, over bytes it hands in, for one ceremony, on a site the person ticked — and every one of those signatures is scoped, logged and revocable.

The shape of it

PieceWhat it does
The serviceHolds the passkeys. Signs. Enforces the origin, the policy, the approval and the rate limit.
The extensionEnrols a passkey, in a human's own browser, on a site they are already signed in to. The default way a credential is created; the enrol scope is the other.
The remote MCP serverhttps://app.solospace.dev/mcp — OAuth 2.1, no API key. Signs bytes for an agent that drives its own browser.
The local MCP serverA stdio server that drives its own Chromium and does the whole ceremony in one call.
The dashboardConnectors, agents, approvals, and an append-only audit log.

Signing someone in, end to end

The remote server has no browser. It signs bytes; the agent drives the page. That split is the whole design, and it is why the procedure has several steps rather than one.

  1. passkey_list — which sites are connected, the sign-in URL for each, and the operating notes for that site. The notes say where the passkey option hides.
  2. passkey_begin_signin mints a single-use ceremony ticket and returns armUrl. In a browser with the Agent Passkey extension, the agent just navigates the tab to armUrl: the extension binds the ticket to that tab and sends it back to the page. No page script at all. For a browser without the extension, the result also carries shim — a page script with the ticket baked in that stands in for navigator.credentials.get — which works where the site's CSP lets the page reach this service (shimProcedure says which).
  3. Drive the page to the passkey prompt. This is the step that actually fails. Most sites will not offer a passkey until they know who is signing in, and many hide it behind "Try another way".
  4. Click the site's own passkey button, then watch. When the site asks for a credential, the extension's background worker (or, on the shim path, THE PAGE) posts the ceremony bytes to this service itself and gets the signature back — the agent carries nothing. Poll window.__apk.status (waiting, signing, signed, failed: <reason>), or call passkey_signin_status({ ticket }) for the server's view of the same ticket.
  5. The site sets its session. Watch for the navigation.

If the page's own CSP blocks it from reaching this service (github.com and login.gov.il do), the extension path is unaffected — its worker is not subject to the page's CSP — and it is what next names. With no extension there, ifNotArmed gives the agent one sentence to ask the person. For an agent whose browser tool can evaluate scripts and return long strings whole, the shim also degrades on its own: the request is parked in window.__apk.pending instead, and the agent ferries clientDataJSON to passkey_complete_signin, gets back authData/signature/ userHandle, and hands them to window.__apk.deliver({...}). That fallback is the one case where the agent carries ceremony bytes at all.

An agent connected to the local server skips all of that: passkey_signin(domain) does the whole thing in one call, in its own Chromium. An agent can tell which situation it is in by looking at its own tool list — if passkey_signin is usable there, the local server is connected.

The rule that shapes everything else

The UP and UV flags in a WebAuthn assertion are a signed factual claim to the relying party: *a human was present, and a human was verified*. This service will not lie about them.

A connector in approval mode wakes a real person before every signature, and only then are the flags set. A connector in silent mode signs UP=0/UV=0 — honestly — and most sites reject that. There is no third setting where the flags are true because it would be convenient.

Where to go next