agentsclimarketplace

Mcp auth fastapi fastmcp scalekit

Skill scalekit-inc/skills/skills/mcp-auth-fastapi-fastmcp-scalekit

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.From its SKILL.md

Install
npx -y skills add scalekit-inc/skills --skill mcp-auth-fastapi-fastmcp-scalekit

Assembled 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.

SKILL.md

7.4 KB, ~1.7k tokens by cl100k_base, 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:

  1. Create mcp_app from FastMCP
  2. Create FastAPI with lifespan=mcp_app.lifespan
  3. Add CORSMiddleware
  4. Add @app.middleware("http") auth middleware
  5. Add GET /.well-known/oauth-protected-resource (public)
  6. Add GET /health (public)
  7. 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

  1. GET /.well-known/oauth-protected-resource → returns PROTECTED_RESOURCE_METADATA JSON (no auth required)
  2. GET /health → {"status": "healthy"} (no auth required)
  3. @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
  4. FastMCP mounted at / with lifespan=mcp_app.lifespan
  5. At least one @mcp.tool registered

Mode A — New project scaffold

Steps

  1. Create directory and virtual environment:

    mkdir -p <project-name>
    cd <project-name>
    python3 -m venv .venv
    source .venv/bin/activate
    
  2. Install dependencies:

    pip install -r assets/requirements.txt
    
  3. Create .env from assets/env.example (fill real values from Scalekit dashboard).

  4. Use assets/main-minimal.py as starting point. Add your tools using assets/tool-template.py.

  5. Run:

    python main.py
    
  6. 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.

What ships with it: 7 files

25.0 KB alongside SKILL.md, 3 of them executable

assets/

references/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.