FastAPI x FastMCP quickstart
This guide shows you how Scalekit’s provider secures any FastAPI x FastMCP server. We’ll register a sample server as a protected resource, configure a local mcp runtime, and use Scalekit as the Authentication server. We will able confirm that every request from MCP Client carries the right scopes and validate them on the MCP Server. The full code can be found at GitHub.
Before you start
Section titled “Before you start”- A Scalekit workspace (https://app.scalekit.com).
- Python or node installed
- MCP Inspector installed locally to test the connection.
Create your MCP server entry
Section titled “Create your MCP server entry”- Navigate to Dashboard -> MCP Servers and click Add MCP Server.
- Give your server a friendly name (for example,
Greeting MCP). - Set the resource identifier to the URL your server will use (for local tests this is
http://localhost:3002/).

When you save, Scalekit shows the OAuth-protected resource metadata. Copy the endpoint and metadata json as is required by for implementations below.

Build your MCP server
Section titled “Build your MCP server”Follow these steps to assemble a Google-authenticated MCP server from scratch.
mkdir google-mcp-pythoncd google-mcp-pythonpython3 -m venv .venvsource .venv/bin/activateCreate requirements.txt:
cat <<'REQ' > requirements.txtmcp>=1.0.0fastapi>=0.104.0fastmcp>=0.8.0uvicorn>=0.24.0pydantic>=2.5.0python-dotenv>=1.0.0httpx>=0.25.0python-jose[cryptography]>=3.3.0cryptography>=41.0.0scalekit-sdk-python>=2.4.0starlette>=0.27.0REQpip install -r requirements.txtCreate .env with the values from your Scalekit environment (replace the placeholders). You can find these values in Dashboard -> Settings and Dashboard -> MCP Servers -> Metadata JSON
cat <<'ENV' > .envPORT=3002SK_ENV_URL=https://<your-env>.scalekit.comSK_CLIENT_ID=<your-client-id>SK_CLIENT_SECRET=<your-client-secret>MCP_SERVER_ID=<mcp-server-id-from-dashboard>PROTECTED_RESOURCE_METADATA='<resource-metadata-json>'EXPECTED_AUDIENCE=http://localhost:3002/ENVopen .envCreate main.py and paste the complete server implementation:
import jsonimport osfrom fastapi import FastAPI, Request, Responsefrom fastmcp import FastMCP, Contextfrom scalekit import ScalekitClientfrom scalekit.common.scalekit import TokenValidationOptionsfrom starlette.middleware.cors import CORSMiddlewarefrom dotenv import load_dotenv
load_dotenv()
PORT = int(os.getenv("PORT", "3002"))SK_ENV_URL = os.getenv("SK_ENV_URL", "")SK_CLIENT_ID = os.getenv("SK_CLIENT_ID", "")SK_CLIENT_SECRET = os.getenv("SK_CLIENT_SECRET", "")EXPECTED_AUDIENCE = os.getenv("EXPECTED_AUDIENCE", "")PROTECTED_RESOURCE_METADATA = os.getenv("PROTECTED_RESOURCE_METADATA", "")
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,)
mcp = FastMCP("Greeting MCP", stateless_http=True)
@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!"}]}
mcp_app = mcp.http_app(path="/")app = FastAPI(lifespan=mcp_app.lifespan)
app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["GET", "POST", "OPTIONS"], allow_headers=["*"])
@app.middleware("http")async def auth_middleware(request: Request, call_next): if request.url.path in {"/health", "/.well-known/oauth-protected-resource"}: 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\"}", 401, WWW_HEADER, media_type="application/json")
token = auth_header.split("Bearer ", 1)[1].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\"}", 401, WWW_HEADER, media_type="application/json")
return await call_next(request)
@app.get("/.well-known/oauth-protected-resource")async def oauth_metadata(): if not PROTECTED_RESOURCE_METADATA: return Response("{\"error\": \"PROTECTED_RESOURCE_METADATA config missing\"}", 500, media_type="application/json") metadata = json.loads(PROTECTED_RESOURCE_METADATA) return Response(json.dumps(metadata, indent=2), media_type="application/json")
@app.get("/health")async def health_check(): return {"status": "healthy"}
app.mount("/", mcp_app)
if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=PORT)Start the server:
python main.pyThe MCP endpoint now runs at http://localhost:3002/ and accepts only Google-authenticated requests issued by Scalekit.
Connect with MCP Inspector
Section titled “Connect with MCP Inspector”Launch MCP Inspector
npx @modelcontextprotocol/inspector@latestProvide your MCP Server URL: http://localhost:3002/
