skills/fastapi-fastmcp/SKILL.md
Build a production-ready MCP server using FastAPI and FastMCP with OAuth 2.1 Bearer token authentication via Scalekit. Use when the user wants to build an MCP server with FastAPI/FastMCP and needs fine-grained control over authentication middleware and token validation.
npx skillsauth add scalekit-inc/skills fastapi-fastmcpInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill documents the pattern for building production-ready MCP (Model Context Protocol) servers using FastAPI and FastMCP with OAuth 2.1 Bearer token authentication via Scalekit. This approach provides fine-grained control over authentication middleware, token validation, and server behavior compared to using FastMCP's built-in OAuth provider.
Use this FastAPI + FastMCP integration when you need:
Don't use this pattern if FastMCP's built-in OAuth provider meets your needs—the additional FastAPI layer adds complexity.
MCP Client → FastAPI Server (401 + WWW-Authenticate)
MCP Client → Scalekit (Exchange code for token)
Scalekit → MCP Client (Bearer token)
MCP Client → FastAPI Server (Request + Bearer token)
FastAPI Middleware → Scalekit SDK (Validate token)
FastAPI → MCP Tool Handler → Response
/.well-known/oauth-protected-resource) for client discovery@mcp.tool decoratorRequired variables:
SK_ENV_URL: Scalekit environment URL (issuer)SK_CLIENT_ID + SK_CLIENT_SECRET: SDK authentication credentialsEXPECTED_AUDIENCE: The resource identifier that tokens must targetPROTECTED_RESOURCE_METADATA: Complete OAuth discovery metadata JSONPORT: Server listening port (must match registered server URL)Security:
.env files to version controlSK_CLIENT_SECRET regularlyEXPECTED_AUDIENCE matches your server's public URL exactly@app.middleware("http")
async def auth_middleware(request: Request, call_next):
# Exempt public endpoints
if request.url.path in {"/health", "/.well-known/oauth-protected-resource"}:
return await call_next(request)
# Extract Bearer token
auth_header = request.headers.get("authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return Response(
'{"error": "Missing Bearer token"}',
status_code=401,
headers={"WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{RESOURCE_METADATA_URL}"'},
media_type="application/json"
)
token = auth_header.split("Bearer ", 1)[1].strip()
# Validate with Scalekit SDK
options = TokenValidationOptions(
issuer=SK_ENV_URL,
audience=[EXPECTED_AUDIENCE]
)
try:
is_valid = scalekit_client.validate_access_token(token, options=options)
if not is_valid:
raise ValueError("Invalid token")
except Exception:
return Response(
'{"error": "Token validation failed"}',
status_code=401,
headers=WWW_HEADER,
media_type="application/json"
)
return await call_next(request)
Key principles:
@app.get("/.well-known/oauth-protected-resource")
async def oauth_metadata():
if not PROTECTED_RESOURCE_METADATA:
return Response(
'{"error": "PROTECTED_RESOURCE_METADATA config missing"}',
status_code=500,
media_type="application/json"
)
metadata = json.loads(PROTECTED_RESOURCE_METADATA)
return Response(
json.dumps(metadata, indent=2),
media_type="application/json"
)
Purpose:
@mcp.tool(
name="greet_user",
description="Greets the user with a personalized message."
)
async def greet_user(name: str, ctx: Context | None = None) -> dict:
return {
"content": [
{
"type": "text",
"text": f"Hi {name}, welcome to Scalekit!"
}
]
}
Tool design:
ctx: Context | None to access authenticated user context in futurecontent arraymcp_app = mcp.http_app(path="/")
app = FastAPI(lifespan=mcp_app.lifespan)
# Add middleware (CORS, auth, etc.)
app.add_middleware(CORSMiddleware, ...)
# Add custom endpoints
@app.get("/health")
async def health_check():
return {"status": "healthy"}
# Mount MCP at root
app.mount("/", mcp_app)
Layering order:
exp claimuvicorn main:app --workers 4npx @modelcontextprotocol/inspector@latest
http://localhost:3002/# Get token from Scalekit (via OAuth flow or test endpoint)
export TOKEN="<your-access-token>"
# Test authenticated request
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"method":"tools/call","params":{"name":"greet_user","arguments":{"name":"Saif"}}}' \
http://localhost:3002/
# Test missing token (should return 401)
curl -v http://localhost:3002/
import pytest
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_missing_token():
response = client.post("/")
assert response.status_code == 401
assert "WWW-Authenticate" in response.headers
def test_invalid_token():
response = client.post(
"/",
headers={"Authorization": "Bearer invalid-token"}
)
assert response.status_code == 401
def test_health_check():
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "healthy"}
Symptom: Tokens fail validation with "invalid audience" error
Cause: EXPECTED_AUDIENCE doesn't match the Server URL registered in Scalekit
Fix: Ensure both have identical values including trailing slashes
Symptom: CORS errors or authentication bypassed Cause: Mounting order affects execution sequence Fix: Add middleware before mounting MCP app; mount MCP last
Symptom: Clients can't discover how to authenticate
Cause: PROTECTED_RESOURCE_METADATA not set or endpoint not working
Fix: Verify metadata JSON is copied correctly from Scalekit dashboard
Symptom: Works locally but fails in production
Cause: Hardcoded localhost URLs in configuration
Fix: Use environment-specific values for EXPECTED_AUDIENCE and RESOURCE_METADATA_URL
Symptom: Tests pass initially then fail after 1 hour Cause: Access tokens expire (default 3600 seconds) Fix: Refresh tokens before each test run or implement automatic refresh
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
# ... existing token validation ...
# Decode token to access claims (after validation)
import jwt
decoded = jwt.decode(token, options={"verify_signature": False})
scopes = decoded.get("scope", "").split()
# Attach to request state
request.state.scopes = scopes
request.state.user_id = decoded.get("sub")
return await call_next(request)
@mcp.tool()
async def admin_tool(ctx: Context) -> dict:
# Access request state in tool
if "admin" not in ctx.request_context.state.scopes:
raise PermissionError("Requires admin scope")
# ... tool logic ...
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
# ... validate token ...
decoded = jwt.decode(token, options={"verify_signature": False})
org_id = decoded.get("org_id")
request.state.org_id = org_id
return await call_next(request)
@mcp.tool()
async def get_org_data(ctx: Context) -> dict:
org_id = ctx.request_context.state.org_id
# Fetch data scoped to org_id
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.post("/")
@limiter.limit("10/minute")
async def mcp_endpoint(request: Request):
# Rate-limited MCP calls
pass
mcp>=1.0.0 # MCP protocol implementation
fastapi>=0.104.0 # Web framework
fastmcp>=0.8.0 # FastMCP integration
uvicorn>=0.24.0 # ASGI server
pydantic>=2.5.0 # Data validation
python-dotenv>=1.0.0 # Environment variables
httpx>=0.25.0 # HTTP client
python-jose[cryptography]>=3.3.0 # JWT handling
cryptography>=41.0.0 # Cryptographic operations
scalekit-sdk-python>=2.4.0 # Scalekit authentication
starlette>=0.27.0 # ASGI toolkit
requirements.txt>= for development flexibilityA full production-ready FastAPI + FastMCP server is available in the Scalekit MCP Auth Demos repository:
GitHub Repository: scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python
This example includes:
main.py - Main server entry point with FastAPI appsrc/config/config.py - Environment configurationsrc/lib/auth.py - OAuth discovery endpoint handlersrc/lib/middleware.py - Token validation middlewaresrc/lib/transport.py - MCP transport layer setupcd greeting-mcp-python
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python main.py
See README.md for complete setup instructions.
tools
Create or review Scalekit custom providers/connectors for proxy-only usage, including MCP providers. Use this skill when the task is to gather API docs, infer whether a connector is OAuth, Basic, Bearer, or API Key, determine if it is an MCP provider, determine required tracked fields like domain or version, generate provider JSON, check for existing custom providers, show update diffs, run approved create or update curls, and print resolved delete curls.
tools
Use when a developer is new to Scalekit and needs guidance on where to start, doesn't know which auth plugin or skill to choose, wants to connect an AI agent or agentic workflow to third-party services (Gmail, Slack, Notion, Google Calendar), needs OAuth or tool-calling auth for agents, wants to add authentication to a project but hasn't chosen an approach yet, or needs to install the Scalekit plugin for their AI coding tool (Claude Code, Codex, Copilot CLI, Cursor, or other agents).
tools
Use when a user asks to generate, review, validate, or fix any code snippet that uses Scalekit APIs or SDKs. This skill is the single source of truth for Scalekit code correctness — it can generate illustration-quality snippets from scratch (for docs, websites, or integration guides) and review existing code to catch wrong method names, missing parameters, security anti-patterns, and broken auth flows. Covers all four SDKs (Node, Python, Go, Java), raw REST API calls, and both Scalekit product suites — SaaSKit (SSO, login, sessions, RBAC, SCIM) and AgentKit (connections, tool calling, MCP auth). Use when the user says review my Scalekit code, generate a Scalekit example, validate this auth flow, check my SDK usage, fix my Scalekit integration, write a code sample for docs, or anything involving Scalekit code quality.
development
Walks through a structured production readiness checklist for Scalekit SSO implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, hardening their SSO setup, or wants to verify their Scalekit implementation is production-ready.