mbroton/mcp-oauth-example
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
- Well-known metadata, dynamic client registration,
mcp_server/
— FastMCP resource server- Uses
fastmcp
withOAuthProvider
andJWTVerifier
- Exposes a trivial tool: “Greet someone”
- Uses
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
(defaulthttp://localhost:8999
)AUTH_SERVER_RESOURCE
(defaulthttp://localhost:8990
)AUTH_SERVER_KNOWN_RESOURCES
(defaults toRESOURCE
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
(defaulthttp://localhost:8999
)RESOURCE_SERVER_URL
(defaulthttp://localhost:8990
)JWKS_ENDPOINT
(default/oauth/jwks
)REQUIRED_SCOPES
(defaultmcp.read,mcp.write
)
Auth server endpoints (summary)
GET /.well-known/oauth-authorization-server
— metadataPOST /register
— dynamic client registrationGET /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
- Start both servers as above.
- In the inspector, type URL
http://localhost:8990
and select streamable HTTP transport type (left sidebar). - Click "Open Auth Settings" (area in the middle of the screen).
- 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).
- 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.
- 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
andSecure
; 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.