agentsclimarketplace

Mcp oauth fastmcp

Skill scalekit-inc/skills/skills/mcp-oauth-fastmcp

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.

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

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.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

12.0 KB, as published. Nobody here has run it

FastMCP OAuth with Scalekit Provider

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.

FastMCP advantage

Standard MCP OAuth: ~30 lines of middleware code, manual token validation FastMCP with Scalekit provider: ~5 lines of configuration, automatic token handling

Setup workflow

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

Step 1: Register MCP server

In Scalekit dashboard:

  1. Navigate to Dashboard > MCP Servers > Add MCP Server
  2. Enter server name (e.g., FastMCP Todo Server)
  3. Set Server URL to http://localhost:3002/ (include trailing slash)
  4. Define scopes for your tools (e.g., todo:read, todo:write)
  5. Click Save and note the resource_id

Critical: Use base URL with trailing slash. FastMCP appends /mcp automatically.

  • ✓ Correct: http://localhost:3002/
  • ✗ Wrong: http://localhost:3002/mcp

Step 2: Install dependencies

Create 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

Step 3: Configure Scalekit provider

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:

  • Token validation on every request
  • OAuth flow with MCP clients
  • WWW-Authenticate header responses
  • Discovery endpoint (/.well-known/oauth-protected-resource)

Step 4: Add scope validation to tools

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.

Step 5: Test with MCP Inspector

Run your server:

source venv/bin/activate
python server.py

Launch MCP Inspector:

npx @modelcontextprotocol/inspector@latest

In Inspector:

  1. Enter URL: http://localhost:3002/mcp
  2. Leave authentication fields empty (uses dynamic client registration)
  3. Click Connect
  4. Complete OAuth flow when prompted
  5. Test tools with scoped tokens

Testing scope enforcement:

  • Call create_todo with token that only has todo:read → should fail
  • Call list_todos with todo:read scope → should succeed

Complete example: Todo server

import 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")))

Environment variable reference

VariableDescriptionExample
SCALEKIT_ENVIRONMENT_URLYour Scalekit environment URLhttps://yourenv.scalekit.com
SCALEKIT_CLIENT_IDClient ID from Scalekit dashboardskc_...
SCALEKIT_RESOURCE_IDMCP server resource IDres_...
MCP_URLBase URL with trailing slashhttp://localhost:3002/
PORTHTTP server port3002

Scope design patterns

Read-only operations: Use *:read scope

  • todo:read, data:read, user:read

Write operations: Use *:write scope

  • todo:write, data:write, user:write

Admin operations: Use *:admin scope

  • system:admin, user:admin

Multiple 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

Production deployment

Security checklist

  • Store .env in secret manager (AWS Secrets Manager, Vault)
  • Use HTTPS for all public endpoints
  • Rotate SCALEKIT_CLIENT_SECRET regularly
  • Enable rate limiting on MCP endpoints
  • Log all authentication failures
  • Monitor token validation errors

Environment-specific configuration

Development:

MCP_URL=http://localhost:3002/

Production:

MCP_URL=https://mcp.yourapp.com/

Update Scalekit dashboard with production URL before deploying.

Common issues

Token validation fails:

  • Verify SCALEKIT_RESOURCE_ID matches dashboard
  • Check MCP_URL has trailing slash
  • Ensure environment variables are loaded (load_dotenv())

Discovery endpoint not found:

  • Confirm server is running on correct port
  • Verify MCP client uses base URL + /mcp path
  • Check firewall/network allows connections

Scope errors persist:

  • Verify scopes are defined in Scalekit dashboard
  • Check scope strings match exactly (case-sensitive)
  • Ensure token was issued with required scopes

MCP Inspector connection fails:

  • Leave auth fields empty (uses DCR)
  • Check browser console for OAuth errors
  • Verify server logs show authentication attempt

Extending your server

Adding new tools with scopes

@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"}

Multiple scope requirements

@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"}

Optional scope enhancement

@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

Complete Working Example

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:

  • Full CRUD operations with scope-based authorization
  • In-memory todo storage for testing
  • OAuth 2.1 integration via FastMCP ScalekitProvider
  • Production-ready error handling and logging

Resources

Keep looking

Skills are one crate of 328,083. 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.