> **Building with AI coding agents?** If you're using an AI coding agent, install the official Scalekit plugin. It gives your agent full awareness of the Scalekit API — reducing hallucinations and enabling faster, more accurate code generation.
>
> - **Claude Code**: `/plugin marketplace add scalekit-inc/claude-code-authstack` then `/plugin install <auth-type>@scalekit-auth-stack`
> - **GitHub Copilot CLI**: `copilot plugin marketplace add scalekit-inc/github-copilot-authstack` then `copilot plugin install <auth-type>@scalekit-auth-stack`
> - **Codex**: run the bash installer, restart, then open Plugin Directory and enable `<auth-type>`
> - **Skills CLI** (Windsurf, Cline, 40+ agents): `npx skills add scalekit-inc/skills --list` then `--skill <skill-name>`
>
> `<auth-type>` / `<skill-name>`: `agent-auth`, `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` — [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# FastMCP quickstart

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 <a href="https://github.com/scalekit-inc/mcp-demo/tree/main/todo-fastmcp" target="_blank" rel="noreferrer">GitHub</a>.

**Prerequisites**

- A [Scalekit account](https://app.scalekit.com) with permission to manage MCP servers
- **Python 3.11+** installed locally
- Familiarity with OAuth scopes and basic terminal commands

<details>
<summary><IconTdesignSequence style="display: inline; width: 1rem; height: 1rem; vertical-align: middle; margin-right: 0.5rem;" /> Review the FastMCP authorization flow</summary>

```d2 pad=36
title: "FastMCP with Scalekit" {
  near: top-center
  shape: text
  style.font-size: 18
}

shape: sequence_diagram

MCP Client -> MCP Server: Initiate connection
MCP Server -> MCP Client: 401 + WWW-Authenticate header
MCP Client -> Scalekit Authorization Server: Exchange code for access token
Scalekit Authorization Server -> MCP Client: Issue token with required scopes
MCP Client -> MCP Server: Call tool with Bearer token
MCP Server -> MCP Client: Authorized todo response
```

</details>

1. ## Register your MCP server in Scalekit

   Create a protected resource entry so Scalekit can issue scoped tokens that FastMCP validates on every request.

1. Navigate to **[Dashboard](https://app.scalekit.com) > 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). This field is a required. <br/>
   For a server running at `http://localhost:3002/mcp`, register `http://localhost:3002/`. FastMCP appends <code>/mcp</code> automatically, so always provide the base URL with a trailing slash.
4. Create or link the scopes below, then click **Save**.

   ![Register FastMCP server](@/assets/docs/guides/mcp/register-fastmcp.png)

   | Scope        | Description                                   | Required |
   | ------------ | --------------------------------------------- | -------- |
   | `todo:read`  | Grants read access to todo tasks              | Yes      |
   | `todo:write` | Allows creating, updating, or deleting tasks  | Yes      |

2. ## Create your FastMCP todo server

   Prepare a fresh directory and virtual environment to keep FastMCP dependencies isolated.

   ```bash title="Terminal" frame="terminal"
   mkdir -p fastmcp-todo
   cd fastmcp-todo
   python3 -m venv venv
   source venv/bin/activate
   ```

3. ## Add dependencies and configuration templates

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

   ```bash title="Terminal" frame="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
   ```
**Check in templates, not secrets:** Keep <code>env.example</code> under version control so teammates know which variables to supply, but never commit the populated <code>.env</code> file.

4. ## Implement the FastMCP todo server

   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.

   ```python title="server.py" wrap collapse={1-15} {22-27} {"Scalekit provider setup": 22-27} {"Scope validation": 169-173}
   """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. ## Provide runtime secrets

   Copy the environment template and populate the values from your Scalekit dashboard.

   ```bash title="Terminal" frame="terminal"
   cp env.example .env
   open .env
   ```

   | Variable                      | Description                                                                               |
   | ----------------------------- | ----------------------------------------------------------------------------------------- |
   | `SCALEKIT_ENVIRONMENT_URL`    | Your Scalekit environment URL from **Dashboard > Settings**                               |
   | `SCALEKIT_CLIENT_ID`          | Client ID from **Dashboard > Settings**                                                   |
   | `SCALEKIT_RESOURCE_ID`        | The resource identifier assigned to your MCP server (starts with `res_`)                  |
   | `MCP_URL`                     | The base public URL you registered (keep trailing slash, e.g., `http://localhost:3002/`) |
   | `PORT`                        | Local port for FastMCP HTTP transport (defaults to `3002`)                                |
**Store secrets securely:** Avoid committing <code>.env</code> to source control. Use your team's secret manager in production and rotate credentials if they appear in logs or terminal history.

6. ## Run the FastMCP server locally

   Start the server so it can accept authenticated MCP requests at `/mcp`.

   ```bash title="Terminal" frame="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](@/assets/docs/guides/mcp/venv-activate-fastmcp.png)
**Token enforcement:** Every tool in <code>server.py</code> calls <code>_require_scope</code>. If you see <code>Insufficient permissions</code> in responses, verify the caller's token includes the expected scope.

7. ## Connect with an MCP client

   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.

   ```bash title="Terminal" frame="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](@/assets/docs/guides/mcp/mcp-inspector-fastmcp.png)
**Note:** Leave the Inspector's Authentication fields empty. This quickstart uses dynamic client registration (DCR)
**Testing scope enforcement:** Try calling <code>create_todo</code> with a token that only has <code>todo:read</code>. The server will reject the request with an insufficient permissions error.

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.

---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
