Security model
What this server guarantees, and what your deployment still has to do.
Authorization flow
- Authorization Code + PKCE (
S256) is required for all authorization-code clients; there is no implicit or password grant. /authorizematches a dynamically registeredredirect_uriexactly. Registration accepts the RFC 8252 shapes — HTTPS, loopbackhttp, and private-use schemes — which is what desktop MCP clients use. The connector allowlist matches loopbackhttpby scheme, hostname, and path and ignores the port;http://127.0.0.1:*,http://localhost:*, andhttp://[::1]:*match any path on that host. See the deviation note.- A
client_idthat is an HTTPS URL is fetched as a Client ID Metadata Document. The fetch does not follow redirects, rejects credentialed URLs and non-loopback IP literals. Its transport also resolves and checks every address at dial time, rejecting private, link-local and other blocked ranges, including mixed public/private DNS answers. Local-development mode permits loopback addresses for tests. Dynamic registration still works. - Registration failures and an
/authorizefor an unknownclient_idare audit events. Redirect URIs are logged; tokens, secrets, codes, keys, and assertions are not. - The token-exchange grant requires the caller to authenticate as a pre-registered resource server (RFC 7523
private_key_jwt) and to present asubject_tokenthis server itself issued. It is not an open relay to the upstream connector. - The consent page names the client and labels a dynamically registered client's name unverified, because the application supplied
client_nameand this server has not verified it. The consent document and the expired-consent response (HTTP 400) sendContent-Security-Policy(default-src 'none',style-src 'unsafe-inline'for the page's own style element,form-action 'self',frame-ancestors 'none'),X-Frame-Options: DENY,Referrer-Policy: no-referrer, andCache-Control: no-store. Connectorconsent.website_urlandconsent.support_urlmust be absolutehttpsURLs; that is checked when the connector file is loaded. See the consent page.
Tokens
- Access tokens are short-lived
at+jwtwith issuer, audience/resource, scopes, and unique IDs. A token is bound to the single resource the client requested, so it is refused by any other resource server. - Refresh tokens are opaque, hashed at rest, rotated on use, and the whole family is revoked on reuse.
- The direct SDK verifiers default to RS256, refresh JWKS on a bounded schedule, and validate
iss,aud,exp,nbf,sub, and required scopes. Python can explicitly allow RS384/RS512; Go and TypeScript use RS256. The optional FastMCP adapter delegates token verification to FastMCP's verifier. - Direct JWT verification is offline: revoking a refresh token does not make an already-issued access token fail before expiry. External credential assertions check the live refresh family on every uncached exchange; see revocation.
- Tokens, secrets, codes, keys, and assertions are redacted from audit logs. An upstream token-exchange fault is
server_error(502 or 503) with an auditreason.invalid_targetis only an audience the exchanger rejected. - A resource server must never forward an inbound MCP token to a downstream API. See token boundaries.
Transport
MCP_AUTH_REQUIRE_HTTPSdefaults to true and checks canonical URLs and the request transport. The shipped binary listens over HTTP; this setting does not create a TLS listener. Production uses a trusted HTTPS reverse proxy. An application embedding the handler can provide a TLS listener itself.X-Forwarded-Protois set by the caller, so it is not accepted as proof of TLS unlessMCP_AUTH_TRUST_PROXY_TLS=true. That setting is a claim that a trusted proxy terminates TLS and is the only route in; a NetworkPolicy, security group, or equivalent is what makes the claim true. Enabling it without restricting network access lets anything that can reach the process satisfy the HTTPS requirement with one header.
What the deployment owns
- A persistent signing key from a secret manager, KMS, or HSM, with a rotation plan. The ephemeral development key is not usable in production.
- Durable shared storage. SQLite suits a single instance; multiple replicas need a shared database adapter, not an unencrypted volume.
- Restrictive CORS and trusted origins, rate limits, CSRF protection at the login boundary, secure cookies, and reverse-proxy request limits.
- The upstream identity provider: its realm or tenant, users, client credentials, availability, and certificates.
The production checklist is the short form of this page.