Skip to main content
Glama
dawa

mcp_auth_server

by dawa

mcp_auth_server

A local, runnable proof of concept of the MCP Authorization spec (2025-11-25).

It demonstrates, end to end:

  • an MCP server acting as a proper OAuth 2.1 resource server — publishes RFC 9728 Protected Resource Metadata, validates audience-bound access tokens, and enforces scope on a protected tool;

  • a one-command MCP client that discovers the authorization server, registers via Client ID Metadata Documents (CIMD), runs the full OAuth 2.1 + PKCE handshake (including a step-up re-authorization), and calls the protected tool;

  • a minimal custom OAuth 2.1 authorization server behind the MCP server.

Why a custom authorization server?

CIMD is draft-ietf-oauth-client-id-metadata-document-00. No off-the-shelf OSS or SaaS authorization server supports it yet, so using one would still require writing a CIMD shim. A small custom AS gives full, correct CIMD support and lets the whole thing run locally as a single command.

Related MCP server: MCP Server OAuth Toy

Quick start

uv venv && uv pip install -e ".[dev]"   # one-time setup
./scripts/run.sh                          # or: uv run python -m mcp_auth.demo

run.sh starts the authorization server and the resource server, then runs the client, which prints a step-by-step trace of the handshake and the protected tool's result, e.g.:

[1] Discovery
  401 challenge -> resource_metadata=...
  PRM: resource=http://localhost:8001/mcp as=['http://localhost:9000/'] scopes=['mcp:connect']
  AS metadata discovered at http://localhost:9000/.well-known/oauth-authorization-server
[2] Authorization (CIMD + PKCE) for base scope
[3] Call unprotected tool 'whoami'
[4] Call protected tool 'get_weather' (expect insufficient scope)
[5] Step-up re-authorization for the extra scope
[6] Retry 'get_weather' with stepped-up token
=== Success: full CIMD + OAuth 2.1 + step-up flow completed ===

Architecture

src/mcp_auth/
  config.py              shared ports, URLs, scopes, token TTL
  keys.py                authorization-server RSA signing key + JWKS export
  auth_server/           custom OAuth 2.1 authorization server (Starlette)
    app.py               AS metadata, /authorize, /token, /jwks
    cimd.py              fetch + validate Client ID Metadata Documents
    store.py             single-use authorization-code store
    tokens.py            RS256 JWT minting (aud=resource) + PKCE S256 verify
  resource_server/       FastMCP server as an OAuth 2.1 resource server
    server.py            FastMCP app + whoami + protected get_weather tool
    verifier.py          JWT verifier: signature, issuer, expiry, audience
  client/                one-command CIMD-driven MCP client
    metadata.py          the client's CIMD document + a server that hosts it
    flow.py              401 -> PRM -> AS discovery -> PKCE authorize -> token
    run.py               full journey incl. step-up
  demo.py                orchestrator (single command)

Default ports: authorization server :9000, resource server :8001, client CIMD host :3000. The canonical resource URI (the token audience) is http://localhost:8001/mcp.

How the flow maps to the spec

Spec requirement

Where

RFC 9728 Protected Resource Metadata

FastMCP, auto-served at /.well-known/oauth-protected-resource/mcp

401 + WWW-Authenticate with resource_metadata

FastMCP RequireAuthMiddleware

RFC 8414 AS metadata incl. code_challenge_methods_supported, client_id_metadata_document_supported

auth_server/app.py

CIMD: fetch client doc, validate client_id == URL, validate redirect_uri

auth_server/cimd.py

Authorization code + PKCE S256

auth_server/app.py, client/flow.py

RFC 8707 resource parameter in auth + token requests

client/flow.py

Audience-bound tokens; resource server validates aud

auth_server/tokens.py, resource_server/verifier.py

Scope enforcement + insufficient_scope + step-up

resource_server/server.py, client/run.py

Tests

uv run pytest

Covers AS/PRM metadata shape, CIMD validation (mismatched client_id, unlisted redirect URI, missing fields, SSRF host), PKCE and token claims, audience/expiry rejection in the verifier, and a full single-command end-to-end run.

Deliberate deviations from the spec (because this is a local PoC)

These are the conscious trade-offs that let the PoC run fully locally with one command. Each would be tightened for a real deployment:

  • HTTP on localhost instead of HTTPS. The spec mandates HTTPS for AS endpoints and the CIMD client_id URL; localhost redirect URIs are already spec-permitted. The MCP SDK's AnyHttpUrl accepts http, so no patching is needed.

  • Headless auto-approved consent. The AS authenticates a fixed demo subject and issues a code without a browser prompt, so the client reads the redirect directly (no callback listener). A real AS would authenticate the user and show a consent screen.

  • In-memory, process-local state (signing key, authorization codes). No persistence, no refresh-token rotation.

  • SSRF guard is a localhost allowlist for CIMD fetches, sufficient for local use; a real AS needs a stricter trust policy per the CIMD draft's security considerations.

Out of scope

  • infra/terraform/ (anticipated by .gitignore) — a local PoC needs no cloud infra.

  • Multi-user login UI, Dynamic Client Registration (CIMD is used instead), persistent storage.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A remote MCP server implementation that demonstrates authentication and authorization capabilities using OAuth 2.1. This is a workshop project for learning how to build secure MCP servers with user authentication.
    18,182 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A demo MCP server protected by OAuth (DCR), enabling hands-on exploration of OAuth flow for local MCP servers.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    An MCP authorization server implementing OAuth2/OIDC flows, supporting dynamic client registration, PKCE, consent screens, and client ID metadata documents for MCP authorization.
    -