Skip to content

Standards

What mcp-hub implements, in which direction, and where the limits are.

Two directions matter, and they are genuinely different problems. Inbound is mcp-hub as an authorization server and resource server for the MCP clients that connect to it. Outbound is mcp-hub as an OAuth client toward the remote MCP servers it connects to on your behalf. Most gateways do one of the two.

Getting a client identity

The same four ways in and out — with one gap in each direction, named below.

Inbound (a client → the hub)Outbound (the hub → an upstream)
Operator-issued credentialsmcp-hub-admin clients add issues a client_id and secret by handoauth.mode: "static" with a clientId the upstream gave you
Dynamic registration (RFC 7591)POST /register, on by defaultoauth.mode: "dcr"
Registration management (RFC 7592)GET/PUT/DELETE /register/<client_id>the hub deletes its own registration on upstream logout
Client ID Metadata Documentsa client uses its document URL as client_idoauth.mode: "cimd", one document per upstream
Bearer token, no OAuthAPI tokens minted by the operatora static Authorization header

CIMD is draft-ietf-oauth-client-id-metadata-document-00, the mechanism the MCP specification prefers over dynamic registration.

Proving that identity

InboundOutbound
client_secret_post
client_secret_basic❌ — credentials are read from the form body only
none (public client)
private_key_jwt (RFC 7523)for metadata-document clients only✅ via oauth.clientAuth

Both directions sign and verify RS/PS/ES 256/384/512 and EdDSA. Outbound, the hub signs with an Ed25519 key of its own at <DATA_PATH>/upstream-key.pem — deliberately not the key that signs the access tokens it issues — and publishes the public half in the document or registration the upstream reads.

Grants

InboundOutbound
authorization_code + PKCES256 required, plain rejected✅ S256
refresh_token✅ rotating, with reuse detection✅ serialized, single-flight
client_credentials

Inbound refresh tokens rotate on every use and are grouped into families: replaying a retired token revokes the whole family, because a replay means two parties hold one chain. Outbound, refresh is deliberately taken away from the SDK and serialized per upstream — parallel requests that each hit a 401 would otherwise each spend the same rotating token, which an upstream that detects reuse treats exactly as above.

Discovery and binding

StandardInboundOutbound
RFC 8414 authorization server metadata✅ served, incl. the path-inserted form✅ consumed
RFC 9728 protected resource metadata✅ served, incl. path-scoped variants✅ consumed, to find the upstream's authorization server
RFC 8707 resource indicatorsenforced by default✅ sent when the upstream publishes RFC 9728
RFC 8252 native apps✅ loopback redirects, port-flexiblen/a
RFC 7009 revocation⚠️ refresh tokens only — see below✅ best effort on upstream logout

Resource indicators are the reason a token issued for one server does not reach another. Every access token carries the resource as its audience, and every request is checked against the path it arrived on.

/.well-known/openid-configuration is served as a byte-identical alias of the RFC 8414 document, because several clients probe it first. That is all it is: there is no ID token, no userinfo, no OIDC claims. Do not enable a client's "OpenID Connect" option against it.

Not implemented

Named explicitly, so you do not have to find out by trying:

Access-token revocation/revoke accepts an access token and does nothing with it. Only refresh tokens are revoked there. To kill live access tokens, use mcp-hub-admin clients revoke, which sets a marker the verifier checks.
client_credentials inboundThe hub issues tokens to a person who approved a client, not to a machine identity. Use an API token for that.
Device authorization grant (RFC 8628)Neither direction.
private_key_jwt for dynamically registered clientsInbound it is accepted only from metadata-document clients. A DCR client uses its secret.
DPoP (RFC 9449), mTLS (RFC 8705), PAR (RFC 9126), token introspection (RFC 7662), token exchange (RFC 8693), client_secret_jwtNone of them, in either direction.
A published JWKS for the hub's own tokensAccess tokens are EdDSA-signed and verified by the hub alone; there is no endpoint for a third party to verify them.
Scopes as an authorization boundaryScopes are carried through but nothing is enforced on them. Authorization is by resource, not by scope.
Users, roles, audit trailsOne shared password, no per-user identity. Every token's subject is the same. See what the hub does not protect against.
Per-user upstream tokensAn upstream credential belongs to the deployment, not to the client that triggered the call. The hub does not act on behalf of individual users.

Transport and protocol

Streamable HTTP, stateless — no session state to leak — with SSE accepted for upstreams that only speak that. Bearer tokens are read from the Authorization header only; there is no query-parameter or form-body form.

The MCP protocol version is negotiated by the SDK rather than pinned by the hub. The authorization behaviour follows the specification revision that made metadata documents the preferred registration mechanism and deprecated dynamic registration — which the hub still serves, because most clients still need it.

Next

Released under the MIT License. Not affiliated with Anthropic; “Claude” is a trademark of Anthropic PBC.