Mcp auth fastapi fastmcp scalekit
Skill scalekit-inc/skills/skills/mcp-auth-fastapi-fastmcp-scalekit
35 skills that teach AI coding agents to integrate Scalekit auth — agent auth, full-stack login, MCP OAuth 2.1, enterprise SSO, and SCIM. Works with Claude Code, Cursor, Windsurf, and 35+ other agents.
npx -y skills add scalekit-inc/skills --skill mcp-auth-fastapi-fastmcp-scalekitAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Add Scalekit OAuth authentication to a FastAPI + FastMCP server (Python). Use when you need FastAPI-level middleware control over token validation alongside FastMCP tools. Implements /.well-known/oauth-protected-resource, a Starlette middleware that validates Authorization Bearer tokens via Scalekit SDK (issuer + audience), and mounts FastMCP via app.mount.
SKILL.md
7.4 KB, as published. Nobody here has run it
Add OAuth auth to FastAPI + FastMCP (Scalekit)
When to use this skill vs the FastMCP skill
Use THIS skill when:
- You need custom FastAPI middleware, routes, or dependency injection alongside MCP tools
- You're adding MCP to an existing FastAPI application
- You need more control over request handling than FastMCP's built-in ScalekitProvider offers
Use the mcp-auth-fastmcp-scalekit skill instead when:
- You're building a standalone MCP server and don't need FastAPI-specific features
- You want the simplest possible setup (5-line auth config)
Critical wiring: how FastMCP mounts into FastAPI
FastMCP is NOT run standalone. It is mounted inside FastAPI:
mcp_app = mcp.http_app(path="/")
app = FastAPI(lifespan=mcp_app.lifespan) # lifespan MUST come from mcp_app
# ... add middleware and routes BEFORE mounting ...
app.mount("/", mcp_app) # MUST be last
Order matters:
- Create mcp_app from FastMCP
- Create FastAPI with lifespan=mcp_app.lifespan
- Add CORSMiddleware
- Add @app.middleware("http") auth middleware
- Add GET /.well-known/oauth-protected-resource (public)
- Add GET /health (public)
- app.mount("/", mcp_app) — ALWAYS last
Inputs to collect (ask if missing)
- PORT (default 3002)
- SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET
- EXPECTED_AUDIENCE — must match the Server URL registered in Scalekit (with trailing slash, e.g. http://localhost:3002/)
- PROTECTED_RESOURCE_METADATA JSON — copied from Scalekit dashboard MCP server page
Required outcomes
- GET /.well-known/oauth-protected-resource → returns PROTECTED_RESOURCE_METADATA JSON (no auth required)
- GET /health → {"status": "healthy"} (no auth required)
- @app.middleware("http") auth_middleware:
- Exempts /.well-known/oauth-protected-resource and /health
- Extracts Authorization: Bearer <token>
- On missing token → 401 + WWW-Authenticate header
- Validates via scalekit_client.validate_access_token(token, options=TokenValidationOptions(issuer=SK_ENV_URL, audience=[EXPECTED_AUDIENCE]))
- On invalid → 401 + WWW-Authenticate
- FastMCP mounted at / with lifespan=mcp_app.lifespan
- At least one @mcp.tool registered
Mode A — New project scaffold
Steps
-
Create directory and virtual environment:
mkdir -p <project-name> cd <project-name> python3 -m venv .venv source .venv/bin/activate -
Install dependencies:
pip install -r assets/requirements.txt -
Create .env from assets/env.example (fill real values from Scalekit dashboard).
-
Use assets/main-minimal.py as starting point. Add your tools using assets/tool-template.py.
-
Run:
python main.py -
Test with MCP Inspector (point to http://localhost:3002/ — NOT /mcp):
npx @modelcontextprotocol/inspector@latest
Mode B — Retrofit an existing FastAPI app
Identify insertion points
Ask for the existing server entrypoint. Look for:
- Where
app = FastAPI(...)is instantiated - Whether
app.mount(...)is already used - Existing middleware definitions and their order
- Whether POST / is already taken (if so, mount MCP at /mcp instead and update RESOURCE_METADATA_URL)
Patch plan (minimal diffs)
1. Add env vars
SK_ENV_URL=...
SK_CLIENT_ID=...
SK_CLIENT_SECRET=...
EXPECTED_AUDIENCE=http://localhost:3002/
PROTECTED_RESOURCE_METADATA='...'
2. Add imports
from fastmcp import FastMCP, Context
from scalekit import ScalekitClient
from scalekit.common.scalekit import TokenValidationOptions
from starlette.middleware.cors import CORSMiddleware
from fastapi import Request, Response
3. Add Scalekit client + constants
RESOURCE_METADATA_URL = f"http://localhost:{PORT}/.well-known/oauth-protected-resource"
WWW_HEADER = {
"WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{RESOURCE_METADATA_URL}"'
}
scalekit_client = ScalekitClient(
env_url=SK_ENV_URL,
client_id=SK_CLIENT_ID,
client_secret=SK_CLIENT_SECRET,
)
4. Create FastMCP instance + tools BEFORE patching FastAPI
mcp = FastMCP("Your Server Name", stateless_http=True)
@mcp.tool(name="your_tool", description="...")
async def your_tool(...) -> dict:
...
5. Patch FastAPI instantiation
Before:
app = FastAPI()
After:
mcp_app = mcp.http_app(path="/")
app = FastAPI(lifespan=mcp_app.lifespan)
WARNING: If the existing app already sets lifespan, merge the lifespans using an async context manager.
6. Add CORSMiddleware (before auth middleware)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["GET", "POST", "OPTIONS"],
allow_headers=["*"]
)
7. Add auth middleware
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
PUBLIC_PATHS = {"/health", "/.well-known/oauth-protected-resource"}
if request.url.path in PUBLIC_PATHS:
return await call_next(request)
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_HEADER,
media_type="application/json"
)
token = auth_header.split("Bearer ", 1)[0].strip()
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)
8. Add public routes
@app.get("/.well-known/oauth-protected-resource")
async def oauth_metadata():
if not PROTECTED_RESOURCE_METADATA:
return Response('{"error": "config missing"}', status_code=500, media_type="application/json")
return Response(json.dumps(json.loads(PROTECTED_RESOURCE_METADATA), indent=2), media_type="application/json")
@app.get("/health")
async def health_check():
return {"status": "healthy"}
9. Mount FastMCP LAST
app.mount("/", mcp_app)
Verification checklist
- GET /.well-known/oauth-protected-resource returns JSON without auth
- GET /health returns {"status": "healthy"}
- POST to MCP endpoint without token → 401 + WWW-Authenticate
- POST to MCP endpoint with valid token → tool response
- Wrong-audience token → 401
- Tool call works end-to-end in MCP Inspector (connect to http://localhost:PORT/)
See references/TROUBLESHOOTING.md for common issues.