skills/mcp-oauth-fastmcp/SKILL.md
Add OAuth 2.1 authorization to FastMCP servers using Scalekit provider plugin. Use when building FastMCP servers, when users mention FastMCP authentication, Python MCP servers with Scalekit, or need rapid OAuth integration with minimal code.
npx skillsauth add scalekit-inc/skills mcp-oauth-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.
Secure your FastMCP server with OAuth 2.1 in just 5 lines of code using Scalekit's built-in provider. This approach handles token validation, scope enforcement, and authentication flows automatically.
Standard MCP OAuth: ~30 lines of middleware code, manual token validation FastMCP with Scalekit provider: ~5 lines of configuration, automatic token handling
Copy this checklist and track progress:
FastMCP OAuth Setup:
- [ ] Step 1: Register MCP server in Scalekit
- [ ] Step 2: Install FastMCP and dependencies
- [ ] Step 3: Configure Scalekit provider
- [ ] Step 4: Add scope validation to tools
- [ ] Step 5: Test with MCP Inspector
In Scalekit dashboard:
FastMCP Todo Server)http://localhost:3002/ (include trailing slash)todo:read, todo:write)resource_idCritical: Use base URL with trailing slash. FastMCP appends /mcp automatically.
http://localhost:3002/http://localhost:3002/mcpCreate project structure:
mkdir fastmcp-server
cd fastmcp-server
python3 -m venv venv
source venv/bin/activate
Create requirements.txt:
fastmcp>=2.13.0.2
python-dotenv>=1.0.0
Install:
pip install -r requirements.txt
Create .env file with Scalekit credentials:
PORT=3002
SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com
SCALEKIT_CLIENT_ID=your_client_id
SCALEKIT_RESOURCE_ID=res_your_resource_id
MCP_URL=http://localhost:3002/
Get these values from Scalekit Dashboard > Settings and your MCP server configuration.
Initialize FastMCP server with Scalekit provider (server.py):
import os
from dotenv import load_dotenv
from fastmcp import FastMCP
from fastmcp.server.auth.providers.scalekit import ScalekitProvider
load_dotenv()
# 5-line OAuth setup
mcp = FastMCP(
"Your Server Name",
stateless_http=True,
auth=ScalekitProvider(
environment_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
resource_id=os.getenv("SCALEKIT_RESOURCE_ID"),
mcp_url=os.getenv("MCP_URL"),
),
)
if __name__ == "__main__":
mcp.run(transport="http", port=int(os.getenv("PORT", "3002")))
That's it! The Scalekit provider handles:
/.well-known/oauth-protected-resource)Use the built-in get_access_token() dependency to validate scopes:
from fastmcp.server.dependencies import AccessToken, get_access_token
def _require_scope(scope: str) -> str | None:
"""Validate request token has required scope."""
token: AccessToken = get_access_token()
if scope not in token.scopes:
return f"Insufficient permissions: `{scope}` scope required."
return None
@mcp.tool
def create_todo(title: str, description: str = None) -> dict:
"""Create a new todo item. Requires todo:write scope."""
error = _require_scope("todo:write")
if error:
return {"error": error}
# Your tool implementation
todo_id = str(uuid.uuid4())
return {"id": todo_id, "title": title, "description": description}
@mcp.tool
def list_todos() -> dict:
"""List all todos. Requires todo:read scope."""
error = _require_scope("todo:read")
if error:
return {"error": error}
# Your tool implementation
return {"todos": [...]}
Pattern: Every tool that requires authorization should call _require_scope() first.
Run your server:
source venv/bin/activate
python server.py
Launch MCP Inspector:
npx @modelcontextprotocol/inspector@latest
In Inspector:
http://localhost:3002/mcpTesting scope enforcement:
create_todo with token that only has todo:read → should faillist_todos with todo:read scope → should succeedimport os
import uuid
from dataclasses import dataclass, asdict
from typing import Optional
from dotenv import load_dotenv
from fastmcp import FastMCP
from fastmcp.server.auth.providers.scalekit import ScalekitProvider
from fastmcp.server.dependencies import AccessToken, get_access_token
load_dotenv()
mcp = FastMCP(
"Todo Server",
stateless_http=True,
auth=ScalekitProvider(
environment_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
resource_id=os.getenv("SCALEKIT_RESOURCE_ID"),
mcp_url=os.getenv("MCP_URL"),
),
)
@dataclass
class TodoItem:
id: str
title: str
description: Optional[str]
completed: bool = False
def to_dict(self) -> dict:
return asdict(self)
_TODO_STORE: dict[str, TodoItem] = {}
def _require_scope(scope: str) -> Optional[str]:
token: AccessToken = get_access_token()
if scope not in token.scopes:
return f"Insufficient permissions: `{scope}` scope required."
return None
@mcp.tool
def create_todo(title: str, description: Optional[str] = None) -> dict:
error = _require_scope("todo:write")
if error:
return {"error": error}
todo = TodoItem(id=str(uuid.uuid4()), title=title, description=description)
_TODO_STORE[todo.id] = todo
return {"todo": todo.to_dict()}
@mcp.tool
def list_todos(completed: Optional[bool] = None) -> dict:
error = _require_scope("todo:read")
if error:
return {"error": error}
todos = [
todo.to_dict()
for todo in _TODO_STORE.values()
if completed is None or todo.completed == completed
]
return {"todos": todos}
@mcp.tool
def get_todo(todo_id: str) -> dict:
error = _require_scope("todo:read")
if error:
return {"error": error}
todo = _TODO_STORE.get(todo_id)
if todo is None:
return {"error": f"Todo `{todo_id}` not found."}
return {"todo": todo.to_dict()}
@mcp.tool
def update_todo(
todo_id: str,
title: Optional[str] = None,
description: Optional[str] = None,
completed: Optional[bool] = None,
) -> dict:
error = _require_scope("todo:write")
if error:
return {"error": error}
todo = _TODO_STORE.get(todo_id)
if todo is None:
return {"error": f"Todo `{todo_id}` not found."}
if title is not None:
todo.title = title
if description is not None:
todo.description = description
if completed is not None:
todo.completed = completed
return {"todo": todo.to_dict()}
@mcp.tool
def delete_todo(todo_id: str) -> dict:
error = _require_scope("todo:write")
if error:
return {"error": error}
todo = _TODO_STORE.pop(todo_id, None)
if todo is None:
return {"error": f"Todo `{todo_id}` not found."}
return {"deleted": todo_id}
if __name__ == "__main__":
mcp.run(transport="http", port=int(os.getenv("PORT", "3002")))
| Variable | Description | Example |
|----------|-------------|---------|
| SCALEKIT_ENVIRONMENT_URL | Your Scalekit environment URL | https://yourenv.scalekit.com |
| SCALEKIT_CLIENT_ID | Client ID from Scalekit dashboard | skc_... |
| SCALEKIT_RESOURCE_ID | MCP server resource ID | res_... |
| MCP_URL | Base URL with trailing slash | http://localhost:3002/ |
| PORT | HTTP server port | 3002 |
Read-only operations: Use *:read scope
todo:read, data:read, user:readWrite operations: Use *:write scope
todo:write, data:write, user:writeAdmin operations: Use *:admin scope
system:admin, user:adminMultiple scopes per tool: Return error if ANY required scope is missing
def _require_scopes(scopes: list[str]) -> str | None:
token: AccessToken = get_access_token()
missing = [s for s in scopes if s not in token.scopes]
if missing:
return f"Missing scopes: {', '.join(missing)}"
return None
@mcp.tool
def admin_action() -> dict:
error = _require_scopes(["todo:write", "admin:access"])
if error:
return {"error": error}
# Implementation
.env in secret manager (AWS Secrets Manager, Vault)SCALEKIT_CLIENT_SECRET regularlyDevelopment:
MCP_URL=http://localhost:3002/
Production:
MCP_URL=https://mcp.yourapp.com/
Update Scalekit dashboard with production URL before deploying.
Token validation fails:
SCALEKIT_RESOURCE_ID matches dashboardMCP_URL has trailing slashload_dotenv())Discovery endpoint not found:
/mcp pathScope errors persist:
MCP Inspector connection fails:
@mcp.tool
def new_operation(param: str) -> dict:
"""Your tool description."""
error = _require_scope("your:scope")
if error:
return {"error": error}
# Your implementation
return {"result": "success"}
@mcp.tool
def sensitive_operation() -> dict:
"""Requires multiple scopes."""
error = _require_scopes(["data:read", "data:write", "admin:access"])
if error:
return {"error": error}
# Your implementation
return {"result": "success"}
@mcp.tool
def flexible_operation() -> dict:
"""Returns different data based on scopes."""
token: AccessToken = get_access_token()
# Basic data for all authenticated users
result = {"basic": "data"}
# Enhanced data if user has admin scope
if "admin:access" in token.scopes:
result["admin"] = "enhanced_data"
return result
The complete FastMCP todo server shown above is available in the Scalekit MCP Auth Demos repository:
GitHub Repository: scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp
This example demonstrates:
npx @modelcontextprotocol/inspector@latesttools
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.