A clear, educational example demonstrating OAuth2 authentication for MCP (Model Context Protocol) servers using GitHub as the identity provider.
- OAuth2 - Authorization Code flow with PKCE
- Dynamic Client Registration (DCR) - RFC 7591 implementation for automatic client registration
- MCP Authorization - Official specification (2025-06-18)
- Security - Token validation, CSRF protection, resource indicators
- MCP SDK - Building servers with FastMCP and clients with MCP SDK
- AI Agents - Building interactive AI agents with pydantic-ai and MCP tools
# Install the package
pip install -e .
# Or with dev dependencies
pip install -e ".[dev]"
# Or use make
make install| File | Purpose | Start Here If... |
|---|---|---|
| docs/QUICKSTART.md | Get running in 5 minutes | You want to try it immediately |
| docs/GITHUB.md | GitHub OAuth app setup | Setting up for the first time |
| docs/INSPECTOR.md | MCP Inspector setup and testing | You want to test with MCP Inspector |
| docs/FLOW_EXPLAINED.md | Detailed OAuth flow | You want to understand how it works |
| docs/DIAGRAMS.md | Visual explanations | You prefer diagrams |
| docs/IMPLEMENTATION.md | Technical details | You're implementing your own |
| docs/DYNAMIC_CLIENT_REGISTRATION.md | Dynamic Client Registration (DCR) | You want to understand automatic client registration |
mcp-oauth/
├── mcp_oauth_example/ # Python package
│ ├── server.py # MCP server with OAuth2 (FastMCP + FastAPI)
│ ├── client.py # MCP client with OAuth2 flow
│ ├── agent.py # Pydantic AI agent with MCP tools
│ ├── oauth_client.py # Shared OAuth authentication logic
│ ├── __main__.py # CLI entry point
│ └── __init__.py # Package initialization
├── docs/ # Documentation
│ ├── GITHUB.md # GitHub OAuth app setup
│ ├── QUICKSTART.md # 5-minute getting started
│ ├── FLOW_EXPLAINED.md # Detailed OAuth flow walkthrough
│ ├── DIAGRAMS.md # Visual diagrams
│ └── IMPLEMENTATION.md # Technical implementation details
├── tests/ # Tests
│ └── test_auth.py # Basic tests
├── README.md # This file - complete reference
├── pyproject.toml # Package configuration
├── Makefile # Convenient make targets
└── config.json # OAuth credentials (template)
- Built with official MCP Python SDK (FastMCP)
- OAuth2 endpoints integrated with FastAPI
- Three example tools (calculator, greeter, server_info)
- Token validation middleware
- Uses HTTP Streaming (NDJSON) transport for efficient communication
- Built with official MCP Python SDK
- Handles complete OAuth2 flow (PKCE + state)
- Automatic metadata discovery
- Bearer token authentication
- Tests all available tools
- Interactive CLI agent powered by pydantic-ai
- Full access to MCP server tools
- Claude Sonnet 4.5 model
- Conversational interface with streaming responses
- Bearer token authentication with MCP server
- Built with official MCP Python SDK (FastMCP)
- Protected MCP server with OAuth2 integration
- Implements MCP Authorization specification (2025-06-18)
- Exposes OAuth metadata endpoints (RFC 9728, RFC 8414)
- Validates access tokens before serving tools
- Provides MCP tools via HTTP Streaming (NDJSON) transport
- Tools: calculator (add, multiply), greeter (hello, goodbye), server info
- Handles OAuth2 flow with GitHub
- Automatically opens browser for user authentication
- Manages access tokens
- Connects to MCP server using MCP SDK client
- Makes authenticated requests via MCP protocol (HTTP Streaming transport)
- Demonstrates proper MCP tool invocation with NDJSON format
- Interactive CLI agent powered by pydantic-ai
- Uses Anthropic Claude Sonnet 4.5 model
- Automatically authenticates with OAuth2
- Connects to MCP server with authenticated tools via HTTP Streaming
- Natural language interface to MCP tools
- Example: "What is 15 + 27?" → Uses calculator_add tool
- Shared authentication logic (used by both client.py and agent.py)
- Implements OAuth2 with PKCE (RFC 7636)
- Metadata discovery (RFC 8414, RFC 9728)
- Token management and validation
- Reusable for any MCP client implementation
- GitHub OAuth app credentials
- Server settings
- Create GitHub OAuth app → docs/GITHUB.md
- Configure → Edit
config.jsonwith your Client ID and Secret - Install →
make install(orpip install -e .) - Run server →
make start(orpython -m mcp_oauth_example server) - Run client →
make client(orpython -m mcp_oauth_example client) in a new terminal - Run agent →
make agent(orpython -m mcp_oauth_example agent) for interactive AI 🤖 - Inspect MCP →
make inspectorto launch the MCP Inspector for debugging 🔍
👉 Detailed walkthrough: docs/QUICKSTART.md
- Client connects → Server responds with 401 and OAuth metadata
- Discovery → Client fetches authorization server configuration
- Authentication → Browser opens for GitHub authorization (with PKCE)
- Token exchange → Client receives access token
- MCP access → Client invokes tools via SSE with Bearer token
- Validation → Server validates every request with GitHub
👉 Detailed flow: docs/FLOW_EXPLAINED.md
The server exposes these example MCP tools:
calculator_add(a, b)- Add two numberscalculator_multiply(a, b)- Multiply two numbersgreeter_hello(name)- Say hellogreeter_goodbye(name)- Say goodbyeget_server_info()- Get server information
Create config.json in the example directory:
{
"github": {
"client_id": "YOUR_GITHUB_CLIENT_ID",
"client_secret": "YOUR_GITHUB_CLIENT_SECRET"
},
"server": {
"host": "localhost",
"port": 8080
}
}See docs/GITHUB.md for how to get these credentials.
Note on ports:
- Port 8080: MCP server (configured in
config.json) - Port 8081: OAuth callback listener (used by client during authentication)
- The client runs a temporary HTTP server on port 8081 to receive the OAuth callback from GitHub
pip install -e .
python -m mcp_oauth_example server
# Or use make:
make startServer starts on http://localhost:8080 with OAuth metadata and MCP endpoints.
In a new terminal:
python -m mcp_oauth_example client
# Or use make:
make clientThe client will:
- Connect to MCP server (receives 401)
- Fetch OAuth metadata
- Open browser for GitHub authentication
- Exchange authorization code for access token
- Connect via SSE with Bearer token
- List and invoke MCP tools
The MCP Inspector is a visual debugging tool for MCP servers:
make inspector
# Or directly:
npx @modelcontextprotocol/inspectorThe inspector provides:
- Interactive UI for testing MCP tools
- Request/response visualization for debugging
- Protocol compliance checking
- Token management for OAuth-protected servers
💡 Tip: Start the server first (make start), then use the inspector to explore available tools and test authentication flows.
For detailed code explanations and flow diagrams, see:
- docs/FLOW_EXPLAINED.md - Step-by-step OAuth flow
- docs/DIAGRAMS.md - Visual diagrams
- docs/IMPLEMENTATION.md - Technical implementation details
Server (mcp_oauth_example/server.py):
- Uses FastMCP SDK with
@mcp.tool()decorators - FastAPI for HTTP/SSE transport
- JWT token validation middleware
- OAuth metadata endpoints (RFC 9728, RFC 8414)
Client (mcp_oauth_example/client.py):
- OAuth2 flow with PKCE (RFC 7636)
- Browser-based GitHub authentication
- MCP SDK client with SSE transport
- Proper MCP tool invocation
✅ PKCE - Prevents authorization code interception (RFC 7636)
✅ State Parameter - CSRF protection
✅ Resource Indicators - Token audience binding (RFC 8707)
✅ Token Validation - Every request verified with GitHub
✅ Bearer Token in Header - Never in URL
- ✅ MCP Authorization Specification (2025-06-18)
- ✅ OAuth 2.1 with PKCE
- ✅ RFC 9728 - Protected Resource Metadata
- ✅ RFC 8414 - Authorization Server Metadata
- ✅ RFC 8707 - Resource Indicators
- ✅ RFC 7636 - PKCE
This authentication pattern enables:
- Secure MCP servers - Protect tools with user authentication
- Multi-tenant deployments - Different users, different access
- Audit logging - Track which user invoked which tool
- Rate limiting - Limit requests per user, not just per IP
- Fine-grained authorization - Control tool access by user identity
Modify the server to restrict certain tools to specific users:
def check_tool_permission(user: str, tool: str) -> bool:
# Only admin users can use sensitive tools
if tool.startswith("admin:"):
return user in ADMIN_USERS
return TrueImplement token caching in the client to avoid re-authentication:
def get_cached_token():
if os.path.exists("token_cache.json"):
with open("token_cache.json") as f:
data = json.load(f)
if data["expires_at"] > time.time():
return data["access_token"]
return NoneAdd support for refresh tokens to extend sessions:
def refresh_access_token(refresh_token: str) -> str:
response = requests.post(token_url, data={
"grant_type": "refresh_token",
"refresh_token": refresh_token,
"client_id": client_id
})
return response.json()["access_token"]| Issue | Solution |
|---|---|
redirect_uri_mismatch |
Verify callback URL in GitHub OAuth app is exactly http://localhost:8081/callback (client uses port 8081) |
| Token validation fails | GitHub tokens expire - restart authentication flow |
| Browser doesn't open | Copy URL from terminal and open manually |
| Port already in use | Change port in config.json or use lsof -i :8080 to find/kill process |
More troubleshooting: docs/GITHUB.md
- MCP Authorization Specification
- OAuth 2.1 Draft
- GitHub OAuth Documentation
- RFC 7636 (PKCE), RFC 8414 (Auth Server Metadata), RFC 8707 (Resource Indicators), RFC 9728 (Protected Resource Metadata)
🎓 You now have a working example of OAuth2-protected MCP server!
Start with docs/QUICKSTART.md to get running in 5 minutes. 🚀