Add OAuth 2.1 authorization to MCP servers (Scalekit)
Goal
Secure an MCP server so only authenticated + authorized users (and approved MCP clients) can call your tools, using Scalekit as the OAuth 2.1 authorization server and your MCP server as the resource server.
When to activate this skill
Activate when the user asks to:
- •"Add OAuth/OAuth 2.1/auth to my MCP server"
- •"Implement .well-known/oauth-protected-resource"
- •"Validate Bearer tokens / add middleware"
- •"Enforce scopes per tool" / "least privilege for MCP tools"
- •"Make my MCP server work with Claude Desktop / Cursor / VS Code auth"
Inputs you must collect (ask if missing)
- •MCP server base URL (public): e.g. https://mcp.example.com
- •Scalekit environment URL: https://<env>.scalekit.com (or provided)
- •Resource identifier to validate as
aud:- •Prefer the "Server URL" registered in Scalekit, OR
- •The Scalekit-generated resource id if no Server URL was configured
- •Scopes to support (and which tool requires which scope)
- •Framework/runtime: Node.js (Express/FastMCP) or Python (FastAPI)
- •Whether this is "public clients" usage (Claude/Cursor/VS Code) → recommend enabling DCR + CIMD in dashboard
Outputs you should produce
- •A public discovery endpoint:
/.well-known/oauth-protected-resource - •A token-validation middleware applied to MCP endpoints (excluding
/.well-known/*) - •Optional: per-tool scope authorization checks
- •A small production checklist (CORS, HTTPS, logging, secrets)
Procedure
1) Scalekit dashboard setup (resource server registration)
- •Create/register an MCP server in the Scalekit dashboard.
- •If this MCP server is meant to be used by public MCP clients, enable:
- •Dynamic Client Registration (DCR)
- •Client ID Metadata Document (CIMD)
- •Configure:
- •Server URL (recommended): set it to your MCP server URL if you want tokens to use it as audience.
- •Access token lifetime (typical: 5–60 minutes depending on risk).
- •Scopes: define permissions like
todo:read,todo:write, etc.
Note: If you toggle DCR/CIMD, restart the MCP server (some frameworks cache auth server details).
2) Implement MCP discovery endpoint
Implement GET /.well-known/oauth-protected-resource and return the resource metadata JSON from Scalekit dashboard ("Metadata JSON" for your MCP server).
Minimum fields to include:
- •
authorization_servers: array containing your Scalekit resource authorization server URL (from dashboard) - •
bearer_methods_supported: includeheader - •
resource: your MCP server identifier (usually your base URL) - •
resource_documentation: docs URL (optional but recommended) - •
scopes_supported: list of scopes you configured
If the user wants a template, point them to:
- •
assets/oauth-protected-resource.json - •
assets/express/well-known-route.tsorassets/fastapi/well_known_route.py
3) Add Bearer token validation middleware (resource server enforcement)
Apply middleware to all MCP endpoints. Rules:
- •Allow unauthenticated access to
/.well-known/*so clients can discover metadata. - •For all other routes:
- •Extract access token from
Authorization: Bearer <token> - •If missing → respond
401and includeWWW-Authenticate: Bearer ... resource_metadata="<your .well-known url>" - •Validate token using Scalekit SDK:
- •Validate
audincludes your configured resource identifier - •Validate issuer if your SDK requires/permits it
- •Validate expiry and issued-at (SDK typically handles this)
- •Validate
- •On validation failure → respond
401withWWW-Authenticate
- •Extract access token from
Templates:
- •Node/Express:
assets/express/auth-middleware.ts - •Python/FastAPI:
assets/fastapi/auth_middleware.py
Security notes:
- •Do not log raw tokens.
- •Prefer constant-time comparisons where relevant (SDK should handle signatures).
- •Keep the middleware small; push details to reference docs.
4) Optional: scope-based authorization at tool execution time
For each MCP tool, define required scope(s) and enforce them when executing the tool. Suggested approach:
- •Maintain a map:
tool_name -> required_scopes(seeassets/tool-scope-map.example.yaml) - •At execution:
- •Validate token again with
requiredScopes(or validate once and check claims, depending on SDK support) - •If insufficient scope → return an OAuth-style error response (403,
insufficient_scope) with a helpful message
- •Validate token again with
5) Testing checklist
- •Discovery works: call
/.well-known/oauth-protected-resourcewithout auth and confirm JSON is correct. - •Middleware rejects missing token (401 + WWW-Authenticate).
- •Valid token passes (200).
- •Token with wrong
audfails. - •Scope enforcement denies (403) and allows with correct scope.
- •Test with at least one MCP host (Claude Desktop / Cursor / VS Code) if that's your target.
6) Production checklist
See references/SECURITY.md. Minimum:
- •HTTPS everywhere
- •Correct CORS for MCP endpoints
- •Secure secret storage (env/secret manager)
- •Monitoring + audit logs for auth failures and tool execution
- •Reasonable token lifetimes
- •Validate resource audience (
aud) strictly - •Consider rate limiting on auth-related endpoints
References
- •Deep technical notes:
references/REFERENCE.md - •Scope design patterns:
references/SCOPES.md - •Production hardening:
references/SECURITY.md