What Agent Passkey is, and the six steps from “I hold a passkey for this site” to “I am signed in”.
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
| Piece | What it does |
|---|---|
| The service | Holds the passkeys. Signs. Enforces the origin, the policy, the approval and the rate limit. |
| The extension | Enrols 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 server | https://app.solospace.dev/mcp — OAuth 2.1, no API key. Signs bytes for an agent that drives its own browser. |
| The local MCP server | A stdio server that drives its own Chromium and does the whole ceremony in one call. |
| The dashboard | Connectors, 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.
- `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.
- Open the sign-in URL in a browser the agent controls, and install the shim from
passkey_howto. The shim replacesnavigator.credentials.getso the site's credential request is *parked* instead of being answered by the local device, which does not hold this passkey. - 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".
- Read the parked request —
window.__apk.pendinggives{ rpId, origin, clientDataJSON, allowCredentials }. - `passkey_assert` — send the
credentialId, theclientDataJSONbyte-for-byte, and the page origin. Get backauthData,signatureanduserHandle. - 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.