mcp-oauth-example

mbroton/mcp-oauth-example

3.3

If you are the rightful owner of mcp-oauth-example and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.

This project provides a minimal, non-production example of an OAuth 2.1 Authorization Server using FastAPI and a separate Resource Server using FastMCP, designed for learning and inspection purposes.

MCP OAuth example (separate Authorization Server and Resource Server)

Minimal, non‑production example of an OAuth 2.1 Authorization Server (FastAPI) and a separate Resource Server (FastMCP) aligned with MCP protocol revision 2025‑06‑18, which requires the authorization server to be separate from the resource server.

  • Authorization flow: Authorization Code + PKCE (S256)
  • Tokens: JWT access tokens (RS256) with JWKS discovery
  • Resource server: validates iss, aud, signature, and required scopes
  • Dynamic client registration enabled on the authorization server

This is for learning and inspection only. Storage is in memory, keys are generated on start, and nothing is persisted.

Components

  • auth_server/ — FastAPI OAuth authorization server
    • Well-known metadata, dynamic client registration, /authorize, /token, /oauth/jwks
    • In-memory users and consents
  • mcp_server/ — FastMCP resource server
    • Uses fastmcp with OAuthProvider and JWTVerifier
    • Exposes a trivial tool: “Greet someone”

Requirements

  • Python 3.12+
  • uv (for dependency management and running)

Running

Authorization server (default http://localhost:8999):

cd auth_server
uv sync
uv run uvicorn src.app:app --host 0.0.0.0 --port 8999

Resource server (default http://localhost:8990):

cd mcp_server
uv sync
uv run python -m src.server

Configuration

Both services read a local .env file in their own directories. For testing you don't need to change anything, there are default values for all variables.

Authorization server (auth_server/src/config.py, prefix AUTH_SERVER_):

  • AUTH_SERVER_ISSUER (default http://localhost:8999)
  • AUTH_SERVER_RESOURCE (default http://localhost:8990)
  • AUTH_SERVER_KNOWN_RESOURCES (defaults to RESOURCE if not set)
  • AUTH_SERVER_SESSION_SECRET (change from the dev default)
  • TTLs: AUTH_SERVER_ACCESS_TOKEN_TTL_SECONDS (3600), AUTH_SERVER_AUTH_CODE_TTL_SECONDS (600)

Resource server (mcp_server/src/config.py):

  • AUTHORIZATION_SERVER_URL (default http://localhost:8999)
  • RESOURCE_SERVER_URL (default http://localhost:8990)
  • JWKS_ENDPOINT (default /oauth/jwks)
  • REQUIRED_SCOPES (default mcp.read,mcp.write)

Auth server endpoints (summary)

  • GET /.well-known/oauth-authorization-server — metadata
  • POST /register — dynamic client registration
  • GET /oauth/jwks — JWKS (public keys)
  • GET,POST /authorize — authorization endpoint (code + PKCE)
  • POST /token — token endpoint (JWT access tokens)
  • GET,POST /login — simple form login (in-memory users)

Demo data

  • Users: alice/password123, bob/hunter2
  • Scopes: mcp.read, mcp.write

Testing with an MCP protocol inspector

  1. Start both servers as above.
  2. In the inspector, type URL http://localhost:8990 and select streamable HTTP transport type (left sidebar).
  3. Click "Open Auth Settings" (area in the middle of the screen).
  4. You can go either with Quick OAuth Flow or with Guided (in which you will go step by step by clicking on the Continue button).
  5. In the process you will be redirected to the authorization server to login. Use a demo user, approve requested scopes, and you’ll be redirected back.
  6. You should reach "Authentication Complete" step.

Notes and limitations

  • In-memory storage for users, clients, auth codes, and consents. Restart clears everything, including dynamic client registrations.
  • RSA keys are generated on startup; JWKS changes on restart.
  • Session cookie is HttpOnly and Secure; in pure HTTP setups some browsers won’t store it.
  • No refresh tokens, no DB, no migrations, no hardening. Intended only for exploration and protocol inspection.