This repository demonstrates building an MCP (Model Context Protocol) server with HTTP transport and JWT authentication, progressing through iterative steps.
This repo is a companion to the in-depth, step-by-step blog posts on "MCP Authorization". See the following:
- Understanding MCP Authorization, Step by Step, Part One
- Understanding MCP Authorization, Step by Step, Part Two
- Understanding MCP Authorization, Step by Step, Part Three
Part 4 (late addition to the series): MCP Authorization With Dynamic Client Registration
The table below shows support for OAuth RFCs required by the MCP authorization specification across major identity providers.
- PKCE: Proof Key for Code Exchange (OAuth 2.1 requirement)
- RFC 8414: OAuth 2.0 Authorization Server Metadata
- RFC 7591: OAuth 2.0 Dynamic Client Registration Protocol
- RFC 8707: Resource Indicators for OAuth 2.0
Identity Provider | PKCE | RFC 8414 | RFC 7591 | RFC 8707 |
---|---|---|---|---|
Okta | Yes | Yes | Yes | N0 |
Auth0 | Yes | Yes | Kinda | No |
Keycloak | Yes | Yes | Yes | No |
Ping Federate | Yes | Yes | Yes | Yes |
ForgeRock | Yes | Yes | Yes | Kinda |
Google OAuth | Yes | No | No | No |
Microsoft Entra | Yes | Yes | No | No |
The project shows how to build a secure MCP server with:
- FastAPI-based HTTP transport
- JWT token authentication
- OAuth 2.0 metadata endpoints
- Scope-based authorization
- Role-based access control
- File:
http-transport-steps/src/mcp_http/step1.py
- What it adds: Basic FastAPI application with health endpoint
- Key features:
- FastAPI server setup
- Basic health check endpoint (
/health
) - Foundation for MCP HTTP transport
- File:
http-transport-steps/src/mcp_http/step2.py
- What it adds: MCP protocol request/response handling
- Key features:
- MCP request parsing and validation
- Basic MCP response structure
/mcp
endpoint for MCP protocol communication- JSON-RPC style request handling
- File:
http-transport-steps/src/mcp_http/step3.py
- What it adds: MCP tools and prompts without dispatching
- Key features:
- Tool definitions (
echo
,get_time
) - Prompt definitions (
greeting
,help
) - MCP protocol compliance for tools and prompts
- No actual tool execution yet
- Tool definitions (
- File:
http-transport-steps/src/mcp_http/step4.py
- What it adds: Actual tool execution and prompt handling
- Key features:
- Tool dispatching and execution
- Prompt retrieval and handling
- Working MCP server with functional tools
- Error handling for invalid requests
- File:
http-transport-steps/src/mcp_http/step5.py
- What it adds: JWT public key loading and JWKS endpoint
- Key features:
- Public key loading from file
- JWKS (JSON Web Key Set) endpoint (
/.well-known/jwks.json
) - External token generation script (
generate_token.py
) - JWT infrastructure foundation
- File:
http-transport-steps/src/mcp_http/step6.py
- What it adds: JWT authentication middleware and enforcement
- Key features:
- JWT token validation middleware
- Authentication enforcement on
/mcp
endpoint - User context extraction from tokens
- Proper error responses for invalid/missing tokens
- File:
http-transport-steps/src/mcp_http/step7.py
- What it adds: OAuth 2.0 metadata for protected resource and authorization server
- Key features:
/.well-known/oauth-protected-resource
endpoint/.well-known/oauth-authorization-server
endpoint- Enhanced health endpoint with OAuth metadata
- OAuth metadata in MCP responses
- File:
http-transport-steps/src/mcp_http/step8.py
- What it adds: Permission checking and role-based access control
- Key features:
check_permission
method for scope validation- Role-based access control (admin, user, guest)
- 403 Forbidden responses for insufficient permissions
- Scope enforcement for MCP operations
- What it will add: User context in responses and authenticated tools
- Planned features:
- User context in MCP response headers/metadata
- Authenticated tools with user-aware behavior
- Enhanced MCP protocol integration
- Personalized responses based on user identity
The JWT tokens include:
- User ID: Unique identifier for the user
- Scopes: Permissions (e.g.,
mcp:read
,mcp:tools
,mcp:prompts
) - Roles: User roles (e.g.,
admin
,user
,guest
) - Expiration: Token validity period
Each step includes a corresponding test script (test_stepX.sh
) that validates:
- Basic functionality
- JWT authentication (steps 5+)
- Authorization (steps 6+)
- OAuth metadata (steps 7+)
- Access control (steps 8+)
- Install
uv
: https://docs.astral.sh/uv/getting-started/installation/ - Navigate to the
http-transport-steps
directory
# Run any step using uv run
uv run step1
uv run step2
uv run step3
# ... etc
Step 10 supports environment-based configuration for Keycloak and MCP server URLs. You can specify an env file (not .env) using the --env
flag, or let it default to keycloak_direct.env
.
Two example env files are provided:
keycloak_direct.env
(for direct Keycloak access atlocalhost:8080
)keycloak_proxy.env
(for proxy access atlocalhost:9090
)
Example usage:
# Run step 10 with a specific env file (e.g., proxy)
uv run step10 --env keycloak_proxy.env
If the env file or environment variables are missing, the server will fall back to sensible defaults (localhost:8080, etc).
- you will need to run step10 mcp server
- you will have to allow anonymous client registration:
- add trusted hosts (check keycloak logs for the right IP)
- for trusted host policy, you don't need matching on URI
- allowable scopes for mcp:read, etc and aud mapper
- then run the step11 client
uv run step11
To run with mcp-inspector
- you'll need to run agentgateway with config.yaml
- uv run step10 --env keycloak_proxy.env
- run mcp-inspector UI (note, some of the auth stuff is broken, at the moment, use this: https://github.com/christian-posta/mcp-inspector/tree/ceposta-patches)
- then follow the step by step auth flow
mcp scopes issue: modelcontextprotocol/inspector#587
For steps 5-8 that require JWT authentication, you can generate tokens using the generate_token.py
script:
uv run python generate_token.py --username alice --scopes mcp:read,mcp:tools
uv run python generate_token.py --username bob --scopes mcp:read,mcp:prompts
uv run python generate_token.py --username admin --scopes mcp:read,mcp:tools,mcp:prompts
uv run python generate_token.py --username guest --scopes ""
To quickly get a token for testing step9/keycloak:
curl -X POST "http://localhost:8080/realms/mcp-realm/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "client_id=mcp-test-client" \
-d "username=mcp-admin" \
-d "password=admin123" \
-d "scope=openid profile email mcp:read mcp:tools mcp:prompts" | jq -r '.access_token'
The script will output a JWT token that can be used in the Authorization: Bearer <token>
header for authenticated requests.
- FastAPI
- PyJWT
- cryptography
- uvicorn
The project uses uv
for dependency management with pyproject.toml
configuration.