Adding Resource Server Authentication to Your MCP Server
Resource Server authentication enables your HTTP server to act as an OAuth 2.1 Protected Resource (compliant with MCP’s specification for Authorization ), validating Bearer tokens on every request. This unlocks support for tool-level authorization and secrets on HTTP servers, allowing you to host secure anywhere (local, on-premise, or third-party hosted).
An OAuth 2.1 compliant authorization server (e.g., WorkOS AuthKit, Auth0, Descope, etc.)
Authorization server’s JWKS endpoint URL
Understanding Resource Server Authentication
What is it?
Resource Server authentication turns your server into an OAuth 2.1 Protected Resource that validates Bearer tokens on every HTTP request. Your trusts one or more Authorization Servers to issue valid tokens for accessing your MCP server.
Why is it needed?
By default, HTTP servers cannot use that require authorization or secrets for security reasons.
Resource Server authentication solves this by:
Authenticating every request - Validates the Bearer token before processing any messages
Extracting identity - The token’s sub claim becomes the context.user_id for
Enabling secure tools - Tools requiring authorization or secrets can now safely execute over HTTP
Supporting OAuth discovery - MCP clients can automatically discover your authentication requirements
Resource Server auth vs Tool-level auth: Resource Server authentication secures access to your MCP server, while tool-level authorization secures access to third-party APIs that your tools use. They work together: Resource Server auth identifies who is calling your server, and tool-level auth enables tools to act on behalf of that user.
Choose Your Configuration Approach
The arcade_mcp_server.resource_server module provides two validators:
ResourceServer - Full-featured OAuth 2.1 Resource Server with:
Support for multiple authorization servers (multi-IdP, regional endpoints)
OAuth discovery metadata endpoint
Environment variable configuration
Best for production deployments
JWKSTokenValidator (Simple)
JWKSTokenValidator - Direct JWKS-based validation with:
Simple setup for single authorization server
No OAuth discovery endpoint
Requires explicit configuration
Best for development or simple use cases
Configure Your Authorization Server
First, gather these details from your authorization server:
Authorization Server URL - The base URL of your authorization server (e.g., https://your-app.authkit.app)
Issuer - The expected iss claim in tokens (usually same as authorization server URL)
JWKS URI - Where to fetch public keys for token verification (e.g., https://your-app.authkit.app/oauth2/jwks)
Canonical URL - Your MCP server’s public URL (e.g., http://127.0.0.1:8000/mcp if running locally)
The Canonical URL must match the aud (audience) claim in tokens issued by your authorization server. Configure your authorization server to issue tokens with your MCP server’s URL as the audience.
Add Authentication to Your Server
Update your server.py to add the auth parameter to MCPApp:
#!/usr/bin/env python3"""my_server MCP server"""from arcade_mcp_server import MCPAppfrom arcade_mcp_server.resource_server import ( AccessTokenValidationOptions, AuthorizationServerEntry, ResourceServer,)# Setup your resource server that trusts a single Authkit authorization serverresource_server = ResourceServer( canonical_url="http://127.0.0.1:8000/mcp", authorization_servers=[ AuthorizationServerEntry( authorization_server_url="https://your-workos.authkit.app", issuer="https://your-workos.authkit.app", jwks_uri="https://your-workos.authkit.app/oauth2/jwks", algorithm="RS256", # Authkit doesn't set the aud claim as the MCP server's canonical URL validation_options=AccessTokenValidationOptions( verify_aud=False, ), ) ],)# Pass the resource_server to MCPAppapp = MCPApp( name="my_server", version="1.0.0", auth=resource_server # Enable Resource Server authentication)# Your tools here...@app.tooldef greet(name: Annotated[str, "The name of the person to greet"]) -> str: """Greet a person by name.""" return f"Hello, {name}!"if __name__ == "__main__": app.run(transport="http", host="127.0.0.1", port=8000)
Environment variable configuration is recommended for production as it separates auth configuration from your code and allows deployment-time configuration.
Your server now requires valid Bearer tokens for all requests. You should see output like:
Terminal
INFO | 14:23:45 | Starting my_server v1.0.0 with 3 toolsINFO | 14:23:45 | Resource Server authentication enabled: TrueINFO | 14:23:45 | Accepted authorization server(s): https://your-app.authkit.app
OAuth Discovery
Now that your server is protected, you can see that your server exposes an OAuth discovery endpoint at http://127.0.0.1:8000/mcp/.well-known/oauth-protected-resource. This endpoint is used by MCP clients to discover the authorization servers that are trusted by your server.
Security Note: Token signature verification is always enabled and cannot be disabled. Additionally, the sub claim must always be present. Only disable other validations if your authorization server doesn’t comply with MCP and you accept the risk of not validating all claims in the token.
Different JWT Algorithms
Python
AuthorizationServerEntry( authorization_server_url="https://auth.example.com", issuer="https://auth.example.com", jwks_uri="https://auth.example.com/jwks", algorithm="ES256", # Use ECDSA instead of RSA)
AuthorizationServerEntry( authorization_server_url="https://your-app.authkit.app", issuer="https://your-app.authkit.app", jwks_uri="https://your-app.authkit.app/oauth2/jwks", validation_options=AccessTokenValidationOptions( verify_aud=False, # AuthKit may not set aud claim ),)