Claude Code OAuth deep dive: how EmblemAI's MCP server handles auth
Most posts about MCP skip the auth part. Understandable — MCP is exciting, OAuth is dental work. But if you're shipping an MCP server that touches anything more sensitive than a weather API, you need OAuth to be right, and the gap between "technically compliant" and "actually works with a given MCP client" is wider than you'd hope.
This post walks through the OAuth flow EmblemAI shipped for its hosted MCP server (https://emblemvault.ai/api/mcp). It's a minimal, standards-compliant public-client flow that installs cleanly in Claude Code via RFC 9728 client ID metadata documents. If you're building an MCP server with user-scoped data, copy the pattern — and read the client-compatibility section at the end so you ship the install path your users actually run.
2026-04-23 update. This post originally claimed
client_id_metadata_document_supported: truewas "the RFC 7591 extension." That is incorrect. RFC 7591 is Dynamic Client Registration — a/registerendpoint clients POST to. RFC 9728 is Client ID Metadata Document — the mechanism our server actually implements, which Claude Code supports natively. Clients that rely on themcp-remotestdio bridge (Cursor, Windsurf, Gemini CLI, Claude Desktop via bridge, ElizaOS via @fleek-platform/eliza-plugin-mcp) require RFC 7591 DCR and will fail today against our discovery document. DCR support is on the roadmap. Corrections inline below.
What you need to support
Claude Code's mcp add --transport http <url> command assumes the server exposes:
-
.well-known/oauth-authorization-servermetadata at the authorization server origin. - An
authorizeendpoint that handles PKCE (code_challenge+code_challenge_method=S256). - A
tokenendpoint that accepts a public client (no client secret). - A JWKS endpoint for token verification (
/.well-known/jwks.json). - Loopback redirect URIs (
http://127.0.0.1:<random-port>). -
Either RFC 9728
client_id_metadata_document_supported: true(what Claude Code consumes) or RFC 7591 dynamic client registration at/register(what mcp-remote consumes). Ship both if you want frictionless install across all MCP clients. EmblemAI currently ships RFC 9728 only; RFC 7591 DCR is next.
Skip any of these and you'll get a confusing "failed to install" error from the client with no useful log line.
EmblemAI's implementation
Discovery document:
$ curl -s https://api.emblemvault.ai/.well-known/oauth-authorization-server | jq
{
"issuer": "https://api.emblemvault.ai",
"authorization_endpoint": "https://api.emblemvault.ai/oauth/authorize",
"token_endpoint": "https://api.emblemvault.ai/oauth/token",
"jwks_uri": "https://api.emblemvault.ai/.well-known/jwks.json",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["none"],
"code_challenge_methods_supported": ["S256"],
"client_id_metadata_document_supported": true,
"scopes_supported": ["vault:read"]
}
Note the absence of a registration_endpoint. That is the missing RFC 7591 piece — see the compatibility table at the end.
A few choices worth calling out:
token_endpoint_auth_methods_supported: ["none"]
Correct for public clients. Claude Code cannot keep a client secret, so the server should not require one. Anyone shipping an MCP server for a native-client use case should use none here. Do not use client_secret_basic unless your only clients are server-to-server.
client_id_metadata_document_supported: true
This is the RFC 9728 Client ID Metadata Document mechanism. The client hosts a JSON file at a URL it controls (e.g. https://claude.ai/.well-known/mcp-client-metadata) and passes that URL as its client_id. The server fetches and validates the metadata at authorize time. Claude Code uses this. It is separate from RFC 7591 Dynamic Client Registration, which is the /register POST flow most non-Claude-Code clients use.
If you advertise client_id_metadata_document_supported: true and your clients are Claude Code, you are done on the client-identification axis. If your clients also include mcp-remote-based agents, you additionally need RFC 7591 /register.
Loopback redirect URIs
The authorize page must accept http://127.0.0.1:<random-port>/callback (or http://localhost:<port>/callback) as a redirect URI. If you only allow https:// redirects, native clients cannot install.
PKCE mandatory
code_challenge_methods_supported: ["S256"] — no plain fallback. If you ship an MCP server without PKCE, a malicious process on the same machine can steal the auth code. Don't.
Token lifecycle
The token is a standard JWT signed with RS256:
{
"alg": "RS256",
"typ": "JWT",
"kid": "auth-key-local-001"
}
{
"iss": "https://api.emblemvault.ai",
"aud": "https://agenthustle.ai",
"sub": "<vault_id>",
"appId": "emblem-agent-wallet",
"scope": "vault:read",
"exp": <unix>,
"iat": <unix>
}
Validate it against the JWKS at /.well-known/jwks.json. The kid in the JWT header matches a key in the JWKS; standard RS256 verification from there.
Refresh tokens are one-time-use. Present an already-used refresh token and the server returns {"error": "refresh_replayed"}. If you are building a client, write the fresh refresh token back to disk before any subsequent request, or you will burn the token and lock the user out.
Client compatibility matrix (as of 2026-04-23)
| MCP client | Auth path that works today |
|---|---|
| Claude Code | OAuth via RFC 9728 client ID metadata document — one-command install |
| Claude Desktop (native MCP) | OAuth via RFC 9728 — if the build supports CIMD |
| Cursor, Windsurf, Gemini CLI, ElizaOS, others | API key via mcp-remote --header "x-api-key:..." today. OAuth via mcp-remote currently fails because mcp-remote requires RFC 7591 DCR which our authorization server does not yet advertise. |
The API-key path works for every MCP client that can invoke npx mcp-remote or equivalent, because it bypasses the OAuth flow entirely. Generate a key at emblemvault.ai/welcome.
Why this matters for MCP server authors
MCP is going to eat a lot of integration work over the next year. The pattern to recommend if you are building a server that touches user-scoped data:
- Host the OAuth authorization server yourself if you want tight control over consent UX and scope semantics. External IDPs work, but their MCP-shaped flows are still rough.
- Support public clients with PKCE + loopback. Full stop. Do not require client secrets.
- Ship both RFC 9728 CIMD and RFC 7591 DCR. CIMD handles Claude Code with zero friction; DCR handles mcp-remote-based clients with zero friction. If you only ship one, half your client surface breaks.
- Use short-lived access tokens (~15 min) with one-time refresh tokens. EmblemAI's shipped config is 15 minutes, which feels right for wallet ops.
-
Ship discovery metadata correctly on day one.
.well-known/oauth-authorization-server+.well-known/jwks.json+.well-known/oauth-protected-resourcefor the resource server. Without these, installers silently fail.
What shipped in PR 97
The PR that enabled the Claude Code OAuth flow was PR #97 on api.emblemvault.ai. It added:
- OAuth discovery endpoints (
/.well-known/oauth-authorization-server,jwks.json). - PKCE/public-client authorization-code flow.
- RFC 9728 Client ID Metadata Document support (
client_id_metadata_document_supported: true). - Authorize UX via Emblem Auth.
- Loopback redirect compatibility for native clients.
The actual MCP server itself was already deployed from the hustle-v2 side. PR 97 was the auth unlock for Claude Code. The RFC 7591 DCR follow-up is tracked as a separate workstream.
Try it
claude mcp add --transport http emblem https://emblemvault.ai/api/mcp
And if you are working on an MCP server of your own, these are the corners to get right first — including the RFC 7591 corner we are fixing now.
Links
- https://emblemvault.ai
- https://api.emblemvault.ai/.well-known/oauth-authorization-server
-
https://datatracker.ietf.org/doc/html/rfc7591 (RFC 7591 — Dynamic Client Registration,
/registerflow) - https://datatracker.ietf.org/doc/html/rfc9728 (RFC 9728 — OAuth 2.0 Protected Resource Metadata / Client ID Metadata Document)
- https://datatracker.ietf.org/doc/html/rfc7636 (RFC 7636 — PKCE)
- https://modelcontextprotocol.io
Top comments (0)