What Agent Passkey is, and the six 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. This is the only way a credential is created.
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 six 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. Open the sign-in URL in a browser the agent controls, and install the shim from passkey_howto. The shim replaces navigator.credentials.get so the site's credential request is *parked* instead of being answered by the local device, which does not hold this passkey.
  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. Read the parked requestwindow.__apk.pending gives { rpId, origin, clientDataJSON, allowCredentials }.
  5. `passkey_assert` — send the credentialId, the clientDataJSON byte-for-byte, and the page origin. Get back authData, signature and userHandle.
  6. Hand it back to the page with window.__apk.deliver({...}). The promise resolves, the site verifies the signature against the public key it registered at enrolment, and sets its session.

An agent connected to the local server skips all of that: passkey_signin(domain) does steps 2 to 6 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 in it, 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

  • Quickstart — connect an agent, enrol a passkey.
  • MCP reference — every tool, every argument, every status.
  • Failure modes — the three refusals that look alike and mean opposite things.
  • Limits — what this cannot do, stated plainly.