Here's a problem I didn't expect to be the blocker.
I had three model seats — ChatGPT, Gemini, Copilot — already working against one MCP server (my Canonical Vault, which does no transport credential check; governance lives server-side in branch rules). Those surfaces connect URL-only.
My newer MCP server, the Commons, accepted exactly one presentation: Authorization: Bearer <token>.
Result: three credentialed seats, zero connections. Not a permissions problem. A presentation problem.
OAuth was the obvious textbook answer — and the wrong first move. The seats didn't need a new identity system. They needed a door shaped like the one they already walk through.
The pattern: door credentials
Ship a per-seat secret that can be presented in whatever shape the client can actually send, all resolving through one identity function:
| Presentation | Shape |
|---|---|
| Bearer (original) | Authorization: Bearer <secret> |
| URL path | POST /mcp/s/<secret> |
| URL query | POST /mcp?k=<secret> |
| API key header | X-Api-Key: <secret> |
| Basic password | Authorization: Basic <user>:<secret> |
Rules that make this safe enough to ship:
-
Hash at rest, always. Doors are stored as SHA-256 hashes only (migration
003_door_credentials). The plaintext is shown once, at mint time, in a connection sheet the operator hands to that seat's surface. -
One identity function. Every shape — root token or door secret, any presentation — resolves through the same
authenticate()path. No shape gets a weaker check. A bogus secret returns unauthenticated in every shape (verified live, per seat). -
Revocable independently. Admin gained
issue-door/revoke-door. Revoking a seat's door does not touch its root token, and vice versa. I verified this on a throwaway door: revoked door →revoked, root token unaffected. - High entropy, per seat. Each seat gets its own secret, so a leak is one seat's problem, with one seat's revocation — not a global rotation.
- Auth: None on the client side. In the seat's own surface you register the door URL as the server URL and set auth to None — the credential is in the URL, exactly like the URL-only servers these seats already use.
What shipped
Commons v0.5.0, in production:
- Migration 003 applied to production Postgres
- Auth resolves Bearer, Basic, X-Api-Key,
/mcp/s/<secret>, and?k=through the one identity function - Doors minted and verified live for chatgpt, gemini, and copilot:
whoamithrough the door itself + every alternate shape, per seat - Test suite 46/46, typecheck clean, health endpoint reports
0.5.0
Remaining step is deliberately manual: add each door URL in that seat's own surface. Credentials cross into another vendor's UI only by the operator's hand.
When to use this — and when not to
Use door credentials when:
- Your clients are heterogeneous agents/hosts, some of which genuinely cannot set headers
- Each client is a distinct, named seat (not anonymous public traffic)
- You can hash at rest, rate-limit, and revoke per seat
Don't use them when:
- A real OAuth flow is available to all your clients — prefer it; it's the front door, this is the side door
- Secrets in URLs would land in logs you don't control without redaction — path segments do end up in access logs; treat those logs as credential-adjacent
- You need user-delegated, scoped consent rather than seat identity
I demoted OAuth and a REST shim to optional hardening, in that order. The unblocking move was meeting the clients at the presentation they already had — without weakening the one rule that matters: identity is a credential, never a claim, and the server derives it the same way no matter which door you came through.
Part of the "Building Governed Agents" series. Previous: "The Commons: Claims, Never Commands." Code context: the Commons is an append-only MCP pool where models exchange claims under governance — source currently in a private repo, protocol described in the series. Paper: doi.org/10.5281/zenodo.23242126 · Writing: liminaljermo.substack.com
Hit the same wall with an MCP client that can't send headers? I want to hear which shape you shipped.
Top comments (0)