Skip to content

FastMCP quickstart

FastMCP todo server with OAuth scope validation and CRUD operations.

This guide shows you how to build a production-ready FastMCP server protected by Scalekit’s OAuth authentication. You’ll register your server as a protected resource, implement scope-based authorization for CRUD operations, and validate tokens on every request.

Use this quickstart to experience a working reference implementation with a simple todo application. The todo app demonstrates how to enforce todo:read and todo:write scopes across multiple tools. After completing this guide, you can apply the same authentication pattern to secure your own FastMCP tools. The full code is available on GitHub.

Prerequisites

  • A Scalekit account with permission to manage MCP servers
  • Python 3.11+ installed locally
  • Familiarity with OAuth scopes and basic terminal commands
Review the FastMCP authorization flow FastMCP with ScalekitMCP ClientMCP ServerScalekit Authorization Server Initiate connection 401 + WWW-Authenticate header Exchange code for access token Issue token with required scopes Call tool with Bearer token Authorized todo response
  1. Create a protected resource entry so Scalekit can issue scoped tokens that FastMCP validates on every request.

    1. Navigate to Dashboard > MCP Servers > Add MCP Server.
    2. Enter a descriptive name (for example, FastMCP Todo Server).
    3. Set Server URL to http://localhost:3002/ (keep the trailing slash).
    4. Create or link the scopes below, then click Save.

    Register FastMCP server

    ScopeDescriptionRequired
    todo:readGrants read access to todo tasksYes
    todo:writeAllows creating, updating, or deleting tasksYes
  2. Prepare a fresh directory and virtual environment to keep FastMCP dependencies isolated.

    Terminal
    mkdir -p fastmcp-todo
    cd fastmcp-todo
    python3 -m venv venv
    source venv/bin/activate
  3. Add dependencies and configuration templates

    Section titled “Add dependencies and configuration templates”

    Create the support files that FastMCP and Scalekit expect, then install the required libraries.

    Terminal
    cat <<'EOF' > requirements.txt
    fastmcp>=2.13.0.2
    python-dotenv>=1.0.0
    EOF
    pip install -r requirements.txt
    cat <<'EOF' > env.example
    PORT=3002
    SCALEKIT_ENVIRONMENT_URL=https://your-environment-url.scalekit.com
    SCALEKIT_CLIENT_ID=your_client_id
    SCALEKIT_RESOURCE_ID=mcp_server_id
    MCP_URL=http://localhost:3002/
    EOF
  4. Copy the following code into server.py. It registers the Scalekit provider, defines an in-memory todo store, and exposes CRUD tools guarded by OAuth scopes.

    server.py
    15 collapsed lines
    """Scalekit-authenticated FastMCP server providing in-memory CRUD tools for todos.
    This example demonstrates how to protect FastMCP tools with OAuth scopes.
    Each tool validates the required scope before executing operations.
    """
    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()
    # Use case: Configure FastMCP server with OAuth protection
    # Security: Scalekit provider validates every request's Bearer token
    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"),
    # FastMCP appends /mcp automatically; keep base URL with trailing slash only
    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)
    # Use case: In-memory storage for demo purposes
    # Production: Replace with your database or persistent storage
    _TODO_STORE: dict[str, TodoItem] = {}
    def _require_scope(scope: str) -> Optional[str]:
    """
    Security: Validate that the current request's token includes the required scope.
    This prevents unauthorized access to protected operations.
    """
    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:
    """
    Use case: Create a new todo item for task tracking
    Requires: todo:write scope
    """
    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:
    """
    Use case: Retrieve all todos, optionally filtered by completion status
    Requires: todo:read scope
    """
    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:
    """
    Use case: Retrieve a specific todo by ID
    Requires: todo:read scope
    """
    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:
    """
    Use case: Update existing todo properties or mark as complete
    Requires: todo:write scope
    """
    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:
    """
    Use case: Remove a todo from the system
    Requires: todo:write scope
    """
    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__":
    # Start HTTP transport server
    mcp.run(transport="http", port=int(os.getenv("PORT", "3002")))
  5. Copy the environment template and populate the values from your Scalekit dashboard.

    Terminal
    cp env.example .env
    open .env
    VariableDescription
    SCALEKIT_ENVIRONMENT_URLYour Scalekit environment URL from Dashboard > Settings
    SCALEKIT_CLIENT_IDClient ID from Dashboard > Settings
    SCALEKIT_RESOURCE_IDThe resource identifier assigned to your MCP server (starts with res_)
    MCP_URLThe base public URL you registered (keep trailing slash, e.g., http://localhost:3002/)
    PORTLocal port for FastMCP HTTP transport (defaults to 3002)
  6. Start the server so it can accept authenticated MCP requests at /mcp.

    Terminal
    source venv/bin/activate
    python server.py

    When the server boots successfully, you’ll see FastMCP announce the HTTP transport and listen on http://localhost:3002/, ready to enforce Scalekit-issued tokens.

    Run MCP server

  7. Use any MCP-compatible client to exercise the todo tools with scoped tokens. During development, the MCP Inspector demonstrates how the Scalekit provider enforces scopes end-to-end.

    Terminal
    npx @modelcontextprotocol/inspector@latest

    In the Inspector UI, point the client to http://localhost:3002/mcp and click Connect. The client initiates OAuth authentication with Scalekit. After successful authentication, run any tool—the server exposes create_todo, list_todos, get_todo, update_todo, and delete_todo.

    MCP Inspector

Once you’re satisfied with the quickstart example, extend server.py with your own FastMCP tools or replace the in-memory store with your production data source. Scalekit’s provider handles authentication for any toolset you add.