> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcpmanager.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Build with FastMCP (Python)

> How to build a FastMCP (Python) MCP server that runs behind MCP Manager: serving Streamable HTTP rather than the unsupported legacy SSE transport, choosing among FastMCP's TokenVerifier, RemoteAuthProvider, OAuthProxy, OIDCProxy and OAuthProvider to match one of MCP Manager's three auth modes, the Cloud Run client-storage gotcha (the in-memory default loses dynamically-registered clients), and a compatibility checklist with the framework-specific gotchas.

[FastMCP](https://gofastmcp.com) is the most widely used Python framework for building MCP servers, and its high-level `FastMCP` class also ships inside the official [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk). It can be a full OAuth authorization server *and* serve Streamable HTTP, so it can satisfy any of **MCP Manager**'s three authentication modes. This page covers the decisions that matter for running it behind MCP Manager and the gotchas that bite on cloud hosts — it is not a substitute for [FastMCP's own documentation](https://gofastmcp.com), which is authoritative and moves quickly.

<Note>
  New to the requirements? Read [Building Your Own MCP Server](/build-your-own-mcp-server/overview) first — it covers the two things every server must
  do and how to choose an auth mode. This page is the FastMCP-specific layer on top.
</Note>

## Serve Streamable HTTP — never SSE-only

Run FastMCP over its HTTP transport, which serves Streamable HTTP. The default path is **`/mcp/`** with a **trailing slash** — point MCP Manager at exactly that. For any multi-instance host (Cloud Run, Kubernetes), run the ASGI app in stateless mode so a request isn't tied to one process's memory. See [FastMCP — Running the server over HTTP](https://gofastmcp.com/deployment/http).

```python Illustrative — see gofastmcp.com/deployment/http theme={null}
from fastmcp import FastMCP

mcp = FastMCP('My Server')

# Streamable HTTP at /mcp/ (note the trailing slash).
# stateless_http=True is what you want behind a load balancer.
app = mcp.http_app(stateless_http=True)
```

<Warning>
  Do **not** start FastMCP with `transport='sse'`. That serves the legacy two-endpoint HTTP+SSE transport, which **MCP Manager does not connect to**
  — it speaks Streamable HTTP only and will mark an SSE-only server unsupported. (Streamable HTTP may still *answer* with a `text/event-stream` body;
  that's fine and supported.)
</Warning>

## Match FastMCP's auth to an MCP Manager mode

FastMCP offers a ladder of auth providers (all passed as `auth=` on `FastMCP(...)`). The decision is which one matches the [MCP Manager mode](/build-your-own-mcp-server/overview#choosing-an-authentication-mode) you want.

| You want                                 | Use in FastMCP                                                                                                                              | Notes                                                                                                              |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Standard OAuth + DCR** (most seamless) | `OAuthProvider` (full authorization server), or `OAuthProxy` / `OIDCProxy` to front an upstream IdP while still exposing DCR to MCP Manager | Publishes the well-known metadata and a `/register` endpoint, so MCP Manager self-registers and users just approve |
| **OAuth pre-registration**               | `OAuthProxy` / `OIDCProxy` configured with your fixed upstream app credentials                                                              | You give MCP Manager a Client ID + Secret instead of relying on DCR                                                |
| **Token in a header**                    | `TokenVerifier` (e.g. `JWTVerifier`), or a custom token check                                                                               | Simplest; no metadata or DCR. Maps to MCP Manager's header-token mode                                              |

A bare `TokenVerifier` is **not** enough for the DCR mode — it validates tokens but exposes no `/register` endpoint and no authorization-server metadata, so MCP Manager can't auto-register against it. Choose a provider that publishes metadata if you want the seamless path. See [FastMCP — Authentication](https://gofastmcp.com/servers/auth/authentication), [OAuth Proxy](https://gofastmcp.com/servers/auth/oauth-proxy), and [OIDC Proxy](https://gofastmcp.com/servers/auth/oidc-proxy).

<Tip>
  FastMCP's `OAuthProxy` / `OIDCProxy` are the sweet spot for most teams: you keep an upstream IdP (Google Workspace, Auth0, WorkOS) that doesn't
  itself do DCR, and FastMCP presents a clean DCR-capable face to MCP Manager. That's exactly the shape behind the most common deployment.
</Tip>

## The Cloud Run gotcha you must get right

If you use a FastMCP proxy provider (`OAuthProxy` / `OIDCProxy`) and deploy on a multi-instance host, this is the failure that bites: FastMCP's registered-client store defaults to an **in-memory store on Linux**, and its default encryption key is **ephemeral**. MCP Manager registers a client against one instance; the browser authorize request hits another instance that has no record of it, and your server renders a "client not registered" error. It looks intermittent but is structural.

The fix has **two parts** — getting only the first is a common near-miss:

1. Point `client_storage` at a **shared, network-accessible** backend (Redis on a managed cache, or another durable store) so registrations are visible to every instance. See the [`client_storage` parameter reference](https://gofastmcp.com/servers/auth/oidc-proxy#param-client-storage).
2. Use a **stable encryption key** shared across instances (for example a Fernet key from your secret manager) and a stable `jwt_signing_key`, so a second instance can decrypt what the first one wrote.

```python Illustrative — see gofastmcp.com/servers/auth/oidc-proxy theme={null}
auth = OIDCProxy(
    # ... your upstream OIDC config + public base_url ...
    client_storage=shared_backed_store,   # Redis/etc. — NOT the in-memory default
)
```

<Warning>
  `min-instances` or session affinity won't reliably fix this — the registration and authorize requests come from different origins and can't be
  pinned together. Shared storage plus a stable key is the real fix. The full diagnosis is in [Debug Self-Hosted
  OAuth](/build-your-own-mcp-server/debugging-self-hosted-oauth).
</Warning>

<Note>
  Deploying the fix doesn't reconnect a server you already added. MCP Manager reuses the `client_id` and `client_secret` it first registered, so once
  shared storage is live, **delete and re-add the server** to force a fresh registration against the repaired deployment.
</Note>

## MCP Manager compatibility checklist

<Steps>
  <Step title="Streamable HTTP, at the exact path">
    Serve over the HTTP transport (`mcp.http_app(...)`), point MCP Manager at the real path including the trailing slash (`/mcp/`), and never ship
    `transport='sse'` only.
  </Step>

  <Step title="Run stateless on multi-instance hosts">
    Use `stateless_http=True` so a session isn't bound to one process's memory behind a load balancer.
  </Step>

  <Step title="Pick a provider that publishes metadata (for DCR)">
    For the seamless OAuth path, use `OAuthProvider`, `OAuthProxy`, or `OIDCProxy` — not a bare `TokenVerifier` — so the well-known metadata and
    `/register` exist.
  </Step>

  <Step title="Shared client storage + stable keys">
    Set `client_storage` to a shared backend and use stable encryption / `jwt_signing_key` values across instances.
  </Step>

  <Step title="Public base_url and allowed redirect">
    Set the proxy's `base_url` to your public HTTPS URL (not `localhost`), and allow MCP Manager's callback
    `https://app.mcpmanager.ai/api/v1/mcpm/inbound/oauth/callback` (FastMCP's `allowed_client_redirect_uris`).
  </Step>
</Steps>

## FastMCP gotchas

<AccordionGroup>
  <Accordion title="The /mcp/ trailing slash" icon="slash-forward">
    FastMCP's HTTP transport serves at `/mcp/` by default. If MCP Manager (or any client) calls `/mcp` without the slash and your stack 301-redirects, the redirect can drop the `Authorization` header. Point MCP Manager at the exact path, or configure the path explicitly.
  </Accordion>

  <Accordion title="In-memory client store on Linux" icon="database">
    The proxy providers default to an in-memory client store on Linux and an ephemeral key — fine on one laptop, broken across instances. Configure
    `client_storage` and a stable key before deploying. See [Debug Self-Hosted OAuth](/build-your-own-mcp-server/debugging-self-hosted-oauth).
  </Accordion>

  <Accordion title="localhost leaking into metadata" icon="globe">
    If `base_url` is left as `http://localhost:...`, the published well-known metadata advertises endpoints MCP Manager can't reach. Set `base_url` to your public HTTPS URL so `issuer`, `authorization_endpoint`, `token_endpoint`, and `registration_endpoint` are all reachable.
  </Accordion>
</AccordionGroup>

## Further reading

<CardGroup cols={2}>
  <Card title="Debug Self-Hosted OAuth" icon="bug" href="/build-your-own-mcp-server/debugging-self-hosted-oauth">
    The deep dive on the dynamic-client-registration failure FastMCP hits on Cloud Run.
  </Card>

  <Card title="FastMCP — Authentication" icon="key" href="https://gofastmcp.com/servers/auth/authentication">
    The authoritative reference for FastMCP's auth providers.
  </Card>

  <Card title="FastMCP — OIDC Proxy" icon="bolt" href="https://gofastmcp.com/servers/auth/oidc-proxy">
    Fronting an upstream OIDC provider while exposing DCR, including client storage.
  </Card>

  <Card title="Building Your Own MCP Server" icon="hammer" href="/build-your-own-mcp-server/overview">
    The cross-framework requirements, decision tree, and troubleshooting catalog.
  </Card>
</CardGroup>
