Guides users through adding OAuth 2.1 authorization to Model Context Protocol (MCP) servers using Scalekit. Use when setting up MCP servers, implementing authentication for AI hosts like Claude Desktop, Cursor, or VS Code, or when users mention MCP security, OAuth, or Scalekit integration.
Secure your MCP server with production-ready OAuth 2.1 authorization using Scalekit. This enables authenticated access through AI hosts like Claude Desktop, Cursor, and VS Code.
⚠️ MCP OAuth requires HTTP-based transport (Streamable HTTP): OAuth 2.1 authentication only works when your MCP server is exposed over HTTP using the Streamable HTTP transport. The standard StdioServerTransport (stdin/stdout) does not support OAuth flows.
Node.js requirement:
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
Python requirement (Streamable HTTP via ASGI app):
In Python, the practical equivalent of Node’s StreamableHTTPServerTransport is to and run it behind an ASGI server (Uvicorn/Hypercorn). The official Python SDK exposes this as (convenience) or (lower-level).
streamable_http_app()create_streamable_http_app(...)Accurate Python snippet (FastMCP + Streamable HTTP):
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
@mcp.tool
def ping() -> str:
return "pong"
# HTTP-based transport required for OAuth-capable deployments
app = mcp.streamable_http_app(path="/mcp")
Lower-level equivalent (explicit constructor):
from mcp.server.fastmcp import FastMCP
from fastmcp.server.http import create_streamable_http_app
mcp = FastMCP("My MCP Server")
app = create_streamable_http_app(server=mcp, streamable_http_path="/mcp")
Notes:
mcp). See: https://pypi.org/project/mcp/1.9.1/app you run with an ASGI server (e.g. uvicorn module:app)—this is Streamable HTTP, not stdio.https://gofastmcp.com/python-sdk/fastmcp-server-http
uvicorn your_module:app --host 0.0.0.0 --port 8000If your MCP server currently uses stdio transport, you must migrate to HTTP-based transport before implementing OAuth. See MCP Transport Documentation for migration guidance.
Copy this checklist and track progress:
MCP OAuth Setup:
- [ ] Step 1: Install Scalekit SDK
- [ ] Step 2: Register MCP server in Scalekit dashboard
- [ ] Step 3: Implement discovery endpoint
- [ ] Step 4: Add token validation middleware
- [ ] Step 5: (Optional) Add scope-based authorization
- [ ] Step 6: Test with AI hosts
Node.js:
npm install @scalekit-sdk/[email protected]
Python:
pip install scalekit-sdk-python
Get credentials from Scalekit dashboard after creating an account.
In Scalekit dashboard:
Advanced settings (optional):
https://mcp.yourapp.com)todo:read, todo:writeImportant: Restart your MCP server after toggling DCR or CIMD settings.
Create /.well-known/oauth-protected-resource endpoint. Copy metadata JSON from Dashboard > MCP Servers > Your server > Metadata JSON.
Node.js (Express):
app.get('/.well-known/oauth-protected-resource', (req, res) => {
res.json({
"authorization_servers": [
"https://<SCALEKIT_ENVIRONMENT_URL>/resources/<YOUR_RESOURCE_ID>"
],
"bearer_methods_supported": ["header"],
"resource": "https://mcp.yourapp.com",
"resource_documentation": "https://mcp.yourapp.com/docs",
"scopes_supported": ["todo:read", "todo:write"]
});
});
Python (FastAPI):
@app.get("/.well-known/oauth-protected-resource")
async def get_oauth_protected_resource():
return {
"authorization_servers": [
"https://<SCALEKIT_ENVIRONMENT_URL>/resources/<YOUR_RESOURCE_ID>"
],
"bearer_methods_supported": ["header"],
"resource": "https://mcp.yourapp.com",
"resource_documentation": "https://mcp.yourapp.com/docs",
"scopes_supported": ["todo:read", "todo:write"]
}
Replace placeholders with actual values from Scalekit dashboard.
Node.js:
import { Scalekit } from '@scalekit-sdk/node';
const scalekit = new Scalekit(
process.env.SCALEKIT_ENVIRONMENT_URL,
process.env.SCALEKIT_CLIENT_ID,
process.env.SCALEKIT_CLIENT_SECRET
);
const RESOURCE_ID = 'https://your-mcp-server.com'; // Or autogenerated ID from dashboard
const METADATA_ENDPOINT = 'https://your-mcp-server.com/.well-known/oauth-protected-resource';
export const WWWHeader = {
HeaderKey: 'WWW-Authenticate',
HeaderValue: `Bearer realm="OAuth", resource_metadata="${METADATA_ENDPOINT}"`
};
Python:
from scalekit import ScalekitClient
import os
scalekit_client = ScalekitClient(
env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET")
)
RESOURCE_ID = "https://your-mcp-server.com"
METADATA_ENDPOINT = "https://your-mcp-server.com/.well-known/oauth-protected-resource"
WWW_HEADER = {
"WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{METADATA_ENDPOINT}"'
}
Node.js:
export async function authMiddleware(req, res, next) {
try {
// Allow public access to well-known endpoints
if (req.path.includes('.well-known')) {
return next();
}
// Extract Bearer token
const authHeader = req.headers['authorization'];
const token = authHeader?.startsWith('Bearer ')
? authHeader.split('Bearer ')[1]?.trim()
: null;
if (!token) {
throw new Error('Missing or invalid Bearer token');
}
// Validate token against resource audience
await scalekit.validateToken(token, {
audience: [RESOURCE_ID]
});
next();
} catch (err) {
return res
.status(401)
.set(WWWHeader.HeaderKey, WWWHeader.HeaderValue)
.end();
}
}
// Apply to all MCP endpoints
app.use('/', authMiddleware);
Python:
from scalekit.common.scalekit import TokenValidationOptions
from fastapi import Request, HTTPException, status
async def auth_middleware(request: Request, call_next):
# Allow public access to well-known endpoints
if request.url.path.startswith("/.well-known"):
return await call_next(request)
# Extract Bearer token
auth_header = request.headers.get("Authorization", "")
token = None
if auth_header.startswith("Bearer "):
token = auth_header.split("Bearer ")[1].strip()
if not token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
headers=WWW_HEADER
)
# Validate token
try:
options = TokenValidationOptions(
issuer=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
audience=[RESOURCE_ID]
)
scalekit_client.validate_token(token, options=options)
except Exception:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
headers=WWW_HEADER
)
return await call_next(request)
# Apply to all MCP endpoints
app.middleware("http")(auth_middleware)
Add fine-grained access control at the tool execution level:
Node.js:
try {
await scalekit.validateToken(token, {
audience: [RESOURCE_ID],
requiredScopes: [scope] // e.g., 'todo:write'
});
} catch(error) {
return res.status(403).json({
error: 'insufficient_scope',
error_description: `Required scope: ${scope}`,
scope: scope
});
}
Python: