Archive v3 docs and publish v4 as the primary version (#4613)

* Archive v3 docs under /v3 and publish v4 as the primary version

* Label primary docs version v4.0.0 (alpha 1)

* Add What's New in v4 page; fix upgrade-guide phrasing; point banner at What's New

* Rewrite What's New around v4's new capabilities, not the sampling deprecation

* Lead What's New with the SDK v2 engine swap and the SEPs it brings

* State ships now (link Session State); tasks arrive next alpha

* Exclude docs/v3 frozen snapshots from doc-example import validation
This commit is contained in:
Jeremiah Lowin 2026-07-23 19:47:57 -04:00 committed by GitHub
commit c556f07a66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
186 changed files with 39927 additions and 8 deletions

View file

@ -57,6 +57,42 @@ h6 code:not(pre code) {
background: linear-gradient(135deg, #2d00f7 0%, #4cc9f0 100%);
}
/* V3 banner - inside content-container, breaks out of padding with negative margins */
#v3-banner {
display: block;
background: linear-gradient(135deg, #4cc9f0 0%, #2d00f7 100%);
color: white;
text-align: center;
padding: 10px 16px;
font-size: 0.875rem;
font-weight: 600;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin: -2rem -2rem 1.5rem -2rem;
width: calc(100% + 4rem);
border-radius: 8px 8px 0 0;
}
#v3-banner a {
color: white;
text-decoration: underline;
font-weight: 700;
}
#v3-banner a:hover {
opacity: 0.9;
}
@media (min-width: 1024px) {
#v3-banner {
margin: -3rem -4rem 1.5rem -4rem;
width: calc(100% + 8rem);
}
}
.dark #v3-banner {
background: linear-gradient(135deg, #2d00f7 0%, #4cc9f0 100%);
}

View file

@ -16,7 +16,7 @@
"dark": "#475569",
"light": "#1e3a5f"
},
"content": "Meet [Prefect Horizon](https://prefect.io/horizon?utm_source=gofastmcp&utm_medium=docs&utm_campaign=docs_banner&utm_content=sitewide_banner), the enterprise MCP gateway built by the team behind FastMCP"
"content": "FastMCP 4 is in alpha — you're reading the v4 docs. [What's new](/getting-started/whats-new) · [FastMCP 3 docs](/v3/getting-started/welcome)"
},
"colors": {
"dark": "#f72585",
@ -89,7 +89,8 @@
"pages": [
"getting-started/welcome",
"getting-started/installation",
"getting-started/quickstart"
"getting-started/quickstart",
"getting-started/whats-new"
]
},
{
@ -412,7 +413,10 @@
"icon": "code"
}
],
"version": "v3"
"version": "v4.0.0 (alpha 1)"
},
{
"$ref": "./v3-navigation.json"
},
{
"$ref": "./v2-navigation.json"

View file

@ -76,7 +76,7 @@ A few client behaviors that touch the SDK are preserved so you don't have to cha
## What you must change
Three things are on you.
Everything above, FastMCP handled for you. What remains lives in your own code, where FastMCP can't reach it — your imports, how you construct errors, the custom HTTP clients you hand to a transport, and any place you reach past FastMCP's surfaces into the raw SDK objects. Each surfaces as a clear failure at import or call time, and each is a mechanical fix.
**Your own `mcp.types` imports.** FastMCP can re-export types, but it can't rewrite imports in your code. Any `from mcp.types import X` or `import mcp.types` in your server or client fails at import time with:

View file

@ -0,0 +1,71 @@
---
title: "What's New in FastMCP 4"
sidebarTitle: "What's New"
description: The capabilities that define FastMCP 4 — a rebuilt engine, a new protocol era, and a stateless protocol made practical.
icon: sparkles
---
FastMCP 4 is a major version because its engine changed. The framework is now built on the MCP Python SDK v2, a ground-up rebuild of the protocol layer, and on that foundation it adds a new protocol era, first-class extensions, stateless state, enterprise identity, and more. Most FastMCP 3 servers run on it untouched — the major version signals how much moved underneath, and what that movement unlocks.
<Note>
FastMCP 4 is in **alpha**. Everything below is available today except background tasks, which arrive in the next alpha. Pin an exact version and expect sharp edges.
</Note>
## Built on the MCP Python SDK v2
The defining change in FastMCP 4 is the one you mostly can't see. The MCP Python SDK v2 rewrote the protocol layer end to end: it split the protocol types into a standalone `mcp_types` package, renamed every wire field from camelCase to snake_case, replaced the server's request-handling model, and made server-side middleware and multi-era serving first-class. FastMCP absorbs nearly all of it — your reads stay working through a compatibility bridge, and the handful of changes left in your code are mechanical.
The major version is the signal. Even where your surface is unchanged, the behavior underneath is substantially different, and bumping to 4.0 is how we tell you that plainly rather than slipping a new engine in under a patch release.
The rebuild also pulls the protocol's recent evolution forward in a single step. A batch of accepted MCP proposals arrives with SDK v2, and FastMCP 4 surfaces each one: capability-negotiated extensions (SEP-2133), multi-round-trip elicitation for sessionless connections (SEP-2322), response cache hints (SEP-2549), spec-standard error codes (SEP-2164), the enterprise identity-assertion grant (SEP-990), and the sessionless `2026-07-28` protocol itself, which removes server-initiated requests (SEP-2577). The rest of this page is what those add up to.
## Every protocol era
A FastMCP 4 server answers clients across the protocol transition from one deployment. The MCP SDK negotiates the era per connection — the sessionless `2026-07-28` protocol for clients that have moved forward, the session-based handshake for everyone else — and any replica behind a plain load balancer can serve a modern request. This supersedes FastMCP's earlier "latest protocol only" stance: you adopt the new protocol without forking your deployment or gating clients by version.
The modern protocol is sessionless, so it drops the server's ability to call back into the client mid-request (SEP-2577). Imperative `ctx.elicit` and `ctx.list_roots` move to a request-shaped pattern on modern connections, and server-initiated sampling — which has no such replacement — is [deprecated](/servers/sampling). Everything else about writing a server is unchanged.
## State without a session
A stateless protocol raises an obvious question: if every request is a fresh connection, where does a tool keep a shopping cart, a conversation, or a running total? FastMCP 4 follows the MCP working group's own decision to reject protocol-level sessions in favor of *explicit state handles* (SEP-2567) — the server hands out an identifier, and the client passes it back.
Two shapes cover the cases. `UserSession` is injected like `Context` and keyed to the authenticated user, so a tool reads and writes one bucket of state with nothing to pass around. `SessionId` is an explicit handle a tool mints and the caller supplies as an argument, for when one user holds many independent states. Both store their data server-side in the storage backend, keyed to the authenticated user — so a handle is inert in anyone else's hands. See [Session State](/servers/sessions).
## Background tasks
Long-running work runs as a background task: the server accepts the call, returns a handle, and the client polls for the result while the work proceeds. Tasks left the core MCP spec during the SDK v2 rebuild and returned as the `io.modelcontextprotocol/tasks` extension (SEP-2663), which FastMCP 4 implements end to end in the optional `fastmcp-tasks` package. The durable execution engine that made FastMCP 3's tasks reliable carries straight over, and `@mcp.tool(task=True)` remains the only authoring surface — so the wire protocol modernizing underneath costs you no code change. See [Background Tasks](/development/v4-notes/background-tasks) for the design; the runtime arrives in the next alpha.
## Server extensions
Background tasks are the first capability built on a more general one: FastMCP 4 makes MCP extensions — capability-negotiated protocol features named by a reverse-DNS string (SEP-2133) — a first-class surface. `FastMCP.add_extension()` lets an extension advertise a capability, add request methods, intercept `tools/call`, and run a lifespan hook, all with full access to the component registry, `Context`, and auth. The same extensions flow through the client with `Client(extensions=...)`. A cross-cutting protocol feature stops being surgery on core and becomes a supported plugin.
## Enterprise identity
FastMCP 4 ships a complete server-side implementation of identity assertion (SEP-990): enterprise "on-behalf-of" access, where a corporate identity provider issues a signed assertion, the user's agent presents it, and the server mints a short-lived token — no browser login and no per-user consent screen. Behind one parameter on the existing auth providers, FastMCP performs the full signature verification, binding checks, replay rejection, and scoped token issuance.
```python
from fastmcp import FastMCP
from fastmcp.server.auth import IdentityAssertion, OAuthProxy
auth = OAuthProxy(
# existing upstream configuration unchanged
identity_assertion=IdentityAssertion(trusted_issuers=["https://login.acme-corp.com"]),
)
mcp = FastMCP("Internal API", auth=auth)
```
The asserted subject flows into the normal auth context, so tools read it through `get_access_token()` like any other identity. See [Identity Assertion](/servers/auth/oauth-proxy#identity-assertion-sep-990).
## Faster and safer
Two more capabilities arrive by default. Response caching (SEP-2549) lets a server stamp freshness hints on its results that a caching [client](/clients/client#response-caching) reuses without a round trip, and a distributed `KeyValueResponseCacheStore` backs that cache with Redis or any key-value store, so a fleet of clients or proxy replicas shares fills.
```python
from fastmcp import FastMCP
mcp = FastMCP("Weather", cache_ttl=300, cache_scope="public")
```
Security tightened in the same release: every templated resource screens its parameters for path traversal, absolute paths, and null bytes before the handler runs — [path security](/servers/resources#path-security) on by default, covering mounted and proxied templates too.
When you're ready to move a server to v4, [Upgrading from FastMCP 3](/getting-started/upgrading/from-fastmcp-3) walks through every change and what it looks like in practice.

39
docs/v3-banner.js Normal file
View file

@ -0,0 +1,39 @@
// Add v3 banner inside content-container with negative margins
(function() {
if (typeof window === 'undefined') return;
function addBanner() {
const isV3 = window.location.pathname.includes('/v3/');
const container = document.getElementById('content-container');
let banner = document.getElementById('v3-banner');
if (isV3 && container) {
if (!banner) {
banner = document.createElement('div');
banner.id = 'v3-banner';
banner.innerHTML = 'These are the docs for FastMCP 3. <a href="/getting-started/welcome" style="color: white; text-decoration: underline; font-weight: 700;">FastMCP 4</a> is now available.';
container.insertBefore(banner, container.firstChild);
}
} else if (!isV3 && banner) {
banner.remove();
}
}
function run() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addBanner);
} else {
addBanner();
}
}
run();
let lastUrl = location.href;
new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(addBanner, 100);
}
}).observe(document.body, {subtree: true, childList: true});
})();

312
docs/v3-navigation.json Normal file
View file

@ -0,0 +1,312 @@
{
"dropdowns": [
{
"dropdown": "Documentation",
"groups": [
{
"group": "Get Started",
"pages": [
"v3/getting-started/welcome",
"v3/getting-started/installation",
"v3/getting-started/quickstart"
]
},
{
"group": "Servers",
"pages": [
"v3/servers/server",
{
"collapsed": true,
"group": "Core Components",
"icon": "toolbox",
"pages": [
"v3/servers/tools",
"v3/servers/resources",
"v3/servers/prompts",
"v3/servers/context"
]
},
{
"collapsed": true,
"group": "Working with Tools",
"icon": "wand-magic-sparkles",
"pages": [
"v3/servers/transforms/transforms",
"v3/servers/transforms/tool-transformation",
"v3/servers/transforms/code-mode",
"v3/servers/transforms/tool-search",
"v3/servers/transforms/namespace",
"v3/servers/visibility",
"v3/servers/transforms/resources-as-tools",
"v3/servers/transforms/prompts-as-tools",
"v3/servers/tool-fingerprinting"
]
},
{
"collapsed": true,
"group": "MCP Providers",
"icon": "layer-group",
"pages": [
"v3/servers/providers/overview",
"v3/servers/providers/local",
"v3/servers/providers/filesystem",
"v3/servers/providers/proxy",
"v3/servers/providers/skills",
"v3/servers/composition",
"v3/servers/providers/custom"
]
},
{
"collapsed": true,
"group": "Interactivity",
"icon": "comments",
"pages": [
"v3/servers/elicitation",
"v3/servers/sampling",
"v3/servers/progress",
"v3/servers/logging",
"v3/servers/pagination",
"v3/servers/icons"
]
},
{
"collapsed": true,
"group": "Extensibility",
"icon": "puzzle-piece",
"pages": [
"v3/servers/middleware",
"v3/servers/dependency-injection",
"v3/servers/lifespan",
"v3/servers/storage-backends",
"v3/servers/tasks",
"v3/servers/versioning"
]
},
{
"collapsed": true,
"group": "Auth",
"icon": "shield-check",
"pages": [
{
"collapsed": true,
"group": "Authentication",
"icon": "key",
"pages": [
"v3/servers/auth/authentication",
"v3/servers/auth/token-verification",
"v3/servers/auth/remote-oauth",
"v3/servers/auth/oauth-proxy",
"v3/servers/auth/oidc-proxy",
"v3/servers/auth/full-oauth-server",
"v3/servers/auth/multi-auth"
]
},
"v3/servers/authorization"
]
},
{
"collapsed": true,
"group": "Deployment",
"icon": "rocket",
"pages": [
"v3/deployment/running-server",
"v3/deployment/http",
"v3/deployment/sandboxed-agents",
"v3/deployment/prefect-horizon",
"v3/deployment/server-configuration",
"v3/servers/testing",
"v3/servers/telemetry"
]
}
]
},
{
"group": "Apps",
"pages": [
"v3/apps/overview",
"v3/apps/quickstart",
"v3/apps/fastmcp-app",
"v3/apps/prefab",
"v3/apps/generative",
"v3/apps/low-level",
{
"collapsed": true,
"group": "Reference",
"icon": "book",
"pages": [
{
"collapsed": true,
"group": "Prefab Providers",
"icon": "cube",
"pages": [
"v3/apps/providers/approval",
"v3/apps/providers/choice",
"v3/apps/providers/file-upload",
"v3/apps/providers/form"
]
},
"v3/apps/development",
"v3/apps/examples",
"v3/apps/architecture"
]
}
]
},
{
"group": "Clients",
"pages": [
"v3/clients/client",
"v3/clients/client-only-package",
"v3/clients/transports",
"v3/clients/fastmcp-remote",
{
"collapsed": true,
"group": "Operations",
"icon": "toolbox",
"pages": [
"v3/clients/tools",
"v3/clients/resources",
"v3/clients/prompts",
"v3/clients/sampling",
"v3/clients/elicitation",
"v3/clients/tasks",
"v3/clients/progress",
"v3/clients/logging",
"v3/clients/roots",
"v3/clients/notifications"
],
"tag": "UPDATED"
},
{
"collapsed": true,
"group": "Authentication",
"icon": "key",
"pages": [
"v3/clients/auth/oauth",
"v3/clients/auth/cimd",
"v3/clients/auth/bearer"
],
"tag": "UPDATED"
}
]
},
{
"group": "Integrations",
"pages": [
{
"collapsed": true,
"group": "Auth",
"icon": "key",
"pages": [
"v3/integrations/auth0",
"v3/integrations/authkit",
"v3/integrations/aws-cognito",
"v3/integrations/azure",
"v3/integrations/descope",
"v3/integrations/discord",
"v3/integrations/eunomia-authorization",
"v3/integrations/github",
"v3/integrations/google",
"v3/integrations/huggingface",
"v3/integrations/keycloak",
"v3/integrations/oci",
"v3/integrations/permit",
"v3/integrations/propelauth",
"v3/integrations/scalekit",
"v3/integrations/supabase",
"v3/integrations/workos"
]
},
{
"collapsed": true,
"group": "Web Frameworks",
"icon": "code",
"pages": [
"v3/integrations/fastapi",
"v3/integrations/openapi"
]
},
{
"collapsed": true,
"group": "AI Assistants",
"icon": "robot",
"pages": [
"v3/integrations/chatgpt",
"v3/integrations/claude-code",
"v3/integrations/claude-desktop",
"v3/integrations/cursor",
"v3/integrations/gemini-cli",
"v3/integrations/goose"
]
},
{
"collapsed": true,
"group": "AI SDKs",
"icon": "microchip",
"pages": [
"v3/integrations/anthropic",
"v3/integrations/gemini",
"v3/integrations/openai",
"v3/integrations/pydantic-ai"
]
},
"v3/integrations/mcp-json-configuration"
]
},
{
"group": "More",
"pages": [
"v3/more/settings",
{
"collapsed": true,
"group": "CLI",
"icon": "terminal",
"pages": [
"v3/cli/overview",
"v3/cli/running",
"v3/cli/install-mcp",
"v3/cli/inspecting",
"v3/cli/client",
"v3/cli/generate-cli",
"v3/cli/auth"
]
},
{
"collapsed": true,
"group": "Upgrading",
"icon": "up",
"pages": [
"v3/getting-started/upgrading/from-fastmcp-2",
"v3/getting-started/upgrading/from-mcp-sdk",
"v3/getting-started/upgrading/from-low-level-sdk"
]
},
{
"collapsed": true,
"group": "Development",
"icon": "code",
"pages": [
"v3/development/contributing",
"v3/development/tests",
"v3/development/releases",
"v3/patterns/contrib"
]
},
{
"collapsed": true,
"group": "What's New",
"icon": "sparkles",
"pages": [
"v3/updates",
"v3/changelog"
]
},
"v3/more/faq"
]
}
],
"icon": "book"
}
],
"version": "v3.4.4"
}

View file

@ -0,0 +1,118 @@
---
title: Architecture
sidebarTitle: Architecture
description: How FastMCP apps work under the hood — from Python to pixels.
icon: sitemap
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
You don't need this page to build apps. It's for when something isn't rendering the way you expect, when UI tool calls aren't reaching your server, or when you're writing [custom HTML apps](/apps/low-level) and need to understand the protocol directly.
## The pipeline
An MCP app moves through five stages from Python to pixels:
```
Python components → JSON tree → structuredContent → Renderer iframe → Host UI
```
You write Prefab components. FastMCP serializes them to a JSON component tree and delivers it as `structuredContent` on the tool result. The host loads the Prefab renderer in a sandboxed iframe, pushes the JSON in, and the renderer paints the UI. If the UI calls server tools, it talks back through the same `postMessage` channel.
The sections below walk each stage.
## Tool registration
When you mark a tool with `app=True` or `@app.ui()`, FastMCP wires up the metadata and renderer resource that the protocol requires.
### The `app=True` flag
`app` on `@mcp.tool` accepts `True`, an `AppConfig`, or a dict. When you pass `True`, FastMCP checks whether the tool's return type is a Prefab type (`PrefabApp`, `Component`, or unions containing them). If it qualifies, FastMCP expands `True` into a full `AppConfig` — setting the renderer URI, CSP headers, and visibility — and stores it in the tool's `meta["ui"]` dict.
This expansion also registers the shared Prefab renderer resource (below). The tool and the renderer are linked through a `resourceUri` field in the metadata: the tool says "render me with `ui://prefab/renderer.html`" and the host fetches that resource when it displays the result.
Type inference works the same way. If the return type is a Prefab type and you haven't set `app` explicitly, FastMCP auto-wires the metadata as if you'd written `app=True`.
### FastMCPApp registration
`FastMCPApp` uses the same mechanism but adds two things. First, it tags every tool — both `@app.ui()` entry points and `@app.tool()` backends — with `meta["fastmcp"]["app"]` set to the app's name. That tag lets the server identify which app a tool belongs to when routing UI calls.
Second, it sets `meta["ui"]["visibility"]` to control who can see each tool. Entry points default to `["model"]` (LLM-visible). Backend tools default to `["app"]` (UI-only). Hosts use this to filter the tool list.
## Serialization
When a Prefab tool runs, its return value — a `PrefabApp` or a bare `Component` — becomes a JSON blob the renderer can interpret.
### `PrefabApp.to_json()`
The entry point is `PrefabApp.to_json()`. It walks the component tree and produces a JSON object with three top-level keys: `view` (the component tree), `state` (initial state values), and `_meta` (routing metadata).
FastMCP passes a `tool_resolver` callback to `to_json()`. Whenever the tree contains a `CallTool` action that references a function (not a string), the resolver converts it to a `ResolvedTool` with the function's registered name. This is how `CallTool(save_contact)` becomes `CallTool("save_contact")` on the wire. The resolver also handles `unwrap_result` — a flag telling the renderer to unwrap single-value results from the `{"result": value}` envelope FastMCP uses for schema compliance.
### The `_meta.fastmcp.app` tag
After `to_json()` produces the tree, FastMCP injects `_meta.fastmcp.app` with the app's name (if the tool belongs to a `FastMCPApp`). This tag rides along inside `structuredContent` all the way to the renderer.
When the renderer calls a backend tool, it includes `_meta.fastmcp.app` in the `CallTool` request. The server sees this tag and routes the call through a special path that bypasses transforms (below).
### ToolResult assembly
The final tool result has two parts: `content` (a list of `TextContent` blocks for the LLM) and `structuredContent` (the JSON tree for the renderer). By default, Prefab tools send `"[Rendered Prefab UI]"` as the text content — just enough for the LLM to know something was rendered. If you return a `ToolResult` explicitly, you control both halves.
## Tool call routing
Normal tool calls go through the provider chain, which applies transforms (namespace prefixes, visibility filters) before resolving by name. App UI calls need a different path.
### The `get_app_tool` bypass
Backend tools are typically hidden from the model (`visibility=["app"]`). Visibility transforms would filter them out of normal resolution. And namespace transforms might rename them — `save_contact` becomes `contacts_save_contact` — while the renderer still uses the original name.
`get_app_tool` solves both problems. When the server sees `_meta.fastmcp.app` on an incoming `CallTool` request, it calls `get_app_tool(app_name, tool_name)` instead of the normal `get_tool(name)`. This walks the provider tree directly, skipping transforms. It finds the tool by its original registered name and verifies that its `meta["fastmcp"]["app"]` matches the expected app.
That's why `CallTool("save_contact")` keeps working when the server is mounted under a namespace. The renderer sends the original name plus the app identity; the server uses `get_app_tool` to find it without transforms in the way.
Authorization still applies. `get_app_tool` bypasses transforms but runs auth checks against the tool's `auth` config before executing.
### Provider delegation
`get_app_tool` is defined on the `Provider` base class and overridden by aggregate and wrapped providers. Aggregate providers fan out the lookup across child providers in parallel. Wrapped providers (like `FastMCPProvider`, which wraps a nested `FastMCP` server) delegate to the inner server's `get_app_tool`. Backend tools are reachable through any depth of composition.
## The renderer
The Prefab renderer is a self-contained JavaScript application that interprets the JSON component tree and renders it as a React UI.
### The shared resource
FastMCP registers the renderer as a `ui://prefab/renderer.html` resource with MIME type `text/html;profile=mcp-app`. The HTML is bundled inside the `prefab-ui` Python package; `get_renderer_html()` returns it as a string. All Prefab tools on a server share this single resource.
The resource also carries CSP metadata (via `get_renderer_csp()`) declaring the CDN domains the renderer needs. Hosts use this to configure the iframe's Content Security Policy.
### `postMessage` communication
The renderer lives in a sandboxed iframe and communicates with the host using `postMessage`. The protocol follows the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) spec:
The host pushes the tool result (with `structuredContent`) into the iframe. The renderer parses the component tree, initializes state, and renders the UI. When the user interacts — submitting a form, clicking a button — and the interaction triggers a `CallTool` action, the renderer sends a `callServerTool` message back to the host via `postMessage`. The host forwards it as a regular MCP `tools/call` request to the server, including `_meta.fastmcp.app` for routing.
The response flows back the same way: server → host → iframe via `postMessage`, and the renderer updates state with the result.
### AppBridge
The `@modelcontextprotocol/ext-apps` JavaScript SDK provides the `App` class (sometimes called AppBridge) that manages the `postMessage` handshake. It handles connection negotiation, tool result delivery, server tool calls, and host context (safe area insets, theme preferences). The Prefab renderer uses it internally; you only touch it directly when building [custom HTML apps](/apps/low-level).
## The dev server
`fastmcp dev apps` simulates the host-side behavior locally without a real MCP client.
### Proxy architecture
Two HTTP servers. Your MCP server runs on port 8000 with the Streamable HTTP transport. The dev UI runs on port 8080 and serves a picker page that lists your app tools.
A reverse proxy at `/mcp` on the dev server forwards requests to your MCP server. This matters because the renderer iframe runs on `localhost:8080` and your MCP server runs on `localhost:8000` — without the proxy, the renderer's `callServerTool` requests would be cross-origin and the browser would block them. The proxy keeps everything same-origin from the iframe's perspective.
### The launch flow
When you select a tool and click launch, the dev UI calls the tool through the proxy, receives the `structuredContent` response, and opens a new tab. That tab loads the tool's renderer resource (via the proxy), creates an AppBridge, and pushes the tool result into the renderer. From here on it matches what a real host provides: the renderer displays the UI, and any `CallTool` actions route back through the proxy to your server.
Auto-reload is on by default, so changes to your server code restart the MCP server automatically. The dev UI keeps running — relaunch the tool to see changes.

View file

@ -0,0 +1,23 @@
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column
from prefab_ui.components.charts import BarChart, ChartSeries
data = [
{"quarter": "Q1", "revenue": 42000, "costs": 28000},
{"quarter": "Q2", "revenue": 51000, "costs": 31000},
{"quarter": "Q3", "revenue": 47000, "costs": 29000},
{"quarter": "Q4", "revenue": 63000, "costs": 35000},
]
with PrefabApp() as app:
with Column(css_class="p-6"):
BarChart(
data=data,
series=[
ChartSeries(data_key="revenue", label="Revenue"),
ChartSeries(data_key="costs", label="Costs"),
],
x_axis="quarter",
show_legend=True,
height=250,
)

View file

@ -0,0 +1,78 @@
from prefab_ui.actions import ShowToast
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
H3,
Badge,
Button,
Column,
DataTable,
DataTableColumn,
Form,
Input,
Row,
Select,
SelectOption,
Separator,
)
contacts = [
{"name": "Arthur Dent", "email": "arthur@earth.com", "category": "Customer"},
{"name": "Ford Prefect", "email": "ford@betelgeuse.org", "category": "Partner"},
{
"name": "Trillian Astra",
"email": "trillian@heartofgold.com",
"category": "Customer",
},
{"name": "Zaphod Beeblebrox", "email": "zaphod@galaxy.gov", "category": "Vendor"},
]
rows = [
{
"name": c["name"],
"email": c["email"],
"category": Badge(
c["category"],
variant="success"
if c["category"] == "Customer"
else "secondary"
if c["category"] == "Partner"
else "outline",
),
}
for c in contacts
]
with PrefabApp() as app:
with Column(gap=4, css_class="p-6"):
DataTable(
columns=[
DataTableColumn(key="name", header="Name", sortable=True),
DataTableColumn(key="email", header="Email"),
DataTableColumn(key="category", header="Category"),
],
rows=rows,
search=True,
)
Separator()
H3("Add Contact")
with Form(
on_submit=ShowToast(
"Contact saved! (preview demo — no backend wired)",
variant="success",
),
):
with Row(gap=4):
Input(name="name", label="Name", placeholder="Full name", required=True)
Input(
name="email",
label="Email",
placeholder="name@example.com",
required=True,
)
with Select(name="category", label="Category"):
SelectOption(value="Customer", label="Customer")
SelectOption(value="Partner", label="Partner")
SelectOption(value="Vendor", label="Vendor")
Button("Save Contact")

View file

@ -0,0 +1,68 @@
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
Badge,
Column,
DataTable,
DataTableColumn,
Row,
Separator,
)
from prefab_ui.components.charts import BarChart, ChartSeries
from prefab_ui.components.metric import Metric
monthly = [
{"month": "Jan", "revenue": 48200, "costs": 31000},
{"month": "Feb", "revenue": 52100, "costs": 32500},
{"month": "Mar", "revenue": 61800, "costs": 34200},
{"month": "Apr", "revenue": 58400, "costs": 33800},
]
deals = [
{"account": "Acme Corp", "value": "$84,000", "stage": "Won"},
{"account": "Globex Inc", "value": "$52,000", "stage": "Negotiation"},
{"account": "Initech", "value": "$31,500", "stage": "Proposal"},
{"account": "Wayne Enterprises", "value": "$45,000", "stage": "Lost"},
]
rows = [
{
"account": d["account"],
"value": d["value"],
"stage": Badge(
d["stage"],
variant="success"
if d["stage"] == "Won"
else "destructive"
if d["stage"] == "Lost"
else "secondary",
),
}
for d in deals
]
total = sum(m["revenue"] for m in monthly)
with PrefabApp() as app:
with Column(gap=4, css_class="p-6"):
with Row(gap=6):
Metric(label="Revenue (Q1-Q4)", value=f"${total:,}")
Metric(label="Deals", value=f"{len(deals)}")
BarChart(
data=monthly,
series=[
ChartSeries(data_key="revenue", label="Revenue"),
ChartSeries(data_key="costs", label="Costs"),
],
x_axis="month",
show_legend=True,
height=200,
)
Separator()
DataTable(
columns=[
DataTableColumn(key="account", header="Account", sortable=True),
DataTableColumn(key="value", header="Value", sortable=True),
DataTableColumn(key="stage", header="Stage"),
],
rows=rows,
)

View file

@ -0,0 +1,24 @@
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, DataTable, DataTableColumn
employees = [
{"name": "Alice Chen", "role": "Staff Engineer", "dept": "Platform"},
{"name": "Bob Martinez", "role": "Lead Designer", "dept": "Design"},
{"name": "Carol Johnson", "role": "Senior Engineer", "dept": "Platform"},
{"name": "David Kim", "role": "Product Manager", "dept": "Product"},
{"name": "Eva Mueller", "role": "Engineer", "dept": "Platform"},
{"name": "Frank Lee", "role": "Data Scientist", "dept": "ML"},
{"name": "Grace Park", "role": "Eng Manager", "dept": "Platform"},
]
with PrefabApp() as app:
with Column(gap=4, css_class="p-6"):
DataTable(
columns=[
DataTableColumn(key="name", header="Name", sortable=True),
DataTableColumn(key="role", header="Role", sortable=True),
DataTableColumn(key="dept", header="Dept", sortable=True),
],
rows=employees,
search=True,
)

View file

@ -0,0 +1,461 @@
"""The Hitchhiker's Guide dashboard from the Prefab welcome page.
Run with:
prefab serve examples/hitchhikers-guide/dashboard.py
prefab export examples/hitchhikers-guide/dashboard.py
"""
from prefab_ui import PrefabApp
from prefab_ui.actions import SetInterval, SetState, ShowToast
from prefab_ui.components import (
Alert,
AlertDescription,
AlertTitle,
Badge,
Button,
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
Carousel,
Checkbox,
Column,
Combobox,
ComboboxOption,
DataTable,
DataTableColumn,
DatePicker,
Dialog,
Grid,
GridItem,
HoverCard,
Loader,
Metric,
Muted,
P,
Progress,
Radio,
RadioGroup,
Ring,
Row,
Separator,
Slider,
Switch,
Text,
Tooltip,
)
from prefab_ui.components.charts import (
BarChart,
ChartSeries,
RadarChart,
Sparkline,
)
from prefab_ui.components.control_flow import Else, If
from prefab_ui.rx import Rx
ctx_tick = Rx("ctx_tick")
# Context window: climbs from 24% to ~78%, then resets
ctx_pct = (ctx_tick % 20) * 3 + 20
ctx_variant = (ctx_pct > 70).then(
"destructive", (ctx_pct <= 33).then("success", "default")
)
with PrefabApp(
title="Prefab Showcase",
state={"ctx_tick": 0, "improbability": 42},
on_mount=SetInterval(
400,
on_tick=SetState("ctx_tick", ctx_tick + 1),
),
) as app:
with Grid(columns={"default": 1, "md": 2, "lg": 4}, gap=4):
# ── Col 1 ─────────────────────────────────────────────────────────
with Column(gap=4):
with Card():
with CardHeader():
CardTitle("Register Towel")
CardDescription("The most important item in the galaxy")
with CardContent():
with Column(gap=3):
with Combobox(
placeholder="Type...",
search_placeholder="Search types...",
):
ComboboxOption("Bath", value="bath")
ComboboxOption("Beach", value="beach")
ComboboxOption("Interstellar", value="interstellar")
ComboboxOption("Microfiber", value="micro")
DatePicker(placeholder="Registration date")
with CardFooter():
with Row(gap=2):
with Dialog(
title="Towel Registered!",
description="Your towel has been added to the galactic registry.",
):
Button("Register")
Text("Don't forget to bring it.")
Button("Cancel", variant="outline")
with If("{{ !pressed }}"):
Button(
"This is probably the best button to press.",
variant="success",
on_click=SetState("pressed", True),
)
with Else():
Button(
"Please do not press this button again.",
variant="destructive",
on_click=SetState("pressed", False),
)
with Card():
with CardHeader():
CardTitle("Ship Status")
with CardContent():
with Column(gap=3):
with Row(
align="center",
css_class="justify-between",
):
Text("heart-of-gold")
with HoverCard(open_delay=0, close_delay=200):
Badge("In Orbit", variant="default")
with Column(gap=2):
Text("heart-of-gold")
Muted("Deployed 2h ago")
Progress(
value=100,
max=100,
variant="success",
)
Progress(
value=100,
max=100,
indicator_class="bg-yellow-400",
)
with Row(
align="center",
css_class="justify-between",
):
Text("vogon-poetry")
with Tooltip("64% — ETA 12 min", delay=0):
with Badge(variant="secondary"):
Loader(size="sm")
Text("Deploying")
Progress(value=64, max=100)
with Row(
align="center",
css_class="justify-between",
):
Text("deep-thought")
with Tooltip(
"Computing... 7.5 million years remaining",
delay=0,
):
with Badge(variant="outline"):
Loader(size="sm", variant="ios")
Text("Soon...")
Progress(value=12, max=100)
with Card():
with CardHeader():
CardTitle("Planet Ratings")
with CardContent():
RadarChart(
data=[
{"axis": "Views", "earth": 30, "mag": 95},
{"axis": "Fjords", "earth": 65, "mag": 100},
{"axis": "Pubs", "earth": 90, "mag": 10},
{"axis": "Mice", "earth": 40, "mag": 85},
{"axis": "Tea", "earth": 95, "mag": 15},
{"axis": "Safety", "earth": 45, "mag": 70},
],
series=[
ChartSeries(dataKey="earth", label="Earth"),
ChartSeries(dataKey="mag", label="Magrathea"),
],
axis_key="axis",
height=200,
show_legend=True,
show_tooltip=True,
)
# ── Col 2 ─────────────────────────────────────────────────────────
with Column(gap=4):
with Card():
with CardHeader():
CardTitle("Survival Odds")
with CardContent(css_class="w-fit mx-auto"):
Ring(
value=42,
label="42%",
variant="info",
size="lg",
thickness=12,
indicator_class="group-hover:drop-shadow-[0_0_24px_rgba(59,130,246,0.9)]",
)
with Card():
with CardHeader():
with Row(gap=2, align="center"):
CardTitle("Improbability Drive")
Loader(
variant="pulse",
size="sm",
css_class="text-blue-500",
)
with CardContent():
with Column(gap=2):
Slider(
min=0,
max=100,
value=42,
name="improbability",
)
with Row(
align="center",
css_class="justify-between",
):
Muted("Probable")
Muted("Infinite")
with Carousel(auto_advance=3000, show_controls=False, direction="up"):
with Alert(variant="success", icon="circle-check"):
AlertTitle("Don't Panic")
AlertDescription("Normality achieved.")
with Alert(variant="destructive", icon="triangle-alert"):
AlertTitle("Display Department")
AlertDescription("Beware of the leopard.")
with Card():
with CardHeader():
CardTitle("Prefect Horizon Config")
with CardContent():
with Column(gap=3):
Switch(
label="Auto-scale agents",
value=True,
name="autoscale",
)
Separator()
Switch(
label="Code Mode",
value=True,
name="code_mode",
)
Separator()
Switch(
label="Tool call caching",
value=False,
name="cache",
)
with CardFooter():
Button(
"Save Preferences",
on_click=ShowToast("Preferences saved!"),
)
with Card():
with CardHeader():
CardTitle("Travel Class")
with CardContent():
with RadioGroup(name="travel_class"):
Radio(option="economy", label="Economy")
Radio(option="business", label="Business Class")
Radio(
option="improbability",
label="Infinite Improbability",
value=True,
)
# ── Cols 34: summary row, chart, then 2-col grid below ─────────
with GridItem(css_class="md:col-span-2"):
with Column(gap=4):
with Grid(columns=2, gap=4, css_class="h-32"):
with Card():
with CardHeader():
CardTitle("Context Window")
with CardContent():
with Column(
gap=6,
justify="center",
css_class="h-full",
):
with Row(
align="center",
css_class="justify-between",
):
Text(f"{ctx_pct}% used")
Muted(f"{ctx_pct * 2}k / 200k tokens")
with Tooltip(
"Auto-compact buffer: 12%",
delay=0,
):
Progress(
value=ctx_pct,
max=100,
variant=ctx_variant,
)
with Card(css_class="pb-0 gap-0"):
with CardContent():
Metric(
label="Fjords designed",
value="1,847",
delta="+3 coastlines",
)
Sparkline(
data=[
820,
950,
1100,
980,
1250,
1400,
1350,
1500,
1680,
1847,
],
variant="success",
fill=True,
css_class="h-16",
)
with Card():
with CardHeader():
CardTitle("Towel Incidents")
with CardContent():
BarChart(
data=[
{"month": "Jan", "lost": 8, "found": 5},
{"month": "Feb", "lost": 24, "found": 15},
{"month": "Mar", "lost": 12, "found": 28},
{"month": "Apr", "lost": 35, "found": 19},
{"month": "May", "lost": 18, "found": 38},
{"month": "Jun", "lost": 42, "found": 30},
],
series=[
ChartSeries(dataKey="lost", label="Lost"),
ChartSeries(dataKey="found", label="Found"),
],
x_axis="month",
height=200,
bar_radius=4,
show_legend=True,
show_tooltip=True,
show_grid=True,
)
with Grid(columns=2, gap=4):
with Column(gap=4):
with Card():
with CardContent():
with Column(gap=2):
Checkbox(label="Towel packed", value=True)
Checkbox(label="Guide charged", value=True)
Checkbox(
label="Babel fish inserted",
value=False,
)
with Card():
with CardHeader():
CardTitle("Marvin's Mood")
with CardContent():
with Column(gap=3):
P("How's life?")
with Column(gap=2):
Button(
"Meh",
on_click=ShowToast(
"Noted. Enthusiasm levels nominal."
),
)
Button(
"Depressed",
variant="info",
on_click=ShowToast(
"I think you ought to "
"know I'm feeling very "
"depressed."
),
)
Button(
"Don't talk to me about life",
variant="warning",
on_click=ShowToast(
"Brain the size of a "
"planet and they ask me "
"to pick up a piece of "
"paper."
),
)
with Column(gap=4):
with Card():
with CardContent():
with Row(gap=2, align="center"):
Loader(variant="dots", size="sm")
Muted("Marvin is thinking...")
with Card():
with CardContent():
DataTable(
columns=[
DataTableColumn(
key="crew",
header="Crew",
sortable=True,
),
DataTableColumn(
key="species",
header="Species",
sortable=True,
),
DataTableColumn(
key="towel",
header="Towel?",
sortable=True,
),
DataTableColumn(
key="status",
header="Status",
sortable=True,
),
],
rows=[
{
"crew": "Arthur Dent",
"species": "Human",
"towel": "Yes",
"status": "Confused",
},
{
"crew": "Ford Prefect",
"species": "Betelgeusian",
"towel": "Always",
"status": "Drinking",
},
{
"crew": "Zaphod",
"species": "Betelgeusian",
"towel": "Lost it",
"status": "Presidential",
},
{
"crew": "Trillian",
"species": "Human",
"towel": "Yes",
"status": "Navigating",
},
{
"crew": "Marvin",
"species": "Android",
"towel": "No point",
"status": "Depressed",
},
{
"crew": "Slartibartfast",
"species": "Magrathean",
"towel": "Somewhere",
"status": "Designing",
},
],
search=True,
paginated=False,
)

View file

@ -0,0 +1,21 @@
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column
from prefab_ui.components.charts import PieChart
data = [
{"category": "Bug", "count": 42},
{"category": "Feature", "count": 28},
{"category": "Docs", "count": 15},
{"category": "Infra", "count": 10},
]
with PrefabApp() as app:
with Column(css_class="p-6"):
PieChart(
data=data,
data_key="count",
name_key="category",
inner_radius=50,
show_legend=True,
height=240,
)

View file

@ -0,0 +1,66 @@
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
Column,
Row,
Select,
SelectOption,
Switch,
Text,
)
from prefab_ui.components.charts import BarChart, ChartSeries
from prefab_ui.components.control_flow import If
from prefab_ui.components.metric import Metric
from prefab_ui.rx import Rx
region = Rx("region")
north = [
{"month": "Jan", "sales": 22000},
{"month": "Feb", "sales": 25500},
{"month": "Mar", "sales": 24200},
]
south = [
{"month": "Jan", "sales": 5800},
{"month": "Feb", "sales": 6400},
{"month": "Mar", "sales": 5600},
]
west = [
{"month": "Jan", "sales": 6000},
{"month": "Feb", "sales": 6000},
{"month": "Mar", "sales": 5600},
]
with PrefabApp(
state={
"region": "north",
"north": north,
"south": south,
"west": west,
"show_target": True,
},
) as app:
with Column(
gap=4,
css_class="p-6",
let={
"data": "{{ region == 'south' ? south : region == 'west' ? west : north }}",
},
):
with Row(gap=4, align="center"):
with Select(name="region", css_class="w-40"):
SelectOption(value="north", label="North")
SelectOption(value="south", label="South")
SelectOption(value="west", label="West")
Switch(name="show_target", css_class="ml-auto")
Text("Show target", css_class="text-sm text-muted-foreground")
BarChart(
data=Rx("data"),
series=[ChartSeries(data_key="sales", label="Sales")],
x_axis="month",
height=200,
)
with If(Rx("show_target")):
Metric(
label="Q1 Target",
value="$75,000",
)

View file

@ -0,0 +1,116 @@
from collections import Counter
from prefab_ui.actions import SetState
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
H3,
Badge,
Card,
CardContent,
CardHeader,
Column,
DataTable,
DataTableColumn,
Grid,
Row,
Small,
Text,
)
from prefab_ui.components.charts import PieChart
from prefab_ui.components.control_flow import If
from prefab_ui.rx import STATE, Rx
MEMBERS = [
{
"name": "Alice Chen",
"role": "Staff Engineer",
"office": "San Francisco",
"email": "alice@company.com",
"projects": 3,
},
{
"name": "Bob Martinez",
"role": "Lead Designer",
"office": "New York",
"email": "bob@company.com",
"projects": 5,
},
{
"name": "Carol Johnson",
"role": "Senior Engineer",
"office": "London",
"email": "carol@company.com",
"projects": 2,
},
{
"name": "David Kim",
"role": "Product Manager",
"office": "San Francisco",
"email": "david@company.com",
"projects": 7,
},
{
"name": "Eva Mueller",
"role": "Engineer",
"office": "Berlin",
"email": "eva@company.com",
"projects": 1,
},
{
"name": "Frank Lee",
"role": "Data Scientist",
"office": "San Francisco",
"email": "frank@company.com",
"projects": 4,
},
{
"name": "Grace Park",
"role": "Engineering Manager",
"office": "New York",
"email": "grace@company.com",
"projects": 6,
},
]
OFFICE_COUNTS = [
{"office": office, "count": count}
for office, count in Counter(m["office"] for m in MEMBERS).items()
]
with PrefabApp(state={"selected": None}) as app:
with Column(gap=4, css_class="p-6"):
with Grid(columns=[1, 2], gap=4):
PieChart(
data=OFFICE_COUNTS,
data_key="count",
name_key="office",
show_legend=True,
)
DataTable(
columns=[
DataTableColumn(key="name", header="Name", sortable=True),
DataTableColumn(key="role", header="Role", sortable=True),
DataTableColumn(key="office", header="Office", sortable=True),
],
rows=MEMBERS,
search=True,
on_row_click=SetState("selected", Rx("$event")),
)
with If(STATE.selected):
with Card():
with CardHeader():
with Row(gap=2, align="center"):
H3(Rx("selected.name"))
Badge(Rx("selected.office"))
with CardContent():
with Grid(columns=3, gap=4):
with Column(gap=0):
Small("Role")
Text(Rx("selected.role"))
with Column(gap=0):
Small("Email")
Text(Rx("selected.email"))
with Column(gap=0):
Small("Active Projects")
Text(Rx("selected.projects"))

View file

@ -0,0 +1,39 @@
from collections import Counter
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, DataTable, DataTableColumn, Grid
from prefab_ui.components.charts import PieChart
members = [
{"name": "Alice Chen", "role": "Staff Engineer", "office": "San Francisco"},
{"name": "Bob Martinez", "role": "Lead Designer", "office": "New York"},
{"name": "Carol Johnson", "role": "Senior Engineer", "office": "London"},
{"name": "David Kim", "role": "Product Manager", "office": "San Francisco"},
{"name": "Eva Mueller", "role": "Engineer", "office": "Berlin"},
{"name": "Frank Lee", "role": "Data Scientist", "office": "San Francisco"},
{"name": "Grace Park", "role": "Engineering Manager", "office": "New York"},
]
office_counts = [
{"office": office, "count": count}
for office, count in Counter(m["office"] for m in members).items()
]
with PrefabApp() as app:
with Column(gap=4, css_class="p-6"):
with Grid(columns=[1, 2], gap=4):
PieChart(
data=office_counts,
data_key="count",
name_key="office",
show_legend=True,
)
DataTable(
columns=[
DataTableColumn(key="name", header="Name", sortable=True),
DataTableColumn(key="role", header="Role", sortable=True),
DataTableColumn(key="office", header="Office", sortable=True),
],
rows=members,
search=True,
)

View file

@ -0,0 +1,65 @@
---
title: Development
sidebarTitle: Development
description: Preview and test your app tools locally without a full MCP host.
icon: flask
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
<Frame>
<img src="/apps/images/dev-app.png" alt="The dev UI showing a rendered Prefab app with the MCP inspector panel" />
</Frame>
`fastmcp dev apps` gives you a browser preview for your app tools without needing an MCP host client. It starts your server and a local dev UI side by side: you pick a tool, fill in its arguments, and the rendered result opens in a new tab.
Works with both [Interactive Tools](/apps/prefab) and [custom HTML apps](/apps/low-level).
## Quick start
```bash
fastmcp dev apps server.py
```
The dev UI opens at `http://localhost:8080`. Your MCP server runs on port 8000 with auto-reload enabled by default — save a file and the server restarts automatically.
## How it works
The dev server does three things:
The **picker page** connects to your MCP server, finds all tools with UI metadata, and renders a form for each one. The forms are auto-generated from the tool's input schema — text fields, dropdowns, checkboxes, all wired up.
When you submit a form, the dev server **calls your tool** via the MCP protocol and opens the result in a new tab. The result page loads the tool's UI resource (the Prefab renderer or your custom HTML) inside an AppBridge — the same protocol that real MCP hosts use.
A **reverse proxy** on `/mcp` forwards requests from the browser to your MCP server, avoiding CORS issues that would otherwise block the iframe-based renderer from talking to a different port.
## MCP inspector
The dev UI includes an inspector panel on the left side that captures MCP traffic in real time. It shows JSON-RPC messages flowing between the browser and your server — requests, responses, and AppBridge `postMessage` traffic.
Each entry shows direction, method, timing, and a smart summary. Click any entry to expand the full JSON-RPC body. The panel auto-scrolls to new messages unless you've scrolled up to inspect older ones.
The inspector is useful for debugging: you can see exactly what arguments your tool received, what it returned, and how the AppBridge communicated with the renderer.
## Options
```bash
fastmcp dev apps server.py:mcp --mcp-port 9000 --dev-port 9090 --no-reload
```
| Option | Flag | Default | Description |
| ------ | ---- | ------- | ----------- |
| MCP Port | `--mcp-port` | `8000` | Port for your MCP server |
| Dev Port | `--dev-port` | `8080` | Port for the dev UI |
| Auto-Reload | `--reload` / `--no-reload` | On | Watch files and restart the server on changes |
## Multiple tools
If your server has multiple app tools, the picker shows a dropdown. Each tool gets its own form and launch button. The tool's `title` is displayed when available, falling back to the tool name.
```bash
# Server with multiple app tools
fastmcp dev apps examples/apps/contacts/contacts_server.py
```

92
docs/v3/apps/examples.mdx Normal file
View file

@ -0,0 +1,92 @@
---
title: Examples
sidebarTitle: Examples
description: Example apps you can run right now.
icon: images
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
Each tile below is a working FastMCP server you can run with `fastmcp dev apps` or connect to from any MCP host. Source lives in `examples/apps/` in the repository.
<Columns cols={2}>
<Tile href="#sales-dashboard" title="Sales Dashboard" description="Metrics, charts, and deal pipeline">
<div style={{overflow: "hidden", width: "100%"}}>
<img src="/apps/images/app-example-sales-dashboard.png" />
</div>
</Tile>
<Tile href="#system-monitor" title="System Monitor" description="Live CPU, memory, disk with auto-refresh">
<img src="/apps/images/app-example-system-dashboard.png" />
</Tile>
<Tile href="#quiz" title="Quiz" description="LLM-generated trivia with scoring">
<img src="/apps/images/app-example-quiz.png" />
</Tile>
<Tile href="#interactive-map" title="Interactive Map" description="Geocoded addresses on Leaflet">
<img src="/apps/images/app-example-map.png" />
</Tile>
<Tile href="/apps/providers/file-upload" title="File Upload" description="Drag-and-drop upload provider">
<img src="/apps/images/app-file-upload.png" />
</Tile>
<Tile href="/apps/providers/approval" title="Approval" description="Human-in-the-loop confirmation">
<img src="/apps/images/app-approval.png" />
</Tile>
<Tile href="/apps/providers/choice" title="Choice" description="Clickable option selection">
<img src="/apps/images/app-choice.png" />
</Tile>
<Tile href="/apps/providers/form" title="Form Input" description="Pydantic model forms">
<img src="/apps/images/app-form.png" />
</Tile>
<Tile href="/apps/generative" title="Generative UI" description="LLM writes the UI at runtime">
<img src="/apps/images/app-showcase.png" />
</Tile>
</Columns>
## Running the examples
Preview any example in your browser with the dev server:
```bash
pip install "fastmcp[apps]"
fastmcp dev apps examples/apps/sales_dashboard/sales_dashboard_server.py
```
The dev UI lets you pick a tool and fill in arguments. In a real deployment the LLM provides those arguments from conversation context — the quiz example especially shines when connected to a host like Goose or Claude Desktop, where the LLM generates the questions itself.
## Standalone apps
### Sales dashboard
A full dashboard with KPI metrics, revenue trends, segment breakdown, and a deal pipeline table. Shows what you can build with a single `app=True` tool and Prefab's chart and data components.
```bash
fastmcp dev apps examples/apps/sales_dashboard/sales_dashboard_server.py
```
### System monitor
Reads live CPU, memory, and disk stats from your machine using `psutil`. Auto-refreshes via `SetInterval` calling a backend tool, with a dropdown to control the refresh rate. The chart accumulates up to 100 data points over time.
```bash
pip install psutil
fastmcp dev apps examples/apps/system_monitor/system_monitor_server.py
```
### Quiz
The LLM generates trivia questions and passes them to the tool. The user answers via buttons, sees correct/incorrect feedback, and tracks score across questions. Demonstrates multi-turn client-side state with FastMCPApp.
```bash
fastmcp dev apps examples/apps/quiz/quiz_server.py
```
### Interactive map
Accepts addresses or place names, geocodes them via OpenStreetMap Nominatim (free, no API key), and renders an interactive Leaflet map using Prefab's `Embed` component with inline HTML. A reminder that Prefab apps can break out of built-in components when they need to.
```bash
fastmcp dev apps examples/apps/map/map_server.py
```
For ready-made building blocks like approvals, choice pickers, file uploads, and Pydantic forms, see the [Providers](/apps/providers/approval) group.

View file

@ -0,0 +1,470 @@
---
title: FastMCPApp
sidebarTitle: FastMCPApp
description: Wire an interactive UI to backend tools with managed visibility and composition safety.
icon: puzzle-piece
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
import PrefabPinWarning from '/snippets/prefab-pin-warning.mdx'
import { PrefabDemoFrame } from '/snippets/prefab-demo-frame.mdx'
<VersionBadge version="3.2.0" />
<PrefabPinWarning />
<PrefabDemoFrame demo="contacts" height="650px" title="Contacts app demo" />
Search a list, fill out a form, click save, the list updates. That pattern — UI that reads and writes data on the server — needs two things: backend tools that actually do the work, and a way to call them from the UI. `FastMCPApp` handles the wiring.
You'll build up to the contacts app above by the end of this page. Let's start with something smaller.
## A minimal interactive app
The smallest interactive app: a form that saves a note, and a list that updates when the user submits.
```python
from prefab_ui.actions import SetState, ShowToast
from prefab_ui.actions.mcp import CallTool
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
Badge, Button, Column, ForEach, Form, Heading,
Input, Row, Separator, Text,
)
from prefab_ui.rx import RESULT
from fastmcp import FastMCP, FastMCPApp
app = FastMCPApp("Notes")
notes_db: list[dict] = []
@app.tool()
def add_note(title: str, body: str) -> list[dict]:
"""Save a note and return all notes."""
notes_db.append({"title": title, "body": body})
return list(notes_db)
@app.ui()
def notes_app() -> PrefabApp:
"""Open the notes app."""
with Column(gap=6, css_class="p-6") as view:
Heading("Notes")
with ForEach("notes") as note:
with Row(gap=2, align="center"):
Text(note.title, css_class="font-semibold")
Badge(note.body)
Separator()
with Form(
on_submit=CallTool(
"add_note",
on_success=[
SetState("notes", RESULT),
ShowToast("Note saved!", variant="success"),
],
on_error=ShowToast("Failed to save", variant="error"),
)
):
Input(name="title", label="Title", required=True)
Input(name="body", label="Body", required=True)
Button("Add Note")
return PrefabApp(view=view, state={"notes": list(notes_db)})
mcp = FastMCP("Notes Server", providers=[app])
```
The model sees one tool: `notes_app`. Calling it opens the UI. When the user submits the form, `CallTool("add_note")` fires, the server saves the note, returns the updated list, and `SetState("notes", RESULT)` writes that list back into state. `ForEach("notes")` re-renders. The model never sees `add_note` — it's UI-only.
## Why not just `@mcp.tool(app=True)`?
A fair question. Any [Interactive Tool](/apps/prefab) can call a server tool — there's nothing stopping you from putting `CallTool("add_note")` inside a regular `@mcp.tool(app=True)`. It works for one or two tools. Things get harder once the app grows:
- Which tools should the model see, and which are UI-only?
- What happens to `CallTool("add_note")` when you mount this server under a namespace and the tool becomes `notes_add_note`?
- How do you keep it all wired correctly as you compose servers?
`FastMCPApp` owns these concerns. Entry points register as model-visible. Backend tools register as UI-only by default. Backend tools get globally stable identifiers that survive namespacing, and `CallTool` accepts function references, so references stay valid when you compose servers.
The rest of this page covers each piece in turn.
## `@app.ui()` — entry points
Entry points are what the model sees. They return a `PrefabApp` and default to `visibility=["model"]`, showing up in the LLM tool list but not callable from within the UI.
```python
@app.ui()
def dashboard() -> PrefabApp:
"""The model calls this to open the dashboard."""
with Column(gap=4, css_class="p-6") as view:
Heading("Dashboard")
...
return PrefabApp(view=view)
```
`@app.ui()` supports the same options as `@mcp.tool`: `name`, `description`, `title`, `tags`, `icons`, `auth`, and `timeout`.
## `@app.tool()` — backend tools
Backend tools do the work. By default they're visible only to the UI (`visibility=["app"]`), not the model.
```python
@app.tool()
def save_contact(name: str, email: str) -> list[dict]:
"""Save a contact and return the updated list."""
db.append({"name": name, "email": email})
return list(db)
```
If you want a tool callable by both the model and the UI, pass `model=True`:
```python
@app.tool(model=True)
def list_contacts() -> list[dict]:
"""Both the model and the UI can call this."""
return list(db)
```
Backend tools support `name`, `description`, `auth`, and `timeout`.
## `CallTool` — UI → backend
`CallTool` is how the UI invokes a backend tool. Pass the tool's name (or a direct function reference):
```python
from prefab_ui.actions.mcp import CallTool
CallTool("save_contact", arguments={"name": "Alice", "email": "alice@example.com"})
# Or a function reference — resolves to a stable global key
CallTool(save_contact, arguments={...})
```
Arguments can reference state with `Rx`:
```python
from prefab_ui.rx import STATE
CallTool("search", arguments={"query": STATE.search_term})
```
### Handling results
Server calls are async. Use `on_success` and `on_error` callbacks:
```python
from prefab_ui.actions import SetState, ShowToast
from prefab_ui.rx import RESULT
CallTool(
"save_contact",
on_success=[
SetState("contacts", RESULT),
ShowToast("Saved!", variant="success"),
],
on_error=ShowToast("Something went wrong", variant="error"),
)
```
`RESULT` is a reactive reference to the tool's return value, available inside `on_success`. `ERROR` (from `prefab_ui.rx`) is the counterpart inside `on_error`. Callbacks can be a single action or a list; they execute in order and short-circuit on error.
### `result_key` shorthand
When a tool's return value should replace a state key, use `result_key`:
```python
CallTool("list_contacts", result_key="contacts")
# same as:
CallTool("list_contacts", on_success=SetState("contacts", RESULT))
```
## Actions
`CallTool` is one of several actions. Actions attach to handlers like `on_click`, `on_submit`, and `on_change`.
Client-side actions run instantly in the browser, no server round-trip:
```python
from prefab_ui.actions import SetState, ToggleState, AppendState, PopState, ShowToast
SetState("count", 42)
ToggleState("expanded")
AppendState("items", {"name": "New Item"})
PopState("items", 0)
ShowToast("Done!", variant="success")
```
Pass a list to chain actions:
```python
Button(
"Reset",
on_click=[
SetState("query", ""),
SetState("results", []),
ShowToast("Cleared"),
],
)
```
### Loading states
A common pattern: disable a button and show a spinner while a call is in flight.
```python
from prefab_ui.rx import Rx
saving = Rx("saving")
Button(
saving.then("Saving...", "Save"),
disabled=saving,
on_click=[
SetState("saving", True),
CallTool(
"save_data",
on_success=[
SetState("saving", False),
SetState("result", RESULT),
ShowToast("Saved!", variant="success"),
],
on_error=[
SetState("saving", False),
ShowToast("Failed", variant="error"),
],
),
],
)
# PrefabApp(view=view, state={"saving": False, ...})
```
## Forms
Forms collect input and submit it to a tool. When submitted, named input values become the tool's arguments.
### Manual forms
```python
from prefab_ui.components import Form, Input, Select, SelectOption, Textarea, Button
with Form(
on_submit=CallTool(
"create_ticket",
on_success=ShowToast("Ticket created!", variant="success"),
)
):
Input(name="title", label="Title", required=True)
with Select(name="priority", label="Priority"):
SelectOption("Low", value="low")
SelectOption("Medium", value="medium")
SelectOption("High", value="high")
Textarea(name="description", label="Description")
Button("Create Ticket")
```
On submit, `CallTool` receives `{"title": ..., "priority": ..., "description": ...}`.
### Forms from Pydantic models
For structured input, `Form.from_model()` generates the whole form — inputs, labels, validation:
```python
from typing import Literal
from pydantic import BaseModel, Field
class BugReport(BaseModel):
title: str = Field(title="Bug Title")
severity: Literal["low", "medium", "high", "critical"] = Field(
title="Severity", default="medium"
)
description: str = Field(title="Description")
@app.ui()
def report_bug() -> PrefabApp:
with Column(gap=4, css_class="p-6") as view:
Heading("Report a Bug")
Form.from_model(
BugReport,
on_submit=CallTool(
"create_bug",
on_success=ShowToast("Bug filed!", variant="success"),
),
)
return PrefabApp(view=view)
@app.tool()
def create_bug(data: BugReport) -> str:
return f"Created: {data.title}"
```
`str` becomes a text input, `Literal` becomes a select, `bool` becomes a checkbox. Field titles and defaults are respected.
## Composition and namespacing
The reason `FastMCPApp` exists — and why you'd pick it over plain `@mcp.tool(app=True)` with string-based `CallTool` — is composition safety.
When you mount a server under a namespace, tool names get prefixed:
```python
platform = FastMCP("Platform")
platform.mount("contacts", contacts_server)
# "save_contact" becomes "contacts_save_contact"
```
`CallTool("save_contact")` would now be broken. But `CallTool(save_contact)` with a function reference resolves to a globally stable identifier that bypasses the namespace. Your app works the same whether standalone or mounted.
### Mounting
`FastMCPApp` is a Provider. Add it to a server with `providers=` or `add_provider`:
```python
mcp = FastMCP("Platform", providers=[app])
# or
mcp = FastMCP("Platform")
mcp.add_provider(app)
```
Multiple apps can coexist; each gets its own global keys, so there's no collision even if two apps have a tool named `save`.
```python
mcp = FastMCP("Platform", providers=[contacts_app, inventory_app, billing_app])
```
### Running standalone
For development, `FastMCPApp` has a `run()` shortcut that wraps itself in a temporary `FastMCP` server:
```python
app = FastMCPApp("Contacts")
# ... register tools ...
if __name__ == "__main__":
app.run()
```
## A full example: contact manager
This brings everything together — entry point, backend tools, Pydantic form, manual form, state, actions, and multi-visibility.
```python expandable
from __future__ import annotations
from typing import Literal
from prefab_ui.actions import SetState, ShowToast
from prefab_ui.actions.mcp import CallTool
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
Badge, Button, Column, ForEach, Form,
Heading, Input, Muted, Row, Separator, Text,
)
from prefab_ui.rx import RESULT, Rx
from pydantic import BaseModel, Field
from fastmcp import FastMCP, FastMCPApp
contacts_db: list[dict] = [
{"name": "Arthur Dent", "email": "arthur@earth.com", "category": "Customer"},
{"name": "Ford Prefect", "email": "ford@betelgeuse.org", "category": "Partner"},
]
class ContactModel(BaseModel):
name: str = Field(title="Full Name", min_length=1)
email: str = Field(title="Email")
category: Literal["Customer", "Vendor", "Partner", "Other"] = "Other"
app = FastMCPApp("Contacts")
@app.tool()
def save_contact(data: ContactModel) -> list[dict]:
"""Save a new contact and return the updated list."""
contacts_db.append(data.model_dump())
return list(contacts_db)
@app.tool()
def search_contacts(query: str) -> list[dict]:
"""Filter contacts by name or email."""
q = query.lower()
return [
c for c in contacts_db
if q in c["name"].lower() or q in c["email"].lower()
]
@app.tool(model=True)
def list_contacts() -> list[dict]:
"""Return all contacts. Visible to both the model and the UI."""
return list(contacts_db)
@app.ui()
def contact_manager() -> PrefabApp:
"""Open the contact manager."""
with Column(gap=6, css_class="p-6") as view:
Heading("Contacts")
with ForEach("contacts") as contact:
with Row(gap=2, align="center"):
Text(contact.name, css_class="font-medium")
Muted(contact.email)
Badge(contact.category)
Separator()
Heading("Add Contact", level=3)
Form.from_model(
ContactModel,
on_submit=CallTool(
"save_contact",
on_success=[
SetState("contacts", RESULT),
ShowToast("Contact saved!", variant="success"),
],
on_error=ShowToast("Failed to save", variant="error"),
),
)
Separator()
Heading("Search", level=3)
with Form(
on_submit=CallTool(
"search_contacts",
arguments={"query": Rx("query")},
on_success=SetState("contacts", RESULT),
)
):
Input(name="query", placeholder="Search by name or email...")
Button("Search")
return PrefabApp(view=view, state={"contacts": list(contacts_db)})
mcp = FastMCP("Contacts Server", providers=[app])
if __name__ == "__main__":
mcp.run()
```
Also available as a runnable server at `examples/apps/contacts/contacts_server.py`.
## Next steps
- **[Interactive Tools](/apps/prefab)** — the building blocks: charts, tables, dashboards, reactive state
- **[Examples](/apps/examples)** — complete working servers
- **[Development](/apps/development)** — preview and test app tools locally
- **[Prefab UI docs](https://prefab.prefect.io)** — full component reference

134
docs/v3/apps/generative.mdx Normal file
View file

@ -0,0 +1,134 @@
---
title: Generative UI
sidebarTitle: Generative UI
description: Let the LLM build custom Prefab UIs on the fly.
icon: wand-magic-sparkles
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
<video src="/apps/images/generative-ui.mp4" autoPlay loop muted playsInline style={{width:"100%", borderRadius:"8px", marginBottom:"1rem"}} />
With Generative UI, the LLM writes the UI code at runtime. Instead of calling a pre-built tool with a fixed shape, the model writes Prefab Python tailored to the current data and request. The user watches the UI stream in as the model generates it.
```python
from fastmcp import FastMCP
from fastmcp.apps.generative import GenerativeUI
mcp = FastMCP("Prefab Studio")
mcp.add_provider(GenerativeUI())
```
One provider registers three things:
- **`generate_prefab_ui`** — a tool that accepts Python code, executes it in a Pyodide sandbox, and renders the result as a Prefab app
- **`search_prefab_components`** — a tool the LLM uses to discover what components are available
- **The streaming renderer** — a `ui://` resource with browser-side Pyodide that progressively renders partial code as the LLM generates it
## How it works
When the LLM calls `generate_prefab_ui`, it writes Prefab Python code into the `code` argument. The MCP Apps protocol creates the renderer iframe in parallel with the tool call, so the app is already running by the time partial arguments start flowing.
As the LLM generates each token:
1. The host forwards partial arguments to the app via `ontoolinputpartial`
2. The renderer extracts the growing `code` string
3. Browser-side Pyodide executes whatever compiles successfully
4. The user sees components appear as they're written
When the LLM finishes, the server runs the complete code in a server-side Pyodide sandbox for validation, and the renderer swaps the streaming preview for the final server-validated result.
## What the LLM writes
The tool description includes examples that teach the model the Prefab patterns. A typical generation looks like:
```python
from prefab_ui.components import Column, Row, Heading, Text, Badge, Card, CardContent
from prefab_ui.components.charts import BarChart, ChartSeries
from prefab_ui.app import PrefabApp
with PrefabApp() as app:
with Column(gap=6, css_class="p-6"):
Heading("Q3 Revenue Report")
BarChart(
data=[
{"month": "Jul", "revenue": 42000},
{"month": "Aug", "revenue": 51000},
{"month": "Sep", "revenue": 63000},
],
series=[ChartSeries(data_key="revenue", label="Revenue")],
x_axis="month",
)
with Row(gap=4):
with Card():
with CardContent():
Text("Total", css_class="text-sm text-muted-foreground")
Heading("$156,000")
with Card():
with CardContent():
Text("Growth", css_class="text-sm text-muted-foreground")
Badge("+18%", variant="success")
```
The model writes real Python — loops, f-strings, computation, helper functions. Prefab gives it charts, tables, forms, cards, badges, and layout primitives to compose.
## The component search tool
Before writing code, the LLM can call `search_prefab_components` to discover what's available:
```
search_prefab_components("Chart")
→ 7 components matching 'Chart':
AreaChart — from prefab_ui.components.charts import AreaChart
BarChart — from prefab_ui.components.charts import BarChart
...
```
Passing `detail=True` returns full field descriptions and docstrings. The search tool introspects Prefab classes at runtime, so it's always up to date with the installed version.
## Passing data
The `generate_prefab_ui` tool accepts a `data` parameter. Values become global variables in the sandbox:
```python
# The LLM can reference 'sales_data' directly in its code
result = await generate_prefab_ui(
code="...",
data={"sales_data": [{"month": "Jan", "revenue": 42000}, ...]}
)
```
This lets the model use data from earlier in the conversation to build visualizations.
## Configuration
`GenerativeUI` takes options for customizing tool names:
```python
GenerativeUI(
tool_name="generate_prefab_ui", # default
components_tool_name="search_prefab_components", # default
include_components_tool=True, # default
)
```
## Requirements
Generative UI needs `fastmcp[apps]`, which pulls in `prefab-ui`. The server-side Pyodide sandbox (for final validation) requires Deno — it installs automatically on first use.
The streaming renderer loads Pyodide from CDN in the browser. The CSP is configured automatically by the provider — no manual setup.
## Sandbox limitations
The Pyodide sandbox includes the Python standard library and Prefab. External packages (NumPy, pandas, requests, etc.) are **not available** — the LLM's code must work with only built-in Python and Prefab. If the LLM imports something unavailable, the sandbox raises `ImportError`.
## Next steps
- **[Interactive Tools](/apps/prefab)** — the component building blocks the LLM will use
- **[Prefab component reference](https://prefab.prefect.io/docs/components)** — full component library
- **[Development](/apps/development)** — preview generative tools locally with `fastmcp dev apps`

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 KiB

View file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,001 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

304
docs/v3/apps/low-level.mdx Normal file
View file

@ -0,0 +1,304 @@
---
title: Custom HTML Apps
sidebarTitle: Custom HTML
description: Build apps with your own HTML, CSS, and JavaScript using the MCP Apps extension directly.
icon: code
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
Everything on this page is for when you want full control: your own HTML, your own JavaScript framework, a map library, a 3D viewer, custom video playback. [Interactive Tools](/apps/prefab) wrap the MCP Apps extension so you never have to think about it — this page is what you reach for when you need to think about it.
You'll be working with two things: the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) JavaScript SDK for host communication, and FastMCP's `AppConfig` for resources and CSP.
## How it works
An MCP App has two parts:
1. A **tool** that does the work and returns data
2. A **`ui://` resource** containing the HTML that renders that data
The tool declares which resource to use via `AppConfig`. When the host calls the tool, it also fetches the linked resource, renders it in a sandboxed iframe, and pushes the tool result into the app via `postMessage`. The app can also call tools back, enabling interactive workflows.
```python
import json
from fastmcp import FastMCP
from fastmcp.apps import AppConfig, ResourceCSP
mcp = FastMCP("My App Server")
# The tool does the work
@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))
def generate_chart(data: list[float]) -> str:
return json.dumps({"values": data})
# The resource provides the UI
@mcp.resource("ui://my-app/view.html")
def chart_view() -> str:
return "<html>...</html>"
```
## AppConfig
`AppConfig` controls how a tool or resource participates in the Apps extension. Import it from `fastmcp.server.apps`:
```python
from fastmcp.apps import AppConfig
```
On **tools**, you'll typically set `resource_uri` to point to the UI resource:
```python
@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))
def my_tool() -> str:
return "result"
```
You can also pass a raw dict with camelCase keys, matching the wire format:
```python
@mcp.tool(app={"resourceUri": "ui://my-app/view.html"})
def my_tool() -> str:
return "result"
```
### Tool visibility
The `visibility` field controls where a tool appears:
- `["model"]` — visible to the LLM (the default behavior)
- `["app"]` — only callable from within the app UI, hidden from the LLM
- `["model", "app"]` — both
This is useful when you have tools that only make sense as part of the app's interactive flow, not as standalone LLM actions.
```python
@mcp.tool(
app=AppConfig(
resource_uri="ui://my-app/view.html",
visibility=["app"],
)
)
def refresh_data() -> str:
"""Only callable from the app UI, not by the LLM."""
return fetch_latest()
```
### AppConfig fields
| Field | Type | Description |
|-------|------|-------------|
| `resource_uri` | `str` | URI of the UI resource. Tools only. |
| `visibility` | `list[str]` | Where the tool appears: `"model"`, `"app"`, or both. Tools only. |
| `csp` | `ResourceCSP` | Content Security Policy for the iframe. |
| `permissions` | `ResourcePermissions` | Iframe sandbox permissions. |
| `domain` | `str` | Stable sandbox origin for the iframe. |
| `prefers_border` | `bool` | Whether the UI prefers a visible border. |
<Note>
On **resources**, `resource_uri` and `visibility` must not be set — the resource *is* the UI. Use `AppConfig` on resources only for `csp`, `permissions`, and other display settings.
</Note>
## UI resources
Resources using the `ui://` scheme are automatically served with the MIME type `text/html;profile=mcp-app`. No need to set it manually.
```python
@mcp.resource("ui://my-app/view.html")
def my_view() -> str:
return "<html>...</html>"
```
The HTML can be anything — a full single-page app, a simple display, or a complex interactive tool. The host renders it in a sandboxed iframe and establishes a `postMessage` channel for communication.
### Writing the app HTML
Your HTML app communicates with the host using the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) JavaScript SDK. The simplest approach is to load it from a CDN:
```html
<script type="module">
import { App } from "https://unpkg.com/@modelcontextprotocol/ext-apps@0.4.0/app-with-deps";
const app = new App({ name: "My App", version: "1.0.0" });
// Receive tool results pushed by the host
app.ontoolresult = ({ content }) => {
const text = content?.find(c => c.type === 'text');
if (text) {
document.getElementById('output').textContent = text.text;
}
};
// Connect to the host
await app.connect();
</script>
```
The `App` object provides:
- **`app.ontoolresult`** — callback that receives tool results pushed by the host
- **`app.callServerTool({name, arguments})`** — call a tool on the server from within the app
- **`app.onhostcontextchanged`** — callback for host context changes (e.g., safe area insets)
- **`app.getHostContext()`** — get current host context
See the full [ext-apps SDK documentation](https://github.com/modelcontextprotocol/ext-apps) for the complete API reference.
<Note>
If your HTML loads external scripts, styles, or makes API calls, you need to declare those domains in the CSP configuration. See [Security](#security) below.
</Note>
## Security
Apps run in sandboxed iframes with a deny-by-default Content Security Policy. By default, only inline scripts and styles are allowed — no external network access.
### Content Security Policy
If your app needs to load external resources (CDN scripts, API calls, embedded iframes), declare the allowed domains with `ResourceCSP`:
```python
from fastmcp.apps import AppConfig, ResourceCSP
@mcp.resource(
"ui://my-app/view.html",
app=AppConfig(
csp=ResourceCSP(
resource_domains=["https://unpkg.com", "https://cdn.example.com"],
connect_domains=["https://api.example.com"],
)
),
)
def my_view() -> str:
return "<html>...</html>"
```
| CSP Field | Controls |
|-----------|----------|
| `connect_domains` | `fetch`, XHR, WebSocket (`connect-src`) |
| `resource_domains` | Scripts, images, styles, fonts (`script-src`, etc.) |
| `frame_domains` | Nested iframes (`frame-src`) |
| `base_uri_domains` | Document base URI (`base-uri`) |
### Permissions
If your app needs browser capabilities like camera or clipboard access, request them via `ResourcePermissions`:
```python
from fastmcp.apps import AppConfig, ResourcePermissions
@mcp.resource(
"ui://my-app/view.html",
app=AppConfig(
permissions=ResourcePermissions(
camera={},
clipboard_write={},
)
),
)
def my_view() -> str:
return "<html>...</html>"
```
Hosts may or may not grant these permissions. Your app should use JavaScript feature detection as a fallback.
## Example: a QR code server
This example creates a tool that generates QR codes and an app that renders them as images. It's based on the [official MCP Apps example](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/qr-server). Requires the `qrcode[pil]` package.
```python expandable
import base64
import io
import qrcode
from mcp import types
from fastmcp import FastMCP
from fastmcp.apps import AppConfig, ResourceCSP
from fastmcp.tools import ToolResult
mcp = FastMCP("QR Code Server")
VIEW_URI = "ui://qr-server/view.html"
@mcp.tool(app=AppConfig(resource_uri=VIEW_URI))
def generate_qr(text: str = "https://gofastmcp.com") -> ToolResult:
"""Generate a QR code from text."""
qr = qrcode.QRCode(version=1, box_size=10, border=4)
qr.add_data(text)
qr.make(fit=True)
img = qr.make_image()
buffer = io.BytesIO()
img.save(buffer, format="PNG")
b64 = base64.b64encode(buffer.getvalue()).decode()
return ToolResult(
content=[types.ImageContent(type="image", data=b64, mimeType="image/png")]
)
@mcp.resource(
VIEW_URI,
app=AppConfig(csp=ResourceCSP(resource_domains=["https://unpkg.com"])),
)
def view() -> str:
"""Interactive QR code viewer."""
return """\
<!DOCTYPE html>
<html>
<head>
<meta name="color-scheme" content="light dark">
<style>
body { display: flex; justify-content: center;
align-items: center; height: 340px; width: 340px;
margin: 0; background: transparent; }
img { width: 300px; height: 300px; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
</style>
</head>
<body>
<div id="qr"></div>
<script type="module">
import { App } from
"https://unpkg.com/@modelcontextprotocol/ext-apps@0.4.0/app-with-deps";
const app = new App({ name: "QR View", version: "1.0.0" });
app.ontoolresult = ({ content }) => {
const img = content?.find(c => c.type === 'image');
if (img) {
const el = document.createElement('img');
el.src = `data:${img.mimeType};base64,${img.data}`;
el.alt = "QR Code";
document.getElementById('qr').replaceChildren(el);
}
};
await app.connect();
</script>
</body>
</html>"""
```
The tool generates a QR code as a base64 PNG. The resource loads the MCP Apps JS SDK from unpkg (declared in the CSP), listens for tool results, and renders the image. The host wires them together — when the LLM calls `generate_qr`, the QR code appears in an interactive frame inside the conversation.
## Checking client support
Not all hosts support the Apps extension. You can check at runtime using the tool's [context](/servers/context):
```python
from fastmcp import Context
from fastmcp.apps import AppConfig, UI_EXTENSION_ID
@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))
async def my_tool(ctx: Context) -> str:
if ctx.client_supports_extension(UI_EXTENSION_ID):
# Return data optimized for UI rendering
return rich_response()
else:
# Fall back to plain text
return plain_text_response()
```

73
docs/v3/apps/overview.mdx Normal file
View file

@ -0,0 +1,73 @@
---
title: Apps
sidebarTitle: Overview
description: Give your tools interactive UIs rendered directly in the conversation.
icon: grid-2
mode: center
---
import { VersionBadge } from '/snippets/version-badge.mdx'
import PrefabPinWarning from '/snippets/prefab-pin-warning.mdx'
import { PrefabDemoFrame } from '/snippets/prefab-demo-frame.mdx'
<VersionBadge version="3.0.0" />
A FastMCP app is a tool that returns an interactive UI instead of text. When the host calls it, the user sees a chart, a table, a form, or a whole dashboard rendered right inside the conversation, with working sort, search, tooltips, and state.
<div style={{
margin: '0 clamp(-180px, calc(-18vw + 90px), 0px) 2rem',
maxHeight: '700px',
overflow: 'hidden',
position: 'relative',
maskImage: 'linear-gradient(to bottom, black 75%, transparent)',
WebkitMaskImage: 'linear-gradient(to bottom, black 75%, transparent)',
}}>
<PrefabDemoFrame demo="hitchhikers" height="2000px" title="Prefab showcase demo" />
</div>
The dashboard above is a [Prefab](https://prefab.prefect.io) showcase — a taste of what you can deliver from a FastMCP tool. Every card, chart, slider, dialog, and carousel is a Python component. Build a composition like this, add `@mcp.tool(app=True)`, and the host renders it inside the conversation.
Under the hood, FastMCP builds on the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) and uses Prefab to describe UIs in Python.
```bash
pip install "fastmcp[apps]"
```
<PrefabPinWarning />
## Pick your path
Four patterns cover almost everything you'd want to build. Most apps start with Interactive Tools; you only reach for the others when you've hit a specific limit.
### [Interactive Tools](/apps/prefab) — start here
Add `app=True` to a tool and return a Prefab component. Charts, tables, dashboards, and client-side interactivity (toggles, tabs, filtering) all work without any server round-trips.
```python
@mcp.tool(app=True)
def team_directory() -> DataTable:
return DataTable(columns=[...], rows=employees, search=True)
```
### [FastMCPApp](/apps/fastmcp-app) — when the UI calls back to the server
Forms that save data, buttons that trigger backend work, search that hits a database. `FastMCPApp` manages the wiring between UI actions and backend tools, with stable tool identifiers that survive server composition.
### [Generative UI](/apps/generative) — when the LLM writes the UI
Register one provider and the model can write Prefab code tailored to the current data and request. The user watches the UI build up as the model generates it.
```python
mcp.add_provider(GenerativeUI())
```
### [Custom HTML](/apps/low-level) — when you need full control
Write your own HTML, CSS, and JavaScript. Use a specific framework, drop in a map or 3D viewer, embed video. You're talking to the MCP Apps protocol directly.
## What's next
- **[Quickstart](/apps/quickstart)** — build a working app in a minute
- **[Examples](/apps/examples)** — complete working servers you can run today
- **[Providers](/apps/providers/approval)** — ready-made capabilities (approvals, choice pickers, file upload, forms) you add with one line
- **[Development](/apps/development)** — preview app tools locally with `fastmcp dev apps`

297
docs/v3/apps/prefab.mdx Normal file
View file

@ -0,0 +1,297 @@
---
title: Interactive Tools
sidebarTitle: Interactive Tools
description: Turn your tools into interactive UIs with charts, tables, and dashboards.
icon: palette
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
import PrefabPinWarning from '/snippets/prefab-pin-warning.mdx'
import { PrefabDemoFrame } from '/snippets/prefab-demo-frame.mdx'
<VersionBadge version="3.1.0" />
<PrefabPinWarning />
<PrefabDemoFrame demo="dashboard" height="680px" title="Sales dashboard demo" />
Believe it or not, that dashboard is a FastMCP tool. The chart has tooltips. The table is sortable. The badges are styled by deal stage. The whole thing is about 40 lines of Python, and the user sees it right inside their conversation instead of a wall of JSON.
The pattern behind every example on this page is the same: add `app=True` to your tool, build a UI with [Prefab](https://prefab.prefect.io) components, and return it as a `PrefabApp`. Prefab has [100+ components](https://prefab.prefect.io/docs/components), from data tables and charts to forms and progress bars. You compose them in Python; the host renders them as a live, interactive application.
## Start with a table
Most tools return data the user wants to explore. A `DataTable` is often the smallest useful upgrade — your data goes from a JSON blob to a searchable, sortable table:
<PrefabDemoFrame demo="data-table" height="530px" title="Data table demo" />
```python
from prefab_ui.components import DataTable, DataTableColumn
from fastmcp import FastMCP
mcp = FastMCP("Directory")
@mcp.tool(app=True)
def team_directory() -> DataTable:
"""Browse the team directory."""
employees = [
{"name": "Alice Chen", "role": "Staff Engineer", "dept": "Platform"},
{"name": "Bob Martinez", "role": "Lead Designer", "dept": "Design"},
{"name": "Carol Johnson", "role": "Senior Engineer", "dept": "Platform"},
{"name": "David Kim", "role": "Product Manager", "dept": "Product"},
{"name": "Eva Mueller", "role": "Engineer", "dept": "Platform"},
{"name": "Frank Lee", "role": "Data Scientist", "dept": "ML"},
{"name": "Grace Park", "role": "Eng Manager", "dept": "Platform"},
]
return DataTable(
columns=[
DataTableColumn(key="name", header="Name", sortable=True),
DataTableColumn(key="role", header="Role", sortable=True),
DataTableColumn(key="dept", header="Dept", sortable=True),
],
rows=employees,
search=True,
)
```
That's it. Add `app=True`, return a Prefab component instead of raw dicts. FastMCP handles the rendering, sandboxing, and security. No wrapper class needed for simple cases like this.
## Add charts
When numbers tell a better story as a visual, swap in a chart. The API is the same: pass your data as a list of dicts, tell the chart which keys to plot.
<PrefabDemoFrame demo="bar-chart" height="430px" title="Bar chart demo" />
```python
@mcp.tool(app=True)
def quarterly_revenue(year: int) -> BarChart:
"""Show quarterly revenue as a bar chart."""
data = [
{"quarter": "Q1", "revenue": 42000, "costs": 28000},
{"quarter": "Q2", "revenue": 51000, "costs": 31000},
{"quarter": "Q3", "revenue": 47000, "costs": 29000},
{"quarter": "Q4", "revenue": 63000, "costs": 35000},
]
return BarChart(
data=data,
series=[
ChartSeries(data_key="revenue", label="Revenue"),
ChartSeries(data_key="costs", label="Costs"),
],
x_axis="quarter",
show_legend=True,
)
```
Each `ChartSeries` plots a different key from the data. `BarChart`, `LineChart`, `AreaChart`, `PieChart`, `RadarChart`, and `RadialChart` all follow the same pattern. Hover over the bars to see tooltips.
<PrefabDemoFrame demo="pie-chart" height="410px" title="Pie chart demo" />
```python
@mcp.tool(app=True)
def ticket_breakdown() -> PieChart:
"""Show open tickets by category."""
data = [
{"category": "Bug", "count": 42},
{"category": "Feature", "count": 28},
{"category": "Docs", "count": 15},
{"category": "Infra", "count": 10},
]
return PieChart(
data=data,
data_key="count",
name_key="category",
inner_radius=50,
show_legend=True,
)
```
See the [Prefab chart docs](https://prefab.prefect.io/docs/components) for stacking, curves, custom colors, and more.
## Compose a dashboard
Tables and charts are useful on their own, but the real power comes from composing them. `Column` stacks children vertically, `Row` lays them out side by side, and `with` blocks establish nesting — the indentation is the layout.
<PrefabDemoFrame demo="dashboard" height="680px" title="Sales dashboard demo" />
```python expandable
@mcp.tool(app=True)
def sales_dashboard() -> PrefabApp:
"""Show sales KPIs, trends, and deals."""
monthly = [
{"month": "Jan", "revenue": 48200, "costs": 31000},
{"month": "Feb", "revenue": 52100, "costs": 32500},
{"month": "Mar", "revenue": 61800, "costs": 34200},
{"month": "Apr", "revenue": 58400, "costs": 33800},
]
deals = [
{"account": "Acme Corp", "value": "$84,000", "stage": "Won"},
{"account": "Globex Inc", "value": "$52,000", "stage": "Negotiation"},
{"account": "Initech", "value": "$31,500", "stage": "Proposal"},
{"account": "Wayne Enterprises", "value": "$45,000", "stage": "Lost"},
]
rows = [
{
"account": d["account"],
"value": d["value"],
"stage": Badge(
d["stage"],
variant="success" if d["stage"] == "Won"
else "destructive" if d["stage"] == "Lost"
else "secondary",
),
}
for d in deals
]
total = sum(m["revenue"] for m in monthly)
with PrefabApp() as app:
with Column(gap=4, css_class="p-6"):
with Row(gap=6):
Metric(label="Revenue (Q1-Q4)", value=f"${total:,}")
Metric(label="Deals", value=f"{len(deals)}")
BarChart(
data=monthly,
series=[
ChartSeries(data_key="revenue", label="Revenue"),
ChartSeries(data_key="costs", label="Costs"),
],
x_axis="month",
show_legend=True,
)
Separator()
DataTable(
columns=[
DataTableColumn(key="account", header="Account", sortable=True),
DataTableColumn(key="value", header="Value", sortable=True),
DataTableColumn(key="stage", header="Stage"),
],
rows=rows,
)
return app
```
Notice how `Badge` components can be placed inside table cells — any Prefab component works as a cell value, so you can put progress bars, icons, or buttons in your tables too.
## Make it reactive
Everything above renders once from the data your Python provides. But interactive tools can also respond to user input in real time, without any server round-trips. Prefab's state system lets components read and write client-side values, so the UI updates instantly as the user interacts with it.
<PrefabDemoFrame demo="reactive" height="500px" title="Reactive sales demo" />
Try switching regions in the dropdown, and toggling the switch on and off.
```python expandable
from prefab_ui.rx import Rx
@mcp.tool(app=True)
def regional_sales() -> PrefabApp:
"""Sales by region with a live filter."""
north = [
{"month": "Jan", "sales": 22000},
{"month": "Feb", "sales": 25500},
{"month": "Mar", "sales": 24200},
]
south = [
{"month": "Jan", "sales": 5800},
{"month": "Feb", "sales": 6400},
{"month": "Mar", "sales": 5600},
]
west = [
{"month": "Jan", "sales": 6000},
{"month": "Feb", "sales": 6000},
{"month": "Mar", "sales": 5600},
]
with PrefabApp(
state={
"region": "north",
"north": north, "south": south, "west": west,
"show_target": True,
},
) as app:
with Column(
gap=4,
css_class="p-6",
let={"data": "{{ region == 'south' ? south"
" : region == 'west' ? west"
" : north }}"},
):
with Row(gap=4, align="center"):
with Select(name="region", css_class="w-40"):
SelectOption(value="north", label="North")
SelectOption(value="south", label="South")
SelectOption(value="west", label="West")
Switch(name="show_target", css_class="ml-auto")
Text("Show target", css_class="text-sm text-muted-foreground")
BarChart(
data=Rx("data"),
series=[ChartSeries(data_key="sales", label="Sales")],
x_axis="month",
)
with If(Rx("show_target")):
Metric(label="Q1 Target", value="$75,000")
return app
```
The `state` dict on `PrefabApp` declares initial values. The `Select` writes to the `region` key on every change. A `let` binding picks the matching dataset, and the chart re-renders. The `Switch` toggles a `Metric` on and off through `If(Rx("show_target"))`. All of this happens in the browser — no calls back to your server.
`Rx` is a reactive reference: `Rx("region")` compiles to an expression the renderer evaluates live. It supports arithmetic, comparisons, formatting pipes (`.currency()`, `.percent()`), and ternary conditionals (`.then()`). For the full state system, see the [Prefab state docs](https://prefab.prefect.io/docs/concepts/state) and [expression docs](https://prefab.prefect.io/docs/concepts/expressions).
## Content Security Policy
Interactive tools render in a sandboxed iframe with a strict CSP. If your tool loads external resources — embedding iframes, fetching from APIs, loading scripts — add the required domains:
```python
from fastmcp.apps import PrefabAppConfig, ResourceCSP
@mcp.tool(app=PrefabAppConfig(
csp=ResourceCSP(frame_domains=["https://example.com"]),
))
def dashboard_with_embed() -> PrefabApp:
...
```
`PrefabAppConfig()` with no arguments is equivalent to `app=True`.
## Giving the LLM context
By default, the LLM sees `"[Rendered Prefab UI]"` as the tool result. If the model needs to reason about the data, return a `ToolResult` with a text summary alongside the UI:
```python
from fastmcp.tools import ToolResult
@mcp.tool(app=True)
def sales_overview(year: int) -> ToolResult:
"""Show sales visually, summarize for the model."""
data = get_sales_data(year)
total = sum(row["revenue"] for row in data)
with Column(gap=4, css_class="p-6") as view:
BarChart(data=data, series=[ChartSeries(data_key="revenue")])
return ToolResult(
content=f"Total revenue for {year}: ${total:,} across {len(data)} quarters",
structured_content=view,
)
```
The user sees the chart. The model sees the summary.
## Next steps
- **[FastMCPApp](/apps/fastmcp-app)** — when your UI needs to call backend tools (forms, search, CRUD)
- **[Generative UI](/apps/generative)** — let the LLM design the UI at runtime
- **[Custom HTML](/apps/low-level)** — when Prefab isn't enough (maps, 3D, your own framework)
- **[Examples](/apps/examples)** — complete working servers you can run today
- **[Development](/apps/development)** — preview your tools locally with `fastmcp dev apps`
- **[Prefab UI](https://prefab.prefect.io)** — full component reference with 100+ components, theming, and advanced patterns

View file

@ -0,0 +1,80 @@
---
title: Approval
sidebarTitle: Approval
description: Human-in-the-loop approval gates for agent actions
icon: shield-check
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
`Approval` adds a human-in-the-loop confirmation step to any server. The LLM presents what it's about to do, the user approves or rejects via buttons, and the decision flows back into the conversation as a message.
<Frame>
<img src="/apps/images/app-approval.png" alt="The Approval provider shown in Goose, with a payment confirmation card and Approve/Cancel buttons" />
</Frame>
```python
from fastmcp import FastMCP
from fastmcp.apps.approval import Approval
mcp = FastMCP("My Server")
mcp.add_provider(Approval())
```
This registers a single tool:
| Tool | Visibility | Purpose |
|------|-----------|---------|
| `request_approval` | Model | Shows an approval card, sends the user's decision back as a message |
The LLM calls `request_approval` with a summary (and optional details) whenever it's about to take a significant action. The user sees a card with Approve and Reject buttons. Clicking either sends a message back into the conversation via `SendMessage`, which triggers the LLM's next turn.
The message looks like it came from the user:
```
"Deploy v3.2 to production" — I selected: Approve
```
<Note>
Approval is an advisory gate, not an enforcement mechanism. The conversation isn't blocked while the card is open — the user can keep typing, and a determined LLM could proceed without waiting. Think of it as a strong UX signal that encourages confirmation, not a security boundary. For hard enforcement, implement approval logic server-side in your tool implementations.
</Note>
## Configuration
The constructor sets defaults; the LLM can override all of these per-call via tool arguments.
```python
Approval(
name="Approval", # App name
title="Approval Required", # Card heading
approve_text="Approve", # Approve button label
reject_text="Reject", # Reject button label
approve_variant="default", # "default", "destructive", "success", "info"
reject_variant="outline", # same options plus "outline"
)
```
The LLM can customize each invocation:
```python
request_approval(
summary="Delete 47 files from /tmp",
details="This cannot be undone.",
title="Destructive Action",
approve_text="Delete",
approve_variant="destructive",
reject_text="Keep files",
)
```
## How it works
When the user clicks a button, two things happen:
1. `SendMessage` pushes the decision into the conversation as a user message
2. `SetState("decided", True)` replaces the buttons with "Response sent."
The tool description instructs the LLM to stop and wait for the "I selected:" message before proceeding. If approved, it continues. If rejected, it acknowledges and asks how to proceed.

View file

@ -0,0 +1,72 @@
---
title: Choice
sidebarTitle: Choice
description: Present clickable options instead of free-text responses
icon: list-check
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
`Choice` lets the LLM present a set of options as clickable buttons instead of asking the user to type a response. The selection flows back into the conversation as a message, giving the LLM clean structured input.
<Frame>
<img src="/apps/images/app-choice.png" alt="The Choice provider shown in Goose, with four lunch options as clickable buttons" />
</Frame>
```python
from fastmcp import FastMCP
from fastmcp.apps.choice import Choice
mcp = FastMCP("My Server")
mcp.add_provider(Choice())
```
This registers a single tool:
| Tool | Visibility | Purpose |
|------|-----------|---------|
| `choose` | Model | Shows a card with clickable options, sends the selection back as a message |
The LLM calls `choose` with a prompt and a list of options. The user sees a card with one button per option. Clicking one sends a message back into the conversation:
```
"Which deployment strategy?" — I selected: Blue-green
```
<Note>
This is an advisory interaction, not an enforcement mechanism. The conversation isn't blocked while the card is open — the user can keep typing, and the LLM could proceed without waiting. The tool description instructs the LLM to stop and wait for the "I selected:" response, but for hard enforcement, implement selection logic server-side.
</Note>
## Configuration
The constructor sets defaults; the LLM can override `title` per-call.
```python
Choice(
name="Choice", # App name
title="Choose an Option", # Default card heading
variant="outline", # Button style for all options
)
```
The LLM provides the options per-call:
```python
choose(
prompt="What should we have for lunch?",
options=["Pizza", "Tacos", "Ramen", "Salad"],
title="The Important Questions",
)
```
## How it works
Each option renders as a full-width button in a vertical stack. When the user clicks one:
1. `SendMessage` pushes the selection into the conversation as a user message
2. `SetState("decided", True)` replaces the buttons with "Response sent."
The tool description instructs the LLM to stop and wait for the "I selected:" message before proceeding with whatever the user chose.

View file

@ -0,0 +1,129 @@
---
title: File Upload
sidebarTitle: File Upload
description: Drag-and-drop file upload for any MCP server
icon: upload
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
`FileUpload` adds drag-and-drop file upload to any server. Users upload files through an interactive UI, bypassing the LLM context window entirely. The LLM can then list and read uploaded files through model-visible tools.
<Frame>
<img src="/apps/images/app-file-upload.png" alt="The FileUpload provider shown in Goose, with a drag-and-drop zone for uploading files" />
</Frame>
```python
from fastmcp import FastMCP
from fastmcp.apps.file_upload import FileUpload
mcp = FastMCP("My Server")
mcp.add_provider(FileUpload())
```
This registers four tools:
| Tool | Visibility | Purpose |
|------|-----------|---------|
| `file_manager` | Model | Opens the drag-and-drop upload UI |
| `store_files` | App only | Called by the UI when the user clicks Upload |
| `list_files` | Model | Returns metadata for all uploaded files |
| `read_file` | Model | Returns a file's contents by name |
The LLM sees `file_manager`, `list_files`, and `read_file`. It calls `file_manager` to show the upload interface, then uses `list_files` and `read_file` to work with whatever the user uploaded. `store_files` is app-only — the UI calls it directly and the LLM never needs to know about it.
## Configuration
```python
FileUpload(
name="Files", # App name (used in tool routing)
max_file_size=10 * 1024 * 1024, # 10 MB default, enforced server-side
title="File Upload", # Heading shown in the UI
description="Drop files to...", # Description text below the heading
drop_label="Drop files here", # Label inside the drop zone
)
```
The `max_file_size` limit is enforced both in the UI (the DropZone rejects oversized files) and on the server (the `store_files` tool validates before calling `on_store`).
## Storage scoping
By default, files are stored in memory and scoped by MCP session ID. Each session gets its own isolated file store — files uploaded in one conversation aren't visible in another.
This works with **stdio**, **SSE**, and **stateful HTTP** transports, where sessions persist across requests.
<Warning>
In **stateless HTTP** mode, each request creates a new session object with a new ID. Files stored during one request (e.g. the UI upload) will be invisible to the next request (e.g. the LLM calling `list_files`). You **must** override `_get_scope_key` to use a stable identifier like a user ID from your auth token.
</Warning>
For stateless deployments, override `_get_scope_key` to return a stable identifier. For example, to scope files by authenticated user:
```python
from fastmcp.apps.file_upload import FileUpload
class UserScopedUpload(FileUpload):
def _get_scope_key(self, ctx):
return ctx.access_token["sub"]
```
For process-wide shared storage (all users see all files):
```python
class SharedUpload(FileUpload):
def _get_scope_key(self, ctx):
return "__shared__"
```
## Custom storage
The default implementation stores files in memory for the lifetime of the server process. For persistent storage, subclass `FileUpload` and override three methods. Each receives the current `Context`, giving you access to session IDs, auth tokens, and request metadata for partitioning and authorization.
```python
import base64
from fastmcp.apps.file_upload import FileUpload
class S3Upload(FileUpload):
def on_store(self, files, ctx):
user_id = ctx.access_token["sub"]
for f in files:
s3.put_object(
Bucket="uploads",
Key=f"{user_id}/{f['name']}",
Body=base64.b64decode(f["data"]),
)
return self.on_list(ctx)
def on_list(self, ctx):
user_id = ctx.access_token["sub"]
objects = s3.list_objects(Bucket="uploads", Prefix=f"{user_id}/")
return [
{
"name": obj["Key"].split("/", 1)[1],
"type": "application/octet-stream",
"size": obj["Size"],
"size_display": f"{obj['Size']} B",
"uploaded_at": obj["LastModified"].isoformat(),
}
for obj in objects.get("Contents", [])
]
def on_read(self, name, ctx):
user_id = ctx.access_token["sub"]
obj = s3.get_object(Bucket="uploads", Key=f"{user_id}/{name}")
content = obj["Body"].read()
return {
"name": name,
"size": obj["ContentLength"],
"type": obj["ContentType"],
"uploaded_at": obj["LastModified"].isoformat(),
"content": content.decode("utf-8"),
}
```
Each file dict passed to `on_store` contains `name`, `size`, `type`, and `data` (base64-encoded content). The return value from `on_store` and `on_list` should be a list of summary dicts with `name`, `type`, `size`, `size_display`, and `uploaded_at` fields — these populate the file list in the UI.
`on_read` returns a dict with file metadata and either `content` (decoded text) or `content_base64` (a base64 preview for binary files).

View file

@ -0,0 +1,105 @@
---
title: Form Input
sidebarTitle: Form Input
description: Collect structured data from users via Pydantic models
icon: rectangle-list
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
`FormInput` generates a validated form from a Pydantic model. The user fills it out, and the submission is validated against the model before being returned. Structured elicitation that can't be hallucinated.
<Frame>
<img src="/apps/images/app-form.png" alt="The FormInput provider shown in Goose, with a bug report form" />
</Frame>
```python
from typing import Literal
from pydantic import BaseModel, Field
from fastmcp import FastMCP
from fastmcp.apps.form import FormInput
class BugReport(BaseModel):
title: str = Field(description="Brief summary")
severity: Literal["low", "medium", "high", "critical"]
description: str = Field(
description="Detailed description",
json_schema_extra={"ui": {"type": "textarea"}},
)
mcp = FastMCP("My Server")
mcp.add_provider(FormInput(model=BugReport))
```
This registers two tools:
| Tool | Visibility | Purpose |
|------|-----------|---------|
| `collect_bugreport` | Model | Opens the form UI |
| `submit_form` | App only | Validates and processes the submission |
The tool name is derived from the model class name, lowercased: `collect_{modelname}`. So `BugReport` becomes `collect_bugreport`, `ShippingAddress` becomes `collect_shippingaddress`. Use `tool_name` to override if needed. The LLM calls it with a prompt explaining what it needs, and the user gets a form with fields matching the model.
## Field mapping
`FormInput` uses Prefab's `Form.from_model()`, which maps Pydantic types to form components:
| Python type | Form component |
|------------|---------------|
| `str` | Text input |
| `int`, `float` | Number input |
| `bool` | Checkbox |
| `datetime.date` | Date picker |
| `Literal[...]` | Select dropdown |
| `SecretStr` | Password input |
Use `Field()` metadata to control labels (`title`), placeholders (`description`), and validation (`min_length`, `max_length`, `ge`, `le`). Use `json_schema_extra={"ui": {"type": "textarea"}}` for multiline text.
## Callback
By default, the validated model is returned as JSON. Provide an `on_submit` callback to process the data server-side:
```python
def save_report(report: BugReport) -> str:
db.insert(report.model_dump())
return f"Bug #{db.last_id} filed: {report.title}"
mcp.add_provider(FormInput(model=BugReport, on_submit=save_report))
```
The callback receives a validated model instance and returns a string that becomes the tool result.
## Configuration
```python
FormInput(
model=BugReport, # Required: the Pydantic model
name="BugTracker", # App name (default: model name)
title="File a Bug", # Card heading (default: model name)
tool_name="file_bug", # Tool name (default: collect_{model})
submit_text="Submit Report", # Button label (default: "Submit")
on_submit=save_report, # Optional callback
send_message=True, # Push result as a chat message
)
```
Set `send_message=True` to push the result back into the conversation via `SendMessage`, triggering the LLM's next turn. Without it, the result is just the tool return value.
## Multiple forms
Add multiple providers for different models — each gets its own tool:
```python
mcp = FastMCP(
"My Server",
providers=[
FormInput(model=ShippingAddress),
FormInput(model=BugReport),
FormInput(model=ContactInfo),
],
)
```

197
docs/v3/apps/quickstart.mdx Normal file
View file

@ -0,0 +1,197 @@
---
title: Quickstart
sidebarTitle: Quickstart
description: Build your first FastMCP app in under a minute.
icon: rocket
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
import { PrefabDemoFrame } from '/snippets/prefab-demo-frame.mdx'
<VersionBadge version="3.2.0" />
By the end of this page, you'll have a working tool that returns this:
<PrefabDemoFrame demo="team-directory" height="545px" title="Team directory demo" />
A pie chart the user can hover, a table they can sort and search — and a single Python tool.
## Install
```bash
pip install "fastmcp[apps]"
```
The `apps` extra pulls in [Prefab](https://prefab.prefect.io), the Python component library used to build app UIs.
## Write the tool
Create `server.py`. The interesting parts: `app=True` tells FastMCP this tool renders a UI, and `with PrefabApp() as app:` is the canonical pattern for composing one.
```python server.py expandable
from collections import Counter
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, DataTable, DataTableColumn, Grid
from prefab_ui.components.charts import PieChart
from fastmcp import FastMCP
mcp = FastMCP("My First App")
@mcp.tool(app=True)
def team_directory() -> PrefabApp:
"""Browse the team directory."""
members = [
{"name": "Alice Chen", "role": "Staff Engineer", "office": "San Francisco"},
{"name": "Bob Martinez", "role": "Lead Designer", "office": "New York"},
{"name": "Carol Johnson", "role": "Senior Engineer", "office": "London"},
{"name": "David Kim", "role": "Product Manager", "office": "San Francisco"},
{"name": "Eva Mueller", "role": "Engineer", "office": "Berlin"},
{"name": "Frank Lee", "role": "Data Scientist", "office": "San Francisco"},
{"name": "Grace Park", "role": "Engineering Manager", "office": "New York"},
]
office_counts = [
{"office": office, "count": count}
for office, count in Counter(m["office"] for m in members).items()
]
with PrefabApp() as app:
with Column(gap=4, css_class="p-6"):
with Grid(columns=[1, 2], gap=4):
PieChart(
data=office_counts,
data_key="count",
name_key="office",
show_legend=True,
)
DataTable(
columns=[
DataTableColumn(key="name", header="Name", sortable=True),
DataTableColumn(key="role", header="Role", sortable=True),
DataTableColumn(key="office", header="Office", sortable=True),
],
rows=members,
search=True,
)
return app
```
The Prefab code reads top-to-bottom. `PrefabApp()` is the root; everything inside its `with` block becomes the UI. `Column` stacks children vertically, `Grid` lays them out in columns. `DataTable` takes rows and column definitions and gives you sort and search for free.
`app=True` does the rest: it sets up the renderer resource, the content security policy, and the metadata that tells the host "this tool returns a UI." The host loads the result in a sandboxed iframe where the user can interact with it — all client-side, no round-trips.
## Preview it
FastMCP ships a dev server that renders your app tools in a browser, no MCP host needed:
```bash
fastmcp dev apps server.py
```
Open `http://localhost:8080`, pick `team_directory`, and try sorting columns and searching.
<Frame>
<img src="/apps/images/app-quickstart-dev-2.png" alt="The team directory rendered in the fastmcp dev apps preview, showing a pie chart, searchable table, and a detail card after clicking a row" />
</Frame>
## Make it reactive
The UI above renders once from your Python. Prefab apps can also respond to user input live, without any server round-trips. The key concept is **state**: a client-side key-value store that components read from and write to.
Click a row in the demo below to see a detail card appear:
<PrefabDemoFrame demo="team-directory-reactive" height="675px" title="Reactive team directory demo" />
Add a few imports, give each member a couple more fields, wire up a click handler, and render a detail card when something's selected:
```python expandable server.py
from collections import Counter
from prefab_ui.actions import SetState
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
Badge, Card, CardContent, CardHeader, Column, DataTable, DataTableColumn,
Grid, H3, Row, Small, Text,
)
from prefab_ui.components.charts import PieChart
from prefab_ui.components.control_flow import If
from prefab_ui.rx import Rx, STATE
from fastmcp import FastMCP
mcp = FastMCP("My First App")
MEMBERS = [
{"name": "Alice Chen", "role": "Staff Engineer", "office": "San Francisco", "email": "alice@company.com", "projects": 3},
{"name": "Bob Martinez", "role": "Lead Designer", "office": "New York", "email": "bob@company.com", "projects": 5},
# ... more members ...
]
OFFICE_COUNTS = [
{"office": o, "count": c}
for o, c in Counter(m["office"] for m in MEMBERS).items()
]
@mcp.tool(app=True)
def team_directory() -> PrefabApp:
"""Browse the team directory."""
with PrefabApp(state={"selected": None}) as app:
with Column(gap=4, css_class="p-6"):
with Grid(columns=[1, 2], gap=4):
PieChart(
data=OFFICE_COUNTS,
data_key="count",
name_key="office",
show_legend=True,
)
DataTable(
columns=[
DataTableColumn(key="name", header="Name", sortable=True),
DataTableColumn(key="role", header="Role", sortable=True),
DataTableColumn(key="office", header="Office", sortable=True),
],
rows=MEMBERS,
search=True,
on_row_click=SetState("selected", Rx("$event")),
)
with If(STATE.selected):
with Card():
with CardHeader():
with Row(gap=2, align="center"):
H3(Rx("selected.name"))
Badge(Rx("selected.office"))
with CardContent():
with Grid(columns=3, gap=4):
with Column(gap=0):
Small("Role")
Text(Rx("selected.role"))
with Column(gap=0):
Small("Email")
Text(Rx("selected.email"))
with Column(gap=0):
Small("Active Projects")
Text(Rx("selected.projects"))
return app
```
Three new ideas do all the work:
- **`on_row_click=SetState("selected", Rx("$event"))`** — clicking a row writes its data into the `selected` state key. `$event` is the clicked row dict.
- **`Rx("selected.name")`** — a reactive reference. It doesn't hold a Python value; it compiles to a browser-side expression that re-evaluates whenever `selected` changes, so `Text(Rx("selected.name"))` always shows the latest clicked name.
- **`If(STATE.selected)`** — conditionally renders its body. Before any click, `selected` is `None` and the card stays hidden.
The `state={"selected": None}` dict on `PrefabApp` sets the initial value. Everything else happens in the browser — no round-trips to your server when the user clicks.
## Where to go next
You've built a tool that returns an interactive, reactive UI. This pattern covers a huge range of use cases: build a visualization, return it, and the user gets it rendered right in the conversation.
- **[Interactive Tools](/apps/prefab)** — charts, tables, dashboards, reactive state, with live demos
- **[FastMCPApp](/apps/fastmcp-app)** — when the UI needs to call back to your server (forms, search, CRUD)
- **[Examples](/apps/examples)** — complete working servers you can run today

3759
docs/v3/changelog.mdx Normal file

File diff suppressed because it is too large Load diff

85
docs/v3/cli/auth.mdx Normal file
View file

@ -0,0 +1,85 @@
---
title: Auth Utilities
sidebarTitle: Auth
description: Create and validate CIMD documents for OAuth
icon: key
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
The `fastmcp auth` commands help with CIMD (Client ID Metadata Document) management — part of MCP's OAuth authentication flow. A CIMD is a JSON document you host at an HTTPS URL to identify your client application to MCP servers.
## Creating a CIMD
`fastmcp auth cimd create` generates a CIMD document:
```bash
fastmcp auth cimd create \
--name "My App" \
--redirect-uri "http://localhost:*/callback"
```
```json
{
"client_id": "https://your-domain.com/oauth/client.json",
"client_name": "My App",
"redirect_uris": ["http://localhost:*/callback"],
"token_endpoint_auth_method": "none"
}
```
The generated document includes a placeholder `client_id` — update it to match the URL where you'll host the document before deploying.
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Name | `--name` | **Required.** Human-readable client name |
| Redirect URI | `--redirect-uri` | **Required.** Allowed redirect URIs (repeatable) |
| Client URI | `--client-uri` | Client's home page URL |
| Logo URI | `--logo-uri` | Client's logo URL |
| Scope | `--scope` | Space-separated list of scopes |
| Output | `--output`, `-o` | Save to file (default: stdout) |
| Pretty | `--pretty` | Pretty-print JSON (default: true) |
### Example
```bash
fastmcp auth cimd create \
--name "My Production App" \
--redirect-uri "http://localhost:*/callback" \
--redirect-uri "https://myapp.example.com/callback" \
--client-uri "https://myapp.example.com" \
--scope "read write" \
--output client.json
```
## Validating a CIMD
`fastmcp auth cimd validate` fetches a hosted CIMD and verifies it conforms to the spec:
```bash
fastmcp auth cimd validate https://myapp.example.com/oauth/client.json
```
The validator checks that the URL is valid (HTTPS, non-root path), the document is valid JSON, the `client_id` matches the URL, and no shared-secret auth methods are used.
On success:
```
→ Fetching https://myapp.example.com/oauth/client.json...
✓ Valid CIMD document
Document details:
client_id: https://myapp.example.com/oauth/client.json
client_name: My App
token_endpoint_auth_method: none
redirect_uris:
• http://localhost:*/callback
```
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Timeout | `--timeout`, `-t` | HTTP request timeout in seconds (default: 10) |

144
docs/v3/cli/client.mdx Normal file
View file

@ -0,0 +1,144 @@
---
title: Client Commands
sidebarTitle: Client
description: List tools, call them, and discover configured servers
icon: satellite-dish
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
The CLI can act as an MCP client — connecting to any server (local or remote) to list what it exposes and call its tools directly. This is useful for development, debugging, scripting, and giving shell-capable LLM agents access to MCP servers.
## Listing Tools
`fastmcp list` connects to a server and prints its tools as function signatures, showing parameter names, types, and descriptions at a glance:
```bash
fastmcp list http://localhost:8000/mcp
fastmcp list server.py
fastmcp list weather # name-based resolution
```
When you need the full JSON Schema for a tool's inputs or outputs — for understanding nested objects, enum constraints, or complex types — opt in with `--input-schema` or `--output-schema`:
```bash
fastmcp list server.py --input-schema
```
### Resources and Prompts
By default, only tools are shown. Add `--resources` or `--prompts` to include those:
```bash
fastmcp list server.py --resources --prompts
```
### Machine-Readable Output
The `--json` flag switches to structured JSON with full schemas included. This is the format to use when feeding tool definitions to an LLM or building automation:
```bash
fastmcp list server.py --json
```
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Command | `--command` | Connect via stdio (e.g., `'npx -y @mcp/server'`) |
| Transport | `--transport`, `-t` | Force `http` or `sse` for URL targets |
| Resources | `--resources` | Include resources in output |
| Prompts | `--prompts` | Include prompts in output |
| Input Schema | `--input-schema` | Show full input schemas |
| Output Schema | `--output-schema` | Show full output schemas |
| JSON | `--json` | Structured JSON output |
| Timeout | `--timeout` | Connection timeout in seconds |
| Auth | `--auth` | `oauth` (default for HTTP), a bearer token, or `none` |
## Calling Tools
`fastmcp call` invokes a single tool on a server. Pass arguments as `key=value` pairs — the CLI fetches the tool's schema and coerces your string values to the right types automatically:
```bash
fastmcp call server.py greet name=World
fastmcp call http://localhost:8000/mcp search query=hello limit=5
```
Type coercion is schema-driven: `"5"` becomes the integer `5` when the schema expects an integer. Booleans accept `true`/`false`, `yes`/`no`, and `1`/`0`. Arrays and objects are parsed as JSON.
### Complex Arguments
For tools with nested or structured parameters, `key=value` syntax gets awkward. Pass a single JSON object instead:
```bash
fastmcp call server.py create_item '{"name": "Widget", "tags": ["sale"], "metadata": {"color": "blue"}}'
```
Or use `--input-json` to provide a base dictionary, then override individual keys with `key=value` pairs:
```bash
fastmcp call server.py search --input-json '{"query": "hello", "limit": 5}' limit=10
```
### Error Handling
If you misspell a tool name, the CLI suggests corrections via fuzzy matching. Missing required arguments produce a clear message with the tool's signature as a reminder. Tool execution errors are printed with a non-zero exit code, making the CLI straightforward to use in scripts.
### Structured Output
`--json` emits the raw result including content blocks, error status, and structured content:
```bash
fastmcp call server.py get_weather city=London --json
```
### Interactive Elicitation
Some tools request additional input during execution through MCP's elicitation mechanism. When this happens, the CLI prompts you in the terminal — showing each field's name, type, and whether it's required. You can type `decline` to skip a question or `cancel` to abort the call entirely.
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Command | `--command` | Connect via stdio |
| Transport | `--transport`, `-t` | Force `http` or `sse` |
| Input JSON | `--input-json` | Base arguments as JSON (merged with `key=value`) |
| JSON | `--json` | Raw JSON output |
| Timeout | `--timeout` | Connection timeout in seconds |
| Auth | `--auth` | `oauth`, a bearer token, or `none` |
## Discovering Configured Servers
`fastmcp discover` scans your machine for MCP servers configured in editors and tools. It checks:
- **Claude Desktop** — `claude_desktop_config.json`
- **Claude Code** — `~/.claude.json`
- **Cursor** — `.cursor/mcp.json` (walks up from current directory)
- **Gemini CLI** — `~/.gemini/settings.json`
- **Goose** — `~/.config/goose/config.yaml`
- **Project** — `./mcp.json` in the current directory
```bash
fastmcp discover
```
The output groups servers by source, showing each server's name and transport. Filter by source or get machine-readable output:
```bash
fastmcp discover --source claude-code
fastmcp discover --source cursor --source gemini --json
```
Any server that appears here can be used by name with `list`, `call`, and other commands — so you can go from "I have a server in Claude Code" to querying it without copying URLs or paths.
## LLM Agent Integration
For LLM agents that can execute shell commands but don't have native MCP support, the CLI provides a clean bridge. The agent calls `fastmcp list --json` to discover available tools with full schemas, then `fastmcp call --json` to invoke them with structured results.
Because the CLI handles connection management, transport selection, and type coercion internally, the agent doesn't need to understand MCP protocol details — it just reads JSON and constructs shell commands.
## Remote Stdio Bridges
For MCP hosts that expect a local stdio command but need to connect to a remote HTTP server, use [`fastmcp-remote`](/clients/fastmcp-remote). It provides a small standalone bridge for host configuration, while `fastmcp list` and `fastmcp call` remain focused on direct inspection and invocation from the terminal.

View file

@ -0,0 +1,106 @@
---
title: Generate CLI
sidebarTitle: Generate CLI
description: Scaffold a standalone typed CLI from any MCP server
icon: wand-magic-sparkles
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
`fastmcp list` and `fastmcp call` are general-purpose — you always specify the server, the tool name, and the arguments from scratch. `fastmcp generate-cli` goes further: it connects to a server, reads its tool schemas, and writes a standalone Python script where every tool is a proper subcommand with typed flags, help text, and tab completion. The result is a CLI that feels hand-written for that specific server.
MCP tool schemas already contain everything a CLI framework needs — parameter names, types, descriptions, required/optional status, and defaults. `generate-cli` maps that into [cyclopts](https://cyclopts.readthedocs.io/) commands, so JSON Schema types become Python type annotations, descriptions become `--help` text, and required parameters become mandatory flags.
## Generating a Script
Point the command at any [server target](/cli/overview#server-targets) and it writes a CLI script:
```bash
fastmcp generate-cli weather
fastmcp generate-cli http://localhost:8000/mcp
fastmcp generate-cli server.py my_weather_cli.py
```
The second positional argument sets the output path (defaults to `cli.py`). If the file already exists, pass `-f` to overwrite:
```bash
fastmcp generate-cli weather -f
```
## What You Get
The generated script is a regular Python file — executable, editable, and yours:
```
$ python cli.py call-tool --help
Usage: weather-cli call-tool COMMAND
Call a tool on the server
Commands:
get_forecast Get the weather forecast for a city.
search_city Search for a city by name.
```
Each tool has typed parameters with help text pulled directly from the server's schema:
```
$ python cli.py call-tool get_forecast --help
Usage: weather-cli call-tool get_forecast [OPTIONS]
Get the weather forecast for a city.
Options:
--city [str] City name (required)
--days [int] Number of forecast days (default: 3)
```
Beyond tool commands, the script includes generic MCP operations — `list-tools`, `list-resources`, `read-resource`, `list-prompts`, and `get-prompt` — that always reflect the server's current state, even if tools have changed since generation.
## Parameter Handling
Parameters are mapped based on their JSON Schema type:
**Simple types** (`string`, `integer`, `number`, `boolean`) become typed flags:
```bash
python cli.py call-tool get_forecast --city London --days 3
```
**Arrays of simple types** become repeatable flags:
```bash
python cli.py call-tool tag_items --tags python --tags fastapi --tags mcp
```
**Complex types** (objects, nested arrays, unions) accept JSON strings. The `--help` output shows the full schema so you know what structure to pass:
```bash
python cli.py call-tool create_user \
--name John \
--metadata '{"role": "admin", "dept": "engineering"}'
```
## Agent Skill
Alongside the CLI script, `generate-cli` writes a `SKILL.md` file — a [Claude Code agent skill](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/skills) that documents every tool's exact invocation syntax, parameter flags, types, and descriptions. An agent can pick up the CLI immediately without running `--help` or experimenting with flag names.
To skip skill generation:
```bash
fastmcp generate-cli weather --no-skill
```
## How It Works
The generated script is a *client*, not a server — it connects to the server on every invocation rather than bundling it. A `CLIENT_SPEC` variable at the top holds the resolved transport (a URL string or `StdioTransport` with baked-in command and arguments).
The most common edit is changing `CLIENT_SPEC` — for example, pointing a script generated from a dev server at production. Beyond that, the helper functions (`_call_tool`, `_print_tool_result`) are thin wrappers around `fastmcp.Client` that are easy to adapt.
The script requires `fastmcp` as a dependency. If it lives outside a project that already has FastMCP installed:
```bash
uv run --with fastmcp python cli.py call-tool get_forecast --city London
```

View file

@ -0,0 +1,72 @@
---
title: Inspecting Servers
sidebarTitle: Inspecting
description: View a server's components and metadata
icon: magnifying-glass
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.9.0" />
`fastmcp inspect` loads a server and reports what it contains — its tools, resources, prompts, version, and metadata. The default output is a human-readable summary:
```bash
fastmcp inspect server.py
```
```
Server: MyServer
Instructions: A helpful MCP server
Version: 1.0.0
Components:
Tools: 5
Prompts: 2
Resources: 3
Templates: 1
Environment:
FastMCP: 2.0.0
MCP: 1.0.0
Use --format [fastmcp|mcp] for complete JSON output
```
## JSON Output
For programmatic use, two JSON formats are available:
**FastMCP format** (`--format fastmcp`) includes everything FastMCP knows about the server — tool tags, enabled status, output schemas, annotations, and custom metadata. Field names use `snake_case`. This is the format for debugging and introspecting FastMCP servers.
**MCP protocol format** (`--format mcp`) shows exactly what MCP clients see through the protocol — only standard MCP fields, `camelCase` names, no FastMCP-specific extensions. This is the format for verifying client compatibility and debugging what clients actually receive.
```bash
# Full FastMCP metadata to stdout
fastmcp inspect server.py --format fastmcp
# MCP protocol view saved to file
fastmcp inspect server.py --format mcp -o manifest.json
```
## Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Format | `--format`, `-f` | `fastmcp` or `mcp` (required when using `-o`) |
| Output File | `--output`, `-o` | Save to file instead of stdout |
## Entrypoints
The `inspect` command supports the same local entrypoints as [`fastmcp run`](/cli/running): inferred instances, explicit entrypoints, factory functions, and `fastmcp.json` configs.
```bash
fastmcp inspect server.py # inferred instance
fastmcp inspect server.py:my_server # explicit entrypoint
fastmcp inspect server.py:create_server # factory function
fastmcp inspect fastmcp.json # config file
```
<Warning>
`inspect` only works with local files and `fastmcp.json` — it doesn't connect to remote URLs or standard MCP config files.
</Warning>

146
docs/v3/cli/install-mcp.mdx Normal file
View file

@ -0,0 +1,146 @@
---
title: Install MCP Servers
sidebarTitle: Install MCPs
description: Install MCP servers into Claude, Cursor, Gemini, and other clients
icon: download
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.10.3" />
`fastmcp install` registers a server with an MCP client application so the client can launch it automatically. Each MCP client runs servers in its own isolated environment, which means dependencies need to be explicitly declared — you can't rely on whatever happens to be installed locally.
```bash
fastmcp install claude-desktop server.py
fastmcp install claude-code server.py --with pandas --with matplotlib
fastmcp install cursor server.py -e .
```
<Warning>
`uv` must be installed and available in your system PATH. Both Claude Desktop and Cursor run servers in isolated environments managed by `uv`. On macOS, install it globally with Homebrew for Claude Desktop compatibility: `brew install uv`.
</Warning>
## Supported Clients
| Client | Install method |
| ------ | -------------- |
| `claude-code` | Claude Code's built-in MCP management |
| `claude-desktop` | Direct config file modification |
| `cursor` | Deeplink that opens Cursor for confirmation |
| `gemini-cli` | Gemini CLI's built-in MCP management |
| `goose` | Deeplink that opens Goose for confirmation (uses `uvx`) |
| `mcp-json` | Generates standard MCP JSON config for manual use |
| `stdio` | Outputs the shell command to run via stdio |
## Declaring Dependencies
Because MCP clients run servers in isolation, you need to tell the install command what your server needs. There are two approaches:
**Command-line flags** let you specify dependencies directly:
```bash
fastmcp install claude-desktop server.py --with pandas --with "sqlalchemy>=2.0"
fastmcp install cursor server.py -e . --with-requirements requirements.txt
```
**`fastmcp.json`** configuration files declare dependencies alongside the server definition. When you install from a config file, dependencies are picked up automatically:
```bash
fastmcp install claude-desktop fastmcp.json
fastmcp install claude-desktop # auto-detects fastmcp.json in current directory
```
See [Server Configuration](/deployment/server-configuration) for the full config format.
## Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Server Name | `--server-name`, `-n` | Custom name for the server |
| Editable Package | `--with-editable`, `-e` | Install a directory in editable mode |
| Extra Packages | `--with` | Additional packages (repeatable) |
| Environment Variables | `--env` | `KEY=VALUE` pairs (repeatable) |
| Environment File | `--env-file`, `-f` | Load env vars from a `.env` file |
| Python | `--python` | Python version (e.g., `3.11`) |
| Project | `--project` | Run within a uv project directory |
| Requirements | `--with-requirements` | Install from a requirements file |
| Config Path | `--config-path` | Custom path to Claude Desktop config directory (`claude-desktop` only) |
## Examples
```bash
# Basic install with auto-detected server instance
fastmcp install claude-desktop server.py
# Install from fastmcp.json with auto-detection
fastmcp install claude-desktop
# Explicit entrypoint with dependencies
fastmcp install claude-desktop server.py:my_server \
--server-name "My Analysis Server" \
--with pandas
# With environment variables
fastmcp install claude-code server.py \
--env API_KEY=secret \
--env DEBUG=true
# With env file
fastmcp install cursor server.py --env-file .env
# Specific Python version and requirements file
fastmcp install claude-desktop server.py \
--python 3.11 \
--with-requirements requirements.txt
# With custom config path (claude-desktop only)
fastmcp install claude-desktop server.py \
--config-path "C:\Users\username\AppData\Local\Packages\Claude_xyz\LocalCache\Roaming\Claude"
```
## Generating MCP JSON
The `mcp-json` target generates standard MCP configuration JSON instead of installing into a specific client. This is useful for clients that FastMCP doesn't directly support, for CI/CD environments, or for sharing server configs:
```bash
fastmcp install mcp-json server.py
```
The output follows the standard format used by Claude Desktop, Cursor, and other MCP clients:
```json
{
"server-name": {
"command": "uv",
"args": ["run", "--with", "fastmcp", "fastmcp", "run", "/path/to/server.py"],
"env": {
"API_KEY": "value"
}
}
}
```
Use `--copy` to send it to your clipboard instead of stdout.
## Generating Stdio Commands
The `stdio` target outputs the shell command an MCP host would use to start your server over stdio:
```bash
fastmcp install stdio server.py
# Output: uv run --with fastmcp fastmcp run /absolute/path/to/server.py
```
When installing from a `fastmcp.json`, dependencies from the config are included automatically:
```bash
fastmcp install stdio fastmcp.json
# Output: uv run --with fastmcp --with pillow --with 'qrcode[pil]>=8.0' fastmcp run /path/to/server.py
```
Use `--copy` to copy to clipboard.
<Tip>
`fastmcp install` is designed for local server files with stdio transport. For remote servers running over HTTP, use your client's native configuration — FastMCP's value here is simplifying the complex local setup with `uv`, dependencies, and environment variables.
</Tip>

104
docs/v3/cli/overview.mdx Normal file
View file

@ -0,0 +1,104 @@
---
title: CLI
sidebarTitle: Overview
description: The fastmcp command-line interface
icon: terminal
---
import { VersionBadge } from '/snippets/version-badge.mdx'
The `fastmcp` CLI is installed automatically with FastMCP. It's the primary way to run, test, install, and interact with MCP servers from your terminal.
```bash
fastmcp --help
```
## Commands at a Glance
| Command | What it does |
| ------- | ------------ |
| [`run`](/cli/running) | Run a server (local file, factory function, remote URL, or config file) |
| [`dev apps`](/cli/running#previewing-apps) | Launch a browser-based preview UI for Prefab App tools |
| [`dev inspector`](/cli/running#development-with-the-inspector) | Launch a server inside the MCP Inspector for interactive testing |
| [`install`](/cli/install-mcp) | Install a server into Claude Code, Claude Desktop, Cursor, Gemini CLI, or Goose |
| [`inspect`](/cli/inspecting) | Print a server's tools, resources, and prompts as a summary or JSON report |
| [`list`](/cli/client) | List a server's tools (and optionally resources and prompts) |
| [`call`](/cli/client#calling-tools) | Call a single tool with arguments |
| [`discover`](/cli/client#discovering-configured-servers) | Find MCP servers configured in your editors and tools |
| [`generate-cli`](/cli/generate-cli) | Scaffold a standalone typed CLI from a server's tool schemas |
| [`project prepare`](/cli/running#pre-building-environments) | Pre-install dependencies into a reusable uv project |
| [`auth cimd`](/cli/auth) | Create and validate CIMD documents for OAuth |
| `version` | Print version info (`--copy` to copy to clipboard) |
## Server Targets
Most commands need to know *which server* to talk to. You pass a "server spec" as the first argument, and FastMCP resolves the right transport automatically.
**URLs** connect to a running HTTP server:
```bash
fastmcp list http://localhost:8000/mcp
fastmcp call http://localhost:8000/mcp get_forecast city=London
```
**Python files** are loaded directly — no `mcp.run()` boilerplate needed. FastMCP finds a server instance named `mcp`, `server`, or `app` in the file, or you can specify one explicitly:
```bash
fastmcp list server.py
fastmcp run server.py:my_custom_server
```
**Config files** work too — both FastMCP's own `fastmcp.json` format and standard MCP config files with an `mcpServers` key:
```bash
fastmcp run fastmcp.json
fastmcp list mcp-config.json
```
**Stdio commands** connect to any MCP server that speaks over standard I/O. Use `--command` instead of a positional argument:
```bash
fastmcp list --command 'npx -y @modelcontextprotocol/server-github'
```
### Name-Based Resolution
If your servers are already configured in an editor or tool, you can refer to them by name. FastMCP scans configs from Claude Desktop, Claude Code, Cursor, Gemini CLI, and Goose:
```bash
fastmcp list weather
fastmcp call weather get_forecast city=London
```
When the same name appears in multiple configs, use the `source:name` form to be specific:
```bash
fastmcp list claude-code:my-server
fastmcp call cursor:weather get_forecast city=London
```
Run [`fastmcp discover`](/cli/client#discovering-configured-servers) to see what's available on your machine.
## Authentication
When targeting an HTTP URL, the CLI enables OAuth authentication by default. If the server requires it, you'll be guided through the flow (typically opening a browser). If it doesn't, the setup is a silent no-op.
To skip authentication entirely — useful for local development servers — pass `--auth none`:
```bash
fastmcp call http://localhost:8000/mcp my_tool --auth none
```
You can also pass a bearer token directly:
```bash
fastmcp list http://localhost:8000/mcp --auth "Bearer sk-..."
```
## Transport Override
FastMCP defaults to Streamable HTTP for URL targets. If the server only supports Server-Sent Events (SSE), force the older transport:
```bash
fastmcp list http://localhost:8000 --transport sse
```

166
docs/v3/cli/running.mdx Normal file
View file

@ -0,0 +1,166 @@
---
title: Running Servers
sidebarTitle: Running
description: Start, develop, and configure servers from the command line
icon: play
---
import { VersionBadge } from '/snippets/version-badge.mdx'
## Starting a Server
`fastmcp run` starts a server. Point it at a Python file, a factory function, a remote URL, or a config file:
```bash
fastmcp run server.py
fastmcp run server.py:create_server
fastmcp run https://example.com/mcp
fastmcp run fastmcp.json
```
By default, the server runs over **stdio** — the transport that MCP clients like Claude Desktop expect. To serve over HTTP instead, specify the transport:
```bash
fastmcp run server.py --transport http
fastmcp run server.py --transport http --host 0.0.0.0 --port 9000
```
### Entrypoints
FastMCP supports several ways to locate and start your server:
**Inferred instance** — FastMCP imports the file and looks for a variable named `mcp`, `server`, or `app`:
```bash
fastmcp run server.py
```
**Explicit instance** — point at a specific variable:
```bash
fastmcp run server.py:my_server
```
**Factory function** — FastMCP calls the function and uses the returned server. Useful when your server needs async setup or configuration that runs before startup:
```bash
fastmcp run server.py:create_server
```
**Remote URL** — starts a local proxy that bridges to a remote server. Handy for local development against a deployed server, or for bridging a remote HTTP server to stdio:
```bash
fastmcp run https://example.com/mcp
```
**FastMCP config** — uses a `fastmcp.json` file that declaratively specifies the server, its dependencies, and deployment settings. When you run `fastmcp run` with no arguments, it auto-detects `fastmcp.json` in the current directory:
```bash
fastmcp run
fastmcp run my-config.fastmcp.json
```
See [Server Configuration](/deployment/server-configuration) for the full `fastmcp.json` format.
**MCP config** — runs servers defined in a standard MCP configuration file (any `.json` with an `mcpServers` key):
```bash
fastmcp run mcp.json
```
<Warning>
`fastmcp run` completely ignores the `if __name__ == "__main__"` block. Any setup code in that block won't execute. If you need initialization logic to run, use a [factory function](/cli/overview#factory-functions).
</Warning>
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Transport | `--transport`, `-t` | `stdio` (default), `http`, or `sse` |
| Host | `--host` | Bind address for HTTP (default: `127.0.0.1`) |
| Port | `--port`, `-p` | Bind port for HTTP (default: `8000`) |
| Path | `--path` | URL path for HTTP (default: `/mcp/`) |
| Log Level | `--log-level`, `-l` | `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
| No Banner | `--no-banner` | Suppress the startup banner |
| Auto-Reload | `--reload` / `--no-reload` | Watch for file changes and restart automatically |
| Reload Dirs | `--reload-dir` | Directories to watch (repeatable) |
| Skip Env | `--skip-env` | Don't set up a uv environment (use when already in one) |
| Python | `--python` | Python version to use (e.g., `3.11`) |
| Extra Packages | `--with` | Additional packages to install (repeatable) |
| Project | `--project` | Run within a specific uv project directory |
| Requirements | `--with-requirements` | Install from a requirements file |
### Dependency Management
By default, `fastmcp run` uses your current Python environment directly. When you pass `--python`, `--with`, `--project`, or `--with-requirements`, it switches to running via `uv run` in a subprocess, which handles dependency isolation automatically.
The `--skip-env` flag is useful when you're already inside an activated venv, a Docker container with pre-installed dependencies, or a uv-managed project — it prevents uv from trying to set up another environment layer.
## Previewing Apps
<VersionBadge version="3.2.0" />
`fastmcp dev apps` launches a browser-based preview UI for servers with [Prefab App tools](/apps/prefab). It starts your MCP server on one port and a local dev UI on another — giving you a live, interactive picker where you can call app tools and see their rendered output without needing a full MCP host client.
```bash
fastmcp dev apps server.py
fastmcp dev apps server.py:mcp --mcp-port 9000 --dev-port 9090
```
The picker auto-generates a form from each tool's input schema. Submit the form and the result opens in a new tab as a rendered Prefab UI.
Auto-reload is on by default — save a file and the MCP server restarts automatically.
<Tip>
`fastmcp dev apps` requires `fastmcp[apps]` — install with `pip install "fastmcp[apps]"`.
</Tip>
| Option | Flag | Description |
| ------ | ---- | ----------- |
| MCP Port | `--mcp-port` | Port for the MCP server (default: `8000`) |
| Dev Port | `--dev-port` | Port for the dev UI (default: `8080`) |
| Auto-Reload | `--reload` / `--no-reload` | Watch for file changes (default: on) |
## Development with the Inspector
`fastmcp dev inspector` launches your server inside the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), a browser-based tool for interactively testing MCP servers. Auto-reload is on by default, so your server restarts when you save changes.
```bash
fastmcp dev inspector server.py
fastmcp dev inspector server.py -e . --with pandas
```
<Tip>
The Inspector always runs your server via `uv run` in a subprocess — it never uses your local environment directly. Specify dependencies with `--with`, `--with-editable`, `--with-requirements`, or through a `fastmcp.json` file.
</Tip>
<Warning>
The Inspector connects over **stdio only**. When it launches, you may need to select "STDIO" from the transport dropdown and click connect. To test a server over HTTP, start it separately with `fastmcp run server.py --transport http` and point the Inspector at the URL.
</Warning>
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Editable Package | `--with-editable`, `-e` | Install a directory in editable mode |
| Extra Packages | `--with` | Additional packages (repeatable) |
| Inspector Version | `--inspector-version` | MCP Inspector version to use |
| UI Port | `--ui-port` | Port for the Inspector UI |
| Server Port | `--server-port` | Port for the Inspector proxy |
| Auto-Reload | `--reload` / `--no-reload` | File watching (default: on) |
| Reload Dirs | `--reload-dir` | Directories to watch (repeatable) |
| Python | `--python` | Python version |
| Project | `--project` | Run within a uv project directory |
| Requirements | `--with-requirements` | Install from a requirements file |
## Pre-Building Environments
`fastmcp project prepare` creates a persistent uv project from a `fastmcp.json` file, pre-installing all dependencies. This separates environment setup from server execution — install once, run many times.
```bash
# Step 1: Build the environment (slow, does dependency resolution)
fastmcp project prepare fastmcp.json --output-dir ./env
# Step 2: Run using the prepared environment (fast, no install step)
fastmcp run fastmcp.json --project ./env
```
The prepared directory contains a `pyproject.toml`, a `.venv` with all packages installed, and a `uv.lock` for reproducibility. This is particularly useful in deployment scenarios where you want deterministic, pre-built environments.

View file

@ -0,0 +1,88 @@
---
title: Bearer Token Authentication
sidebarTitle: Bearer Auth
description: Authenticate your FastMCP client with a Bearer token.
icon: key
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.6.0" />
<Tip>
Bearer Token authentication is only relevant for HTTP-based transports.
</Tip>
You can configure your FastMCP client to use **bearer authentication** by supplying a valid access token. This is most appropriate for service accounts, long-lived API keys, CI/CD, applications where authentication is managed separately, or other non-interactive authentication methods.
A Bearer token is a JSON Web Token (JWT) that is used to authenticate a request. It is most commonly used in the `Authorization` header of an HTTP request, using the `Bearer` scheme:
```http
Authorization: Bearer <token>
```
## Client Usage
The most straightforward way to use a pre-existing Bearer token is to provide it as a string to the `auth` parameter of the `fastmcp.Client` or transport instance. FastMCP will automatically format it correctly for the `Authorization` header and bearer scheme.
<Tip>
If you're using a string token, do not include the `Bearer` prefix. FastMCP will add it for you.
</Tip>
```python {5}
from fastmcp import Client
async with Client(
"https://your-server.fastmcp.app/mcp",
auth="<your-token>",
) as client:
await client.ping()
```
You can also supply a Bearer token to a transport instance, such as `StreamableHttpTransport` or `SSETransport`:
```python {6}
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport
transport = StreamableHttpTransport(
"http://your-server.fastmcp.app/mcp",
auth="<your-token>",
)
async with Client(transport) as client:
await client.ping()
```
## `BearerAuth` Helper
If you prefer to be more explicit and not rely on FastMCP to transform your string token, you can use the `BearerAuth` class yourself, which implements the `httpx.Auth` interface.
```python {6}
from fastmcp import Client
from fastmcp.client.auth import BearerAuth
async with Client(
"https://your-server.fastmcp.app/mcp",
auth=BearerAuth(token="<your-token>"),
) as client:
await client.ping()
```
## Custom Headers
If the MCP server expects a custom header or token scheme, you can manually set the client's `headers` instead of using the `auth` parameter by setting them on your transport:
```python {5}
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport
async with Client(
transport=StreamableHttpTransport(
"https://your-server.fastmcp.app/mcp",
headers={"X-API-Key": "<your-token>"},
),
) as client:
await client.ping()
```

View file

@ -0,0 +1,138 @@
---
title: CIMD Authentication
sidebarTitle: CIMD
description: Use Client ID Metadata Documents for verifiable, domain-based client identity.
icon: id-badge
tag: NEW
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="3.0.0" />
<Tip>
CIMD authentication is only relevant for HTTP-based transports and requires a server that advertises CIMD support.
</Tip>
With standard OAuth, your client registers dynamically with every server it connects to, receiving a fresh `client_id` each time. This works, but the server has no way to verify *who* your client actually is — any client can claim any name during registration.
CIMD (Client ID Metadata Documents) flips this around. You host a small JSON document at an HTTPS URL you control, and that URL becomes your `client_id`. When your client connects to a server, the server fetches your metadata document and can verify your identity through your domain ownership. Users see a verified domain badge in the consent screen instead of an unverified client name.
## Client Usage
Pass your CIMD document URL to the `client_metadata_url` parameter of `OAuth`:
```python
from fastmcp import Client
from fastmcp.client.auth import OAuth
async with Client(
"https://mcp-server.example.com/mcp",
auth=OAuth(
client_metadata_url="https://myapp.example.com/oauth/client.json",
),
) as client:
await client.ping()
```
When the server supports CIMD, the client uses your metadata URL as its `client_id` instead of performing Dynamic Client Registration. The server fetches your document, validates it, and proceeds with the standard OAuth authorization flow.
<Note>
You don't need to pass `mcp_url` when using `OAuth` with `Client(auth=...)` — the transport provides the server URL automatically.
</Note>
## Creating a CIMD Document
A CIMD document is a JSON file that describes your client. The most important field is `client_id`, which must exactly match the URL where you host the document.
Use the FastMCP CLI to generate one:
```bash
fastmcp auth cimd create \
--name "My Application" \
--redirect-uri "http://localhost:*/callback" \
--client-id "https://myapp.example.com/oauth/client.json"
```
This produces:
```json
{
"client_id": "https://myapp.example.com/oauth/client.json",
"client_name": "My Application",
"redirect_uris": ["http://localhost:*/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code"],
"response_types": ["code"]
}
```
If you omit `--client-id`, the CLI generates a placeholder value and reminds you to update it before hosting.
### CLI Options
The `create` command accepts these flags:
| Flag | Description |
|------|-------------|
| `--name` | Human-readable client name (required) |
| `--redirect-uri`, `-r` | Allowed redirect URIs — can be specified multiple times (required) |
| `--client-id` | The URL where you'll host this document (sets `client_id` directly) |
| `--output`, `-o` | Write to a file instead of stdout |
| `--scope` | Space-separated list of scopes the client may request |
| `--client-uri` | URL of the client's home page |
| `--logo-uri` | URL of the client's logo image |
| `--no-pretty` | Output compact JSON |
### Redirect URIs
The `redirect_uris` field supports wildcard port matching for localhost. The pattern `http://localhost:*/callback` matches any port, which is useful for development clients that bind to random available ports (which is what FastMCP's `OAuth` helper does by default).
## Hosting Requirements
CIMD documents must be hosted at a publicly accessible HTTPS URL with a non-root path:
- **HTTPS required** — HTTP URLs are rejected for security
- **Non-root path** — The URL must have a path component (e.g., `/oauth/client.json`, not just `/`)
- **Public accessibility** — The server must be able to fetch the document over the internet
- **Matching `client_id`** — The `client_id` field in the document must exactly match the hosting URL
Common hosting options include static file hosting services like GitHub Pages, Cloudflare Pages, Vercel, or S3 — anywhere you can serve a JSON file over HTTPS.
## Validating Your Document
Before deploying, verify your hosted document passes validation:
```bash
fastmcp auth cimd validate https://myapp.example.com/oauth/client.json
```
The validator fetches the document and checks that:
- The URL is valid (HTTPS, non-root path)
- The document is well-formed JSON conforming to the CIMD schema
- The `client_id` in the document matches the URL it was fetched from
## How It Works
When your client connects to a CIMD-enabled server, the flow works like this:
<Steps>
<Step title="Client Presents Metadata URL">
Your client sends its `client_metadata_url` as the `client_id` in the OAuth authorization request.
</Step>
<Step title="Server Recognizes CIMD URL">
The server sees that the `client_id` is an HTTPS URL with a path — the signature of a CIMD client — and skips Dynamic Client Registration.
</Step>
<Step title="Server Fetches and Validates">
The server fetches your JSON document from the URL, validates that `client_id` matches the URL, and extracts your client metadata (name, redirect URIs, scopes).
</Step>
<Step title="Authorization Proceeds">
The standard OAuth flow continues: browser opens for user consent, authorization code exchange, token issuance. The consent screen shows your verified domain.
</Step>
</Steps>
The server caches your CIMD document according to HTTP cache headers, so subsequent requests don't require re-fetching.
## Server Configuration
CIMD is a server-side feature that your MCP server must support. FastMCP's OAuth proxy providers (GitHub, Google, Auth0, etc.) support CIMD by default. See the [OAuth Proxy CIMD documentation](/servers/auth/oauth-proxy#cimd-support) for server-side configuration, including private key JWT authentication and security details.

View file

@ -0,0 +1,186 @@
---
title: OAuth Authentication
sidebarTitle: OAuth
description: Authenticate your FastMCP client via OAuth 2.1.
icon: window
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.6.0" />
<Tip>
OAuth authentication is only relevant for HTTP-based transports and requires user interaction via a web browser.
</Tip>
When your FastMCP client needs to access an MCP server protected by OAuth 2.1, and the process requires user interaction (like logging in and granting consent), you should use the Authorization Code Flow. FastMCP provides the `fastmcp.client.auth.OAuth` helper to simplify this entire process.
This flow is common for user-facing applications where the application acts on behalf of the user.
## Client Usage
### Default Configuration
The simplest way to use OAuth is to pass the string `"oauth"` to the `auth` parameter of the `Client` or transport instance. FastMCP will automatically configure the client to use OAuth with default settings:
```python {4}
from fastmcp import Client
# Uses default OAuth settings
async with Client("https://your-server.fastmcp.app/mcp", auth="oauth") as client:
await client.ping()
```
### `OAuth` Helper
To fully configure the OAuth flow, use the `OAuth` helper and pass it to the `auth` parameter of the `Client` or transport instance. `OAuth` manages the complexities of the OAuth 2.1 Authorization Code Grant with PKCE (Proof Key for Code Exchange) for enhanced security, and implements the full `httpx.Auth` interface.
```python {2, 4, 6}
from fastmcp import Client
from fastmcp.client.auth import OAuth
oauth = OAuth(scopes=["user"])
async with Client("https://your-server.fastmcp.app/mcp", auth=oauth) as client:
await client.ping()
```
<Note>
You don't need to pass `mcp_url` when using `OAuth` with `Client(auth=...)` — the transport provides the server URL automatically.
</Note>
#### `OAuth` Parameters
- **`scopes`** (`str | list[str]`, optional): OAuth scopes to request. Can be space-separated string or list of strings
- **`client_name`** (`str`, optional): Client name for dynamic registration. Defaults to `"FastMCP Client"`
- **`client_id`** (`str`, optional): Pre-registered OAuth client ID. When provided, skips Dynamic Client Registration entirely. See [Pre-Registered Clients](#pre-registered-clients)
- **`client_secret`** (`str`, optional): OAuth client secret for pre-registered clients. Optional — public clients that rely on PKCE can omit this
- **`client_metadata_url`** (`str`, optional): URL-based client identity (CIMD). See [CIMD Authentication](/clients/auth/cimd) for details
- **`token_storage`** (`AsyncKeyValue`, optional): Storage backend for persisting OAuth tokens. Defaults to in-memory storage (tokens lost on restart). See [Token Storage](#token-storage) for encrypted storage options
- **`additional_client_metadata`** (`dict[str, Any]`, optional): Extra metadata for client registration
- **`callback_port`** (`int`, optional): Fixed port for OAuth callback server. If not specified, uses a random available port
- **`httpx_client_factory`** (`McpHttpClientFactory`, optional): Factory for creating httpx clients
## OAuth Flow
The OAuth flow is triggered when you use a FastMCP `Client` configured to use OAuth.
<Steps>
<Step title="Token Check">
The client first checks the configured `token_storage` backend for existing, valid tokens for the target server. If one is found, it will be used to authenticate the client.
</Step>
<Step title="OAuth Server Discovery">
If no valid tokens exist, the client attempts to discover the OAuth server's endpoints using a well-known URI (e.g., `/.well-known/oauth-authorization-server`) based on the `mcp_url`.
</Step>
<Step title="Client Registration">
If a `client_id` is provided, the client uses those pre-registered credentials directly and skips this step entirely. Otherwise, if a `client_metadata_url` is configured and the server supports CIMD, the client uses its metadata URL as its identity. As a fallback, the client performs Dynamic Client Registration (RFC 7591) if the server supports it.
</Step>
<Step title="Local Callback Server">
A temporary local HTTP server is started on an available port (or the port specified via `callback_port`). This server's address (e.g., `http://127.0.0.1:<port>/callback`) acts as the `redirect_uri` for the OAuth flow.
</Step>
<Step title="Browser Interaction">
The user's default web browser is automatically opened, directing them to the OAuth server's authorization endpoint. The user logs in and grants (or denies) the requested `scopes`.
</Step>
<Step title="Authorization Code & Token Exchange">
Upon approval, the OAuth server redirects the user's browser to the local callback server with an `authorization_code`. The client captures this code and exchanges it with the OAuth server's token endpoint for an `access_token` (and often a `refresh_token`) using PKCE for security.
</Step>
<Step title="Token Caching">
The obtained tokens are saved to the configured `token_storage` backend for future use, eliminating the need for repeated browser interactions.
</Step>
<Step title="Authenticated Requests">
The access token is automatically included in the `Authorization` header for requests to the MCP server.
</Step>
<Step title="Refresh Token">
If the access token expires, the client will automatically use the refresh token to get a new access token.
</Step>
</Steps>
## Token Storage
<VersionBadge version="2.13.0" />
By default, tokens are stored in memory and lost when your application restarts. For persistent storage, pass an `AsyncKeyValue`-compatible storage backend to the `token_storage` parameter.
<Warning>
**Security Consideration**: Use encrypted storage for production. MCP clients can accumulate OAuth credentials for many servers over time, and a compromised token store could expose access to multiple services.
</Warning>
```python
from fastmcp import Client
from fastmcp.client.auth import OAuth
from key_value.aio.stores.disk import DiskStore
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
import os
# Create encrypted disk storage
encrypted_storage = FernetEncryptionWrapper(
key_value=DiskStore(directory="~/.fastmcp/oauth-tokens"),
fernet=Fernet(os.environ["OAUTH_STORAGE_ENCRYPTION_KEY"])
)
oauth = OAuth(token_storage=encrypted_storage)
async with Client("https://your-server.fastmcp.app/mcp", auth=oauth) as client:
await client.ping()
```
You can use any `AsyncKeyValue`-compatible backend from the [key-value library](https://github.com/strawgate/py-key-value) including Redis, DynamoDB, and more. Wrap your storage in `FernetEncryptionWrapper` for encryption.
<Note>
When selecting a storage backend, review the [py-key-value documentation](https://github.com/strawgate/py-key-value) to understand the maturity level and limitations of your chosen backend. Some backends may be in preview or have constraints that affect production suitability.
</Note>
## CIMD Authentication
<VersionBadge version="3.0.0" />
Client ID Metadata Documents (CIMD) provide an alternative to Dynamic Client Registration. Instead of registering with each server, your client hosts a static JSON document at an HTTPS URL. That URL becomes your client's identity, and servers can verify who you are through your domain ownership.
```python
from fastmcp import Client
from fastmcp.client.auth import OAuth
async with Client(
"https://mcp-server.example.com/mcp",
auth=OAuth(
client_metadata_url="https://myapp.example.com/oauth/client.json",
),
) as client:
await client.ping()
```
See the [CIMD Authentication](/clients/auth/cimd) page for complete documentation on creating, hosting, and validating CIMD documents.
## Pre-Registered Clients
<VersionBadge version="3.0.0" />
Some OAuth servers don't support Dynamic Client Registration — the MCP spec explicitly makes DCR optional. If your client has been pre-registered with the server (you already have a `client_id` and optionally a `client_secret`), you can provide them directly to skip DCR entirely.
```python
from fastmcp import Client
from fastmcp.client.auth import OAuth
async with Client(
"https://mcp-server.example.com/mcp",
auth=OAuth(
client_id="my-registered-client-id",
client_secret="my-client-secret",
),
) as client:
await client.ping()
```
Public clients that rely on PKCE for security can omit `client_secret`:
```python
oauth = OAuth(client_id="my-public-client-id")
```
<Note>
When using pre-registered credentials, the client will not attempt Dynamic Client Registration. If the server rejects the credentials, the error is surfaced immediately rather than falling back to DCR.
</Note>

161
docs/v3/clients/cli.mdx Normal file
View file

@ -0,0 +1,161 @@
---
title: Client CLI
sidebarTitle: CLI
description: Query and invoke MCP server tools directly from the terminal with fastmcp list and fastmcp call.
icon: terminal
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
MCP servers are designed for programmatic consumption by AI assistants and applications. But during development, you often want to poke at a server directly: check what tools it exposes, call one with test arguments, or verify that a deployment is responding correctly. The FastMCP CLI gives you that direct access with two commands, `fastmcp list` and `fastmcp call`, so you can query and invoke any MCP server without writing a single line of Python.
These commands are also valuable for LLM-based agents that lack native MCP support. An agent that can execute shell commands can use `fastmcp list --json` to discover available tools and `fastmcp call --json` to invoke them, with structured JSON output designed for programmatic consumption.
## Server Targets
Both commands need to know which server to talk to. You provide a "server spec" as the first argument, and FastMCP figures out the transport automatically. You can point at an HTTP URL for a running server, a Python file that defines one, a JSON configuration file that describes one, or a JavaScript file. The CLI resolves the right connection mechanism so you can focus on the query.
```bash
fastmcp list http://localhost:8000/mcp
fastmcp list server.py
fastmcp list mcp-config.json
```
Python files are handled with particular care. Rather than requiring your script to call `mcp.run()` at the bottom, the CLI routes it through `fastmcp run` internally, which means any Python file that defines a FastMCP server object works as a target with no boilerplate.
For servers that communicate over stdio (common with Node.js-based MCP servers), use the `--command` flag instead of a positional server spec. The string is shell-split into a command and arguments.
```bash
fastmcp list --command 'npx -y @modelcontextprotocol/server-github'
```
### Name-Based Resolution
If your MCP servers are already configured in an editor or tool, you can refer to them by name instead of spelling out URLs or file paths. The CLI scans config files from Claude Desktop, Claude Code, Cursor, Gemini CLI, and Goose, and matches the name you provide.
```bash
fastmcp list weather
fastmcp call weather get_forecast city=London
```
You can also use the `source:name` form to target a specific source directly, which is useful when the same server name appears in multiple configs or when you want to be explicit about which config you mean.
```bash
fastmcp list claude-code:my-server
fastmcp call cursor:weather get_forecast city=London
```
The available source names are `claude-desktop`, `claude-code`, `cursor`, `gemini`, `goose`, and `project` (for `./mcp.json`). Run `fastmcp discover` to see what's available.
## Discovering Configured Servers
`fastmcp discover` scans your local editor and project configurations for MCP server definitions. It checks Claude Desktop, Claude Code (`~/.claude.json`), Cursor workspace configs (walking up from the current directory), Gemini CLI (`~/.gemini/settings.json`), Goose (`~/.config/goose/config.yaml`), and `mcp.json` in the current directory.
```bash
fastmcp discover
```
The output groups servers by source, showing each server's name and transport. Use `--source` to filter to specific sources, and `--json` for machine-readable output.
```bash
fastmcp discover --source claude-code
fastmcp discover --source cursor --source gemini --json
```
Any server that appears here can be used by name (or `source:name`) with `fastmcp list` and `fastmcp call`, which means you can go from "I have a server configured in Claude Code" to querying it without copying any URLs or paths.
## Discovering Tools
`fastmcp list` connects to a server and prints every tool it exposes. The default output is compact: each tool appears as a function signature with its parameter names, types, and a description.
```bash
fastmcp list http://localhost:8000/mcp
```
The output looks like a Python function signature, making it easy to see at a glance what a tool expects and what it returns. Required parameters appear with just their type annotation, while optional ones show their defaults.
When you need the full JSON Schema for a tool's inputs or outputs -- useful for understanding nested object structures or enum constraints -- opt into them with `--input-schema` or `--output-schema`. These print the raw schema beneath each tool signature.
### Beyond Tools
MCP servers can expose resources and prompts alongside tools. By default, `fastmcp list` only shows tools because they are the most common interaction point. Add `--resources` or `--prompts` to include those in the output.
```bash
fastmcp list server.py --resources --prompts
```
Resources appear with their URIs and descriptions. Prompts appear with their argument names so you can see what parameters they accept.
### Machine-Readable Output
The `--json` flag switches from human-friendly text to structured JSON. Each tool includes its name, description, and full input schema (and output schema when present). When combined with `--resources` or `--prompts`, those are included as additional top-level keys.
```bash
fastmcp list server.py --json
```
This is the format to use when building automation around MCP servers or feeding tool definitions to an LLM agent that needs to decide which tool to call.
## Calling Tools
`fastmcp call` invokes a single tool on a server. You provide the server spec, the tool name, and arguments as `key=value` pairs. The CLI fetches the tool's schema, coerces your string values to the correct types (integers, floats, booleans, arrays, objects), and makes the call.
```bash
fastmcp call http://localhost:8000/mcp search query=hello limit=5
```
Type coercion is driven by the tool's JSON Schema. If a parameter is declared as an integer, the string `"5"` becomes the integer `5`. Booleans accept `true`/`false`, `yes`/`no`, and `1`/`0`. Array and object parameters are parsed as JSON.
For tools with complex or deeply nested arguments, the `key=value` syntax gets unwieldy. You can pass a single JSON object as the argument instead, and the CLI treats it as the full input dictionary.
```bash
fastmcp call server.py create_item '{"name": "Widget", "tags": ["sale", "new"], "metadata": {"color": "blue"}}'
```
Alternatively, `--input-json` provides the base argument dictionary. Any `key=value` pairs you add alongside it override keys from the JSON, which is useful for templating a complex call and varying one parameter at a time.
### Error Handling
The CLI validates your call before sending it. If you misspell a tool name, it uses fuzzy matching to suggest corrections. If you omit a required argument, it tells you which ones are missing and prints the tool's signature as a reminder.
When a tool call itself returns an error (the server executed the tool but it failed), the error message is printed and the CLI exits with a non-zero status code, making it straightforward to use in scripts.
### Structured Output
Like `fastmcp list`, the `--json` flag on `fastmcp call` emits structured JSON instead of formatted text. The output includes the content blocks, error status, and structured content when the server provides it. Use this when you need to parse tool results programmatically.
```bash
fastmcp call server.py get_weather city=London --json
```
## Authentication
When the server target is an HTTP URL, the CLI automatically enables OAuth authentication. If the server requires it, you will be guided through the OAuth flow (typically opening a browser for authorization). If the server has no auth requirements, the OAuth setup is a silent no-op.
To explicitly disable authentication -- for example, when connecting to a local development server where OAuth setup would just slow you down -- pass `--auth none`.
```bash
fastmcp call http://localhost:8000/mcp my_tool --auth none
```
## Transport Override
FastMCP defaults to Streamable HTTP for URL targets. If you are connecting to a server that only supports Server-Sent Events (SSE), use `--transport sse` to force the older transport. This appends `/sse` to the URL path automatically so the client picks the correct protocol.
```bash
fastmcp list http://localhost:8000 --transport sse
```
## Interactive Elicitation
Some MCP tools request additional input from the user during execution through a mechanism called elicitation. When a tool sends an elicitation request, the CLI prints the server's question to the terminal and prompts you to respond. Each field in the elicitation schema is presented with its name and expected type, and required fields are clearly marked.
You can type `decline` to skip a question or `cancel` to abort the tool call entirely. This interactive behavior means the CLI works naturally with tools that have multi-step or conversational workflows.
## LLM Agent Integration
For LLM agents that can execute shell commands but lack built-in MCP support, the CLI provides a clean integration path. The agent calls `fastmcp list --json` to get a structured description of every available tool, including full input schemas, and then calls `fastmcp call --json` with the chosen tool and arguments. Both commands return well-formed JSON that is straightforward to parse.
Because the CLI handles connection management, transport selection, and type coercion internally, the agent does not need to understand MCP protocol details. It just needs to read JSON and construct shell commands.

View file

@ -0,0 +1,89 @@
---
title: Client-Only Package
description: Use FastMCP's client without installing the full server framework.
icon: box
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.3.0" />
FastMCP's full `fastmcp` package includes everything needed to build and run MCP servers, apps, proxies, and clients. If you are only embedding an MCP client in another framework, building your own LLM host, or testing MCP servers, you can install the smaller client-only package instead.
```bash
pip install "fastmcp-slim[client]"
```
The client-only package uses the `fastmcp` import namespace:
```python
from fastmcp import Client
client = Client("https://example.com/mcp")
```
Use `fastmcp-slim[client]` when your code connects to MCP servers but does not define or run FastMCP servers itself. For example, framework authors can depend on `fastmcp-slim[client]` to provide MCP connectivity without requiring users to install the full FastMCP server stack.
## Supported Usage
Client-only installs support remote and subprocess transports:
```python
from fastmcp import Client
# Remote MCP server
http_client = Client("https://example.com/mcp")
# Local MCP server over stdio
stdio_client = Client("my_server.py")
```
Single-server MCP configuration works as well:
```python
from fastmcp import Client
config = {
"mcpServers": {
"weather": {
"url": "https://weather.example.com/mcp"
}
}
}
client = Client(config)
```
Optional sampling handlers are available through the same extras as the full package:
```bash
pip install "fastmcp-slim[client,openai]"
pip install "fastmcp-slim[client,anthropic]"
pip install "fastmcp-slim[client,gemini]"
```
## When to Use the Full Package
Install `fastmcp` when you need server-side FastMCP features:
```bash
pip install fastmcp
```
The full package remains the default for most users and continues to support the existing import style:
```python
from fastmcp import Client, FastMCP
server = FastMCP("Example")
client = Client(server)
```
Use the full package for:
- defining or running FastMCP servers
- in-memory clients connected directly to `FastMCP` server objects
- multi-server MCP configurations
- FastMCP apps, proxies, server auth, middleware, and other server-side features
The `fastmcp-slim` package is intentionally narrower: it is for client-only consumers who want FastMCP's MCP client behavior without depending on the full framework.

237
docs/v3/clients/client.mdx Normal file
View file

@ -0,0 +1,237 @@
---
title: The FastMCP Client
sidebarTitle: Overview
description: Programmatic client for interacting with MCP servers through a well-typed, Pythonic interface.
icon: user-robot
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
The `fastmcp.Client` class provides a programmatic interface for interacting with any MCP server. It handles protocol details and connection management automatically, letting you focus on the operations you want to perform.
The FastMCP Client is designed for deterministic, controlled interactions rather than autonomous behavior, making it ideal for testing MCP servers during development, building deterministic applications that need reliable MCP interactions, and creating the foundation for agentic or LLM-based clients with structured, type-safe operations.
<Note>
This is a programmatic client that requires explicit function calls and provides direct control over all MCP operations. Use it as a building block for higher-level systems.
</Note>
## Creating a Client
You provide a server source and the client automatically infers the appropriate transport mechanism.
```python
import asyncio
from fastmcp import Client, FastMCP
# In-memory server (ideal for testing)
server = FastMCP("TestServer")
client = Client(server)
# HTTP server
client = Client("https://example.com/mcp")
# Local Python script
client = Client("my_mcp_server.py")
async def main():
async with client:
# Basic server interaction
await client.ping()
# List available operations
tools = await client.list_tools()
resources = await client.list_resources()
prompts = await client.list_prompts()
# Execute operations
result = await client.call_tool("example_tool", {"param": "value"})
print(result)
asyncio.run(main())
```
All client operations require using the `async with` context manager for proper connection lifecycle management.
## Choosing a Transport
The client automatically selects a transport based on what you pass to it, but different transports have different characteristics that matter for your use case.
**In-memory transport** connects directly to a FastMCP server instance within the same Python process. Use this for testing and development where you want to eliminate subprocess and network complexity. The server shares your process's environment and memory space.
```python
from fastmcp import Client, FastMCP
server = FastMCP("TestServer")
client = Client(server) # In-memory, no network or subprocess
```
**STDIO transport** launches a server as a subprocess and communicates through stdin/stdout pipes. This is the standard mechanism used by desktop clients like Claude Desktop. The subprocess runs in an isolated environment, so you must explicitly pass any environment variables the server needs.
```python
from fastmcp import Client
# Simple inference from file path
client = Client("my_server.py")
# With explicit environment configuration
client = Client("my_server.py", env={"API_KEY": "secret"})
```
**HTTP transport** connects to servers running as web services. Use this for production deployments where the server runs independently and manages its own lifecycle.
```python
from fastmcp import Client
client = Client("https://api.example.com/mcp")
```
See [Transports](/clients/transports) for detailed configuration options including authentication headers, session persistence, and multi-server configurations.
## Configuration-Based Clients
<VersionBadge version="2.4.0" />
Create clients from MCP configuration dictionaries, which can include multiple servers. While there is no official standard for MCP configuration format, FastMCP follows established conventions used by tools like Claude Desktop.
```python
config = {
"mcpServers": {
"weather": {
"url": "https://weather-api.example.com/mcp"
},
"assistant": {
"command": "python",
"args": ["./assistant_server.py"]
}
}
}
client = Client(config)
async with client:
# Tools are prefixed with server names
weather_data = await client.call_tool("weather_get_forecast", {"city": "London"})
response = await client.call_tool("assistant_answer_question", {"question": "What's the capital of France?"})
# Resources use prefixed URIs
icons = await client.read_resource("weather://weather/icons/sunny")
```
## Connection Lifecycle
The client uses context managers for connection management. When you enter the context, the client establishes a connection and performs an MCP initialization handshake with the server. This handshake exchanges capabilities, server metadata, and instructions.
```python
from fastmcp import Client, FastMCP
mcp = FastMCP(name="MyServer", instructions="Use the greet tool to say hello!")
@mcp.tool
def greet(name: str) -> str:
"""Greet a user by name."""
return f"Hello, {name}!"
async with Client(mcp) as client:
# Initialization already happened automatically
print(f"Server: {client.initialize_result.serverInfo.name}")
print(f"Instructions: {client.initialize_result.instructions}")
print(f"Capabilities: {client.initialize_result.capabilities.tools}")
```
For advanced scenarios where you need precise control over when initialization happens, disable automatic initialization and call `initialize()` manually:
```python
from fastmcp import Client
client = Client("my_mcp_server.py", auto_initialize=False)
async with client:
# Connection established, but not initialized yet
print(f"Connected: {client.is_connected()}")
print(f"Initialized: {client.initialize_result is not None}") # False
# Initialize manually with custom timeout
result = await client.initialize(timeout=10.0)
print(f"Server: {result.serverInfo.name}")
# Now ready for operations
tools = await client.list_tools()
```
## Operations
FastMCP clients interact with three types of server components.
**Tools** are server-side functions that the client can execute with arguments. Call them with `call_tool()` and receive structured results.
```python
async with client:
tools = await client.list_tools()
result = await client.call_tool("multiply", {"a": 5, "b": 3})
print(result.data) # 15
```
See [Tools](/clients/tools) for detailed documentation including version selection, error handling, and structured output.
**Resources** are data sources that the client can read, either static or templated. Access them with `read_resource()` using URIs.
```python
async with client:
resources = await client.list_resources()
content = await client.read_resource("file:///config/settings.json")
print(content[0].text)
```
See [Resources](/clients/resources) for detailed documentation including templates and binary content.
**Prompts** are reusable message templates that can accept arguments. Retrieve rendered prompts with `get_prompt()`.
```python
async with client:
prompts = await client.list_prompts()
messages = await client.get_prompt("analyze_data", {"data": [1, 2, 3]})
print(messages.messages)
```
See [Prompts](/clients/prompts) for detailed documentation including argument serialization.
## Callback Handlers
The client supports callback handlers for advanced server interactions. These let you respond to server-initiated requests and receive notifications.
```python
from fastmcp import Client
from fastmcp.client.logging import LogMessage
async def log_handler(message: LogMessage):
print(f"Server log: {message.data}")
async def progress_handler(progress: float, total: float | None, message: str | None):
print(f"Progress: {progress}/{total} - {message}")
async def sampling_handler(messages, params, context):
# Integrate with your LLM service here
return "Generated response"
client = Client(
"my_mcp_server.py",
log_handler=log_handler,
progress_handler=progress_handler,
sampling_handler=sampling_handler,
timeout=30.0
)
```
Each handler type has its own documentation:
- **[Sampling](/clients/sampling)** - Respond to server LLM requests
- **[Elicitation](/clients/elicitation)** - Handle server requests for user input
- **[Progress](/clients/progress)** - Monitor long-running operations
- **[Logging](/clients/logging)** - Handle server log messages
- **[Roots](/clients/roots)** - Provide local context to servers
<Tip>
The FastMCP Client is designed as a foundational tool. Use it directly for deterministic operations, or build higher-level agentic systems on top of its reliable, type-safe interface.
</Tip>

View file

@ -0,0 +1,138 @@
---
title: User Elicitation
sidebarTitle: Elicitation
description: Handle server requests for structured user input.
icon: message-question
---
import { VersionBadge } from "/snippets/version-badge.mdx";
<VersionBadge version="2.10.0" />
Use this when you need to respond to server requests for user input during tool execution.
Elicitation allows MCP servers to request structured input from users during operations. Instead of requiring all inputs upfront, servers can interactively ask for missing parameters, request clarification, or gather additional context.
## Handler Template
```python
from fastmcp import Client
from fastmcp.client.elicitation import ElicitResult, ElicitRequestParams, RequestContext
async def elicitation_handler(
message: str,
response_type: type | None,
params: ElicitRequestParams,
context: RequestContext
) -> ElicitResult | object:
"""
Handle server requests for user input.
Args:
message: The prompt to display to the user
response_type: Python dataclass type for the response (None if no data expected)
params: Original MCP elicitation parameters including raw JSON schema
context: Request context with metadata
Returns:
- Data directly (implicitly accepts the elicitation)
- ElicitResult for explicit control over the action
"""
# Present the message and collect input
user_input = input(f"{message}: ")
if not user_input:
return ElicitResult(action="decline")
# Create response using the provided dataclass type
return response_type(value=user_input)
client = Client(
"my_mcp_server.py",
elicitation_handler=elicitation_handler,
)
```
## How It Works
When a server needs user input, it sends an elicitation request with a message prompt and a JSON schema describing the expected response structure. FastMCP automatically converts this schema into a Python dataclass type, making it easy to construct properly typed responses without manually parsing JSON schemas.
The handler receives four parameters:
<Card icon="code" title="Handler Parameters">
<ResponseField name="message" type="str">
The prompt message to display to the user
</ResponseField>
<ResponseField name="response_type" type="type | None">
A Python dataclass type that FastMCP created from the server's JSON schema. Use this to construct your response with proper typing. If the server requests an empty object, this will be `None`.
</ResponseField>
<ResponseField name="params" type="ElicitRequestParams">
The original MCP elicitation parameters, including the raw JSON schema in `params.requestedSchema`
</ResponseField>
<ResponseField name="context" type="RequestContext">
Request context containing metadata about the elicitation request
</ResponseField>
</Card>
## Response Actions
You can return data directly, which implicitly accepts the elicitation:
```python
async def elicitation_handler(message, response_type, params, context):
user_input = input(f"{message}: ")
return response_type(value=user_input) # Implicit accept
```
Or return an `ElicitResult` for explicit control over the action:
```python
from fastmcp.client.elicitation import ElicitResult
async def elicitation_handler(message, response_type, params, context):
user_input = input(f"{message}: ")
if not user_input:
return ElicitResult(action="decline") # User declined
if user_input == "cancel":
return ElicitResult(action="cancel") # Cancel entire operation
return ElicitResult(
action="accept",
content=response_type(value=user_input)
)
```
**Action types:**
- **`accept`**: User provided valid input. Include the data in the `content` field.
- **`decline`**: User chose not to provide the requested information. Omit `content`.
- **`cancel`**: User cancelled the entire operation. Omit `content`.
## Example
A file management tool might ask which directory to create:
```python
from fastmcp import Client
from fastmcp.client.elicitation import ElicitResult
async def elicitation_handler(message, response_type, params, context):
print(f"Server asks: {message}")
user_response = input("Your response: ")
if not user_response:
return ElicitResult(action="decline")
# Use the response_type dataclass to create a properly structured response
return response_type(value=user_response)
client = Client(
"my_mcp_server.py",
elicitation_handler=elicitation_handler
)
```

View file

@ -0,0 +1,169 @@
---
title: fastmcp-remote
description: Bridge remote MCP servers into stdio-only MCP hosts with uvx fastmcp-remote.
icon: bridge
---
`fastmcp-remote` is FastMCP's standalone stdio bridge for remote MCP servers. Use it when an MCP host expects to launch a local command, but the server you want to use is hosted over Streamable HTTP or SSE.
```json
{
"mcpServers": {
"linear": {
"command": "uvx",
"args": ["fastmcp-remote", "https://mcp.linear.app/mcp"]
}
}
}
```
The package is powered by FastMCP. It builds one FastMCP client for the remote URL, exposes that client as a local stdio proxy, and keeps the executable focused on that bridge. For running Python server files, local project environments, FastMCP config files, and development reload loops, use [`fastmcp run`](/cli/running).
The command shape follows the original [`mcp-remote`](https://github.com/geelen/mcp-remote) npm project, which established this stdio-to-remote bridge pattern for MCP hosts.
## Installation
Most MCP hosts can run `fastmcp-remote` directly through `uvx`, so you usually do not need to install it yourself:
```bash
uvx fastmcp-remote https://example.com/mcp
```
If your host requires an already-installed command, install the package with your Python package manager:
```bash
uv tool install fastmcp-remote
```
## Host Configuration
For hosts that use `mcpServers` JSON configuration, set the command to `uvx` and pass `fastmcp-remote` plus the remote server URL as arguments:
```json
{
"mcpServers": {
"remote-api": {
"command": "uvx",
"args": ["fastmcp-remote", "https://example.com/mcp"]
}
}
}
```
## Endpoint URLs and Connection Status
Pass the full MCP endpoint URL for the remote server. Many FastMCP HTTP servers expose MCP at `/mcp`, so a local development server may need `http://localhost:8000/mcp` rather than `http://localhost:8000`.
`fastmcp-remote` starts a local stdio bridge, then connects to the upstream server when the MCP host initializes that bridge. If the upstream server is unavailable, the URL does not point to an MCP endpoint, or authentication cannot complete, initialization fails and the host should report the remote server as failed. After initialization succeeds, later tool, resource, prompt, and ping requests continue to proxy through the same remote server configuration.
OAuth is enabled automatically for HTTPS servers. The first connection opens the browser-based OAuth flow when the server requires authentication, then stores tokens locally for future runs.
To pass a bearer token or another custom header directly, provide `--header` in `Name: Value` form. The header name ends at the first colon, so values can contain additional colons. Quote the header when the value contains spaces, just like any other shell argument. An `Authorization` header disables OAuth by default:
```json
{
"mcpServers": {
"private-api": {
"command": "uvx",
"args": [
"fastmcp-remote",
"https://example.com/mcp",
"--header",
"Authorization: Bearer <token>"
]
}
}
}
```
Repeat `--header` to send multiple headers:
```bash
uvx fastmcp-remote https://example.com/mcp \
--header "Authorization: Bearer <token>" \
--header "X-Workspace: production" \
--header "X-Client-Name: My MCP Host" \
--header "X-Callback-Url: https://example.com/oauth/callback"
```
Some MCP hosts on Windows have trouble preserving spaces inside command arguments. Put the spaced value in an environment variable and reference it from the header value:
```json
{
"mcpServers": {
"remote-api": {
"command": "uvx",
"args": [
"fastmcp-remote",
"https://example.com/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer <token>"
}
}
}
}
```
For local development servers over plain HTTP, disable OAuth when the server is unauthenticated:
```bash
uvx fastmcp-remote http://localhost:8000/mcp --auth none
```
## Self-Signed Certificates
For servers behind a self-signed certificate, point `--verify` at a CA bundle that trusts the certificate:
```bash
uvx fastmcp-remote https://internal.example.com/mcp --verify /path/to/ca-bundle.pem
```
To disable certificate verification entirely, pass `--verify false`. This is insecure and should only be used for trusted servers on private networks:
```bash
uvx fastmcp-remote https://internal.example.com/mcp --verify false
```
To trust a CA bundle without a flag, set the standard `SSL_CERT_FILE` environment variable, which OpenSSL reads automatically:
```bash
SSL_CERT_FILE=/path/to/ca-bundle.pem uvx fastmcp-remote https://internal.example.com/mcp
```
## OAuth Storage
OAuth tokens are stored under `~/.fastmcp/remote` by default. Set `FASTMCP_REMOTE_CONFIG_DIR` to use another directory:
```bash
FASTMCP_REMOTE_CONFIG_DIR=~/.config/fastmcp-remote uvx fastmcp-remote https://example.com/mcp
```
Use `--resource` to isolate tokens for a particular remote server identity:
```bash
uvx fastmcp-remote https://example.com/mcp --resource example-prod
```
If the remote authorization server requires a fixed callback port or hostname, pass them after the URL:
```bash
uvx fastmcp-remote https://example.com/mcp 3334 --host 127.0.0.1
```
## Options
| Option | Description |
| ------ | ----------- |
| `--transport` | Choose `http` or `sse`. Defaults to `http`. |
| `--header` | Add a header to upstream requests, for example `--header "Authorization: Bearer <token>"`. Values may contain colons. Quote headers whose values contain spaces. Use `${VAR}` to expand environment variables inside values. Repeat for multiple headers. |
| `--auth` | Choose `oauth` or `none`. The default uses OAuth unless an `Authorization` header is provided. |
| `--verify` | Control TLS certificate verification. Pass a path to a CA bundle to trust a self-signed certificate, or `false` to disable verification (insecure). Defaults to verification enabled. |
| `--resource` | Isolate OAuth token storage for a named remote resource. |
| `--host` | Set the OAuth callback hostname. Defaults to `localhost`. |
| `--auth-timeout` | Set how long to wait for the OAuth callback. Defaults to 300 seconds. |
| `--ignore-tool` | Hide tools whose names match a glob pattern. Repeat for multiple patterns. |
| `--debug` | Enable debug logging. |
| `--silent` | Suppress non-critical logs. |

View file

@ -0,0 +1,166 @@
---
title: Generate CLI
sidebarTitle: Generate CLI
description: Turn any MCP server into a standalone, typed command-line tool.
icon: wand-magic-sparkles
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
`fastmcp list` and `fastmcp call` let you poke at a server interactively, but they're developer tools — you always have to spell out the server spec, the tool name, and the arguments. `fastmcp generate-cli` takes the next step: it connects to a server, reads its schemas, and writes a standalone Python script where every tool is a proper subcommand with typed flags, help text, and tab completion. The result is a CLI that feels like it was hand-written for that specific server.
The key insight is that MCP tool schemas already contain everything a CLI framework needs: parameter names, types, descriptions, required/optional status, and defaults. `generate-cli` maps that schema into [cyclopts](https://cyclopts.readthedocs.io/) commands, so JSON Schema types become Python type annotations, descriptions become `--help` text, and required parameters become mandatory flags.
## Generating a Script
Point the command at any server spec — URLs, Python files, discovered server names, MCPConfig JSON — and it writes a CLI script:
```bash
fastmcp generate-cli weather
fastmcp generate-cli http://localhost:8000/mcp
fastmcp generate-cli server.py my_weather_cli.py
```
The second positional argument sets the output path. When omitted, it defaults to `cli.py`. If either the CLI file or its companion `SKILL.md` already exists, the command refuses to overwrite unless you pass `-f`:
```bash
fastmcp generate-cli weather -f
fastmcp generate-cli weather my_cli.py -f
```
Name-based resolution works here too, so if you have a server configured in Claude Desktop, Cursor, or any other supported editor, you can reference it by name. Run [`fastmcp discover`](/clients/cli#discovering-configured-servers) to see what's available.
```bash
fastmcp generate-cli claude-code:my-server output.py
```
The `--timeout` and `--auth` flags work the same way they do in `fastmcp list` and `fastmcp call`.
## What You Get
The generated script is a regular Python file — executable, editable, and yours. Here's what it looks like in practice:
```
$ python cli.py --help
Usage: weather-cli COMMAND
CLI for weather MCP server
Commands:
call-tool Call a tool on the server
list-tools List available tools.
list-resources List available resources.
read-resource Read a resource by URI.
list-prompts List available prompts.
get-prompt Get a prompt by name. Pass arguments as key=value pairs.
```
The `call-tool` subcommand is where the generated code lives. Each tool on the server becomes its own command:
```
$ python cli.py call-tool --help
Usage: weather-cli call-tool COMMAND
Call a tool on the server
Commands:
get_forecast Get the weather forecast for a city.
search_city Search for a city by name.
```
And each tool has typed parameters with help text pulled directly from the server's schema:
```
$ python cli.py call-tool get_forecast --help
Usage: weather-cli call-tool get_forecast [OPTIONS]
Get the weather forecast for a city.
Options:
--city [str] City name (required)
--days [int] Number of forecast days (default: 3)
```
Tool names are preserved exactly as the server defines them — underscores stay as underscores, so `call-tool get_forecast` matches what the server expects.
## Agent Skill
Alongside the CLI script, `generate-cli` also writes a `SKILL.md` file — a [Claude Code agent skill](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/skills) that documents the generated CLI. The skill includes every tool's exact invocation syntax, parameter flags with types and descriptions, and the utility commands, so an agent can use the CLI immediately without running `--help` or experimenting with flag names.
The skill is written to the same directory as the CLI script. For a weather server, it looks something like:
````markdown
---
name: "weather-cli"
description: "CLI for the weather MCP server. Call tools, list resources, and get prompts."
---
# weather CLI
## Tool Commands
### get_forecast
Get the weather forecast for a city.
```bash
uv run --with fastmcp python cli.py call-tool get_forecast --city <value> --days <value>
```
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--city` | string | yes | City name |
| `--days` | integer | no | Number of forecast days |
````
To skip skill generation, pass `--no-skill`:
```bash
fastmcp generate-cli weather --no-skill
```
## How It Works
The generated script is a client, not a server. It doesn't bundle or embed the MCP server — it connects to it on every invocation. For URL-based servers, the server needs to be running. For stdio-based servers, the command specified in `CLIENT_SPEC` must be available on the system's `PATH`.
At the top of the generated file, a `CLIENT_SPEC` variable holds the resolved transport: either a URL string or a `StdioTransport` with the command and arguments baked in. Every invocation connects through this spec, so the script works without any external configuration.
### Parameter Handling
Parameters are mapped intelligently based on their complexity:
**Simple types** (`string`, `integer`, `number`, `boolean`) become typed Python parameters with clean flags:
```bash
python cli.py call-tool get_forecast --city London --days 3
```
**Arrays of simple types** (`array` with `string`/`integer`/`number`/`boolean` items) become `list[T]` parameters that accept multiple flags:
```bash
python cli.py call-tool tag_items --tags python --tags fastapi --tags mcp
```
**Complex types** (objects, nested arrays, or unions) accept JSON strings. The tool's `--help` displays the full JSON schema so you know exactly what structure to pass:
```bash
python cli.py call-tool create_user \
--name John \
--metadata '{"role": "admin", "dept": "engineering"}'
```
Required parameters are mandatory flags; optional ones default to their schema default or `None`. Empty values are filtered out before calling the server.
Beyond tool commands, the script includes generic commands that work regardless of what the server exposes: `list-tools`, `list-resources`, `read-resource`, `list-prompts`, and `get-prompt`. These connect to the server at runtime, so they always reflect the server's current state even if the tools have changed since generation.
## Editing the Output
The most common edit is changing `CLIENT_SPEC`. If you generated from a local dev server and want to point at production, just change the string. If you generated from a discovered name and want to pin the transport, replace it with an explicit URL or `StdioTransport`.
Beyond that, it's a regular Python file. You can add commands, change the output formatting, integrate it into a larger application, or strip out the parts you don't need. The helper functions (`_call_tool`, `_print_tool_result`) are thin wrappers around `fastmcp.Client` that are easy to adapt.
The generated script requires `fastmcp` as a dependency. If the script lives outside a project that already has fastmcp installed, `uv run` is the easiest way to run it without permanent installation:
```bash
uv run --with fastmcp python cli.py call-tool get_forecast --city London
```

View file

@ -0,0 +1,92 @@
---
title: Server Logging
sidebarTitle: Logging
description: Receive and handle log messages from MCP servers.
icon: receipt
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
Use this when you need to capture or process log messages sent by the server.
MCP servers can emit log messages to clients. The client handles these through a log handler callback.
## Log Handler
Provide a `log_handler` function when creating the client:
```python
import logging
from fastmcp import Client
from fastmcp.client.logging import LogMessage
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
LOGGING_LEVEL_MAP = logging.getLevelNamesMapping()
async def log_handler(message: LogMessage):
"""Forward MCP server logs to Python's logging system."""
msg = message.data.get('msg')
extra = message.data.get('extra')
level = LOGGING_LEVEL_MAP.get(message.level.upper(), logging.INFO)
logger.log(level, msg, extra=extra)
client = Client(
"my_mcp_server.py",
log_handler=log_handler,
)
```
The handler receives a `LogMessage` object:
<Card icon="code" title="LogMessage">
<ResponseField name="level" type='Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]'>
The log level
</ResponseField>
<ResponseField name="logger" type="str | None">
The logger name (may be None)
</ResponseField>
<ResponseField name="data" type="dict">
The log payload, containing `msg` and `extra` keys
</ResponseField>
</Card>
## Structured Logs
The `message.data` attribute is a dictionary containing the log payload. This enables structured logging with rich contextual information.
```python
async def detailed_log_handler(message: LogMessage):
msg = message.data.get('msg')
extra = message.data.get('extra')
if message.level == "error":
print(f"ERROR: {msg} | Details: {extra}")
elif message.level == "warning":
print(f"WARNING: {msg} | Details: {extra}")
else:
print(f"{message.level.upper()}: {msg}")
```
This structure is preserved even when logs are forwarded through a FastMCP proxy, making it useful for debugging multi-server applications.
## Default Behavior
If you do not provide a custom `log_handler`, FastMCP's default handler routes server logs to Python's logging system at the appropriate severity level. The MCP levels map as follows: `notice` becomes INFO; `alert` and `emergency` become CRITICAL.
```python
client = Client("my_mcp_server.py")
async with client:
# Server logs are forwarded at proper severity automatically
await client.call_tool("some_tool")
```

View file

@ -0,0 +1,155 @@
---
title: Notifications
sidebarTitle: Notifications
description: Handle server-sent notifications for list changes and other events.
icon: envelope
---
import { VersionBadge } from "/snippets/version-badge.mdx";
<VersionBadge version="2.9.1" />
Use this when you need to react to server-side changes like tool list updates or resource modifications.
MCP servers can send notifications to inform clients about state changes. The message handler provides a unified way to process these notifications.
## Handling Notifications
The simplest approach is a function that receives all messages and filters for the notifications you care about:
```python
from fastmcp import Client
async def message_handler(message):
"""Handle MCP notifications from the server."""
if hasattr(message, 'root'):
method = message.root.method
if method == "notifications/tools/list_changed":
print("Tools have changed - refresh tool cache")
elif method == "notifications/resources/list_changed":
print("Resources have changed")
elif method == "notifications/prompts/list_changed":
print("Prompts have changed")
client = Client(
"my_mcp_server.py",
message_handler=message_handler,
)
```
## MessageHandler Class
For fine-grained targeting, subclass `MessageHandler` to use specific hooks:
```python
from fastmcp import Client
from fastmcp.client.messages import MessageHandler
import mcp.types
class MyMessageHandler(MessageHandler):
async def on_tool_list_changed(
self, notification: mcp.types.ToolListChangedNotification
) -> None:
"""Handle tool list changes."""
print("Tool list changed - refreshing available tools")
async def on_resource_list_changed(
self, notification: mcp.types.ResourceListChangedNotification
) -> None:
"""Handle resource list changes."""
print("Resource list changed")
async def on_prompt_list_changed(
self, notification: mcp.types.PromptListChangedNotification
) -> None:
"""Handle prompt list changes."""
print("Prompt list changed")
client = Client(
"my_mcp_server.py",
message_handler=MyMessageHandler(),
)
```
### Handler Template
```python
from fastmcp.client.messages import MessageHandler
import mcp.types
class MyMessageHandler(MessageHandler):
async def on_message(self, message) -> None:
"""Called for ALL messages (requests and notifications)."""
pass
async def on_notification(
self, notification: mcp.types.ServerNotification
) -> None:
"""Called for notifications (fire-and-forget)."""
pass
async def on_tool_list_changed(
self, notification: mcp.types.ToolListChangedNotification
) -> None:
"""Called when the server's tool list changes."""
pass
async def on_resource_list_changed(
self, notification: mcp.types.ResourceListChangedNotification
) -> None:
"""Called when the server's resource list changes."""
pass
async def on_prompt_list_changed(
self, notification: mcp.types.PromptListChangedNotification
) -> None:
"""Called when the server's prompt list changes."""
pass
async def on_progress(
self, notification: mcp.types.ProgressNotification
) -> None:
"""Called for progress updates during long-running operations."""
pass
async def on_logging_message(
self, notification: mcp.types.LoggingMessageNotification
) -> None:
"""Called for log messages from the server."""
pass
```
## List Change Notifications
A practical example of maintaining a tool cache that refreshes when tools change:
```python
from fastmcp import Client
from fastmcp.client.messages import MessageHandler
import mcp.types
class ToolCacheHandler(MessageHandler):
def __init__(self):
self.cached_tools = []
async def on_tool_list_changed(
self, notification: mcp.types.ToolListChangedNotification
) -> None:
"""Clear tool cache when tools change."""
print("Tools changed - clearing cache")
self.cached_tools = [] # Force refresh on next access
client = Client("server.py", message_handler=ToolCacheHandler())
```
## Server Requests
While the message handler receives server-initiated requests, you should use dedicated callback parameters for most interactive scenarios:
- **Sampling requests**: Use [`sampling_handler`](/clients/sampling)
- **Elicitation requests**: Use [`elicitation_handler`](/clients/elicitation)
- **Progress updates**: Use [`progress_handler`](/clients/progress)
- **Log messages**: Use [`log_handler`](/clients/logging)
The message handler is primarily for monitoring and handling notifications rather than responding to requests.

View file

@ -0,0 +1,67 @@
---
title: Progress Monitoring
sidebarTitle: Progress
description: Handle progress notifications from long-running server operations.
icon: bars-progress
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.3.5" />
Use this when you need to track progress of long-running operations.
MCP servers can report progress during operations. The client receives these updates through a progress handler.
## Progress Handler
Set a handler when creating the client:
```python
from fastmcp import Client
async def progress_handler(
progress: float,
total: float | None,
message: str | None
) -> None:
if total is not None:
percentage = (progress / total) * 100
print(f"Progress: {percentage:.1f}% - {message or ''}")
else:
print(f"Progress: {progress} - {message or ''}")
client = Client(
"my_mcp_server.py",
progress_handler=progress_handler
)
```
The handler receives three parameters:
<Card icon="code" title="Handler Parameters">
<ResponseField name="progress" type="float">
Current progress value
</ResponseField>
<ResponseField name="total" type="float | None">
Expected total value (may be None if unknown)
</ResponseField>
<ResponseField name="message" type="str | None">
Optional status message
</ResponseField>
</Card>
## Per-Call Handler
Override the client-level handler for specific tool calls:
```python
async with client:
result = await client.call_tool(
"long_running_task",
{"param": "value"},
progress_handler=my_progress_handler
)
```

147
docs/v3/clients/prompts.mdx Normal file
View file

@ -0,0 +1,147 @@
---
title: Getting Prompts
sidebarTitle: Prompts
description: Retrieve rendered message templates with automatic argument serialization.
icon: message-lines
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
Use this when you need to retrieve server-defined message templates for LLM interactions.
Prompts are reusable message templates exposed by MCP servers. They can accept arguments to generate personalized message sequences for LLM interactions.
## Basic Usage
Request a rendered prompt with `get_prompt()`:
```python
async with client:
# Simple prompt without arguments
result = await client.get_prompt("welcome_message")
# result -> mcp.types.GetPromptResult
# Access the generated messages
for message in result.messages:
print(f"Role: {message.role}")
print(f"Content: {message.content}")
```
Pass arguments to customize the prompt:
```python
async with client:
result = await client.get_prompt("user_greeting", {
"name": "Alice",
"role": "administrator"
})
for message in result.messages:
print(f"Generated message: {message.content}")
```
## Argument Serialization
<VersionBadge version="2.9.0" />
FastMCP automatically serializes complex arguments to JSON strings as required by the MCP specification. You can pass typed objects directly:
```python
from dataclasses import dataclass
@dataclass
class UserData:
name: str
age: int
async with client:
result = await client.get_prompt("analyze_user", {
"user": UserData(name="Alice", age=30), # Automatically serialized
"preferences": {"theme": "dark"}, # Dict serialized
"scores": [85, 92, 78], # List serialized
"simple_name": "Bob" # Strings unchanged
})
```
The client handles serialization using `pydantic_core.to_json()` for consistent formatting. FastMCP servers automatically deserialize these JSON strings back to the expected types.
## Working with Results
The `get_prompt()` method returns a `GetPromptResult` containing a list of messages:
```python
async with client:
result = await client.get_prompt("conversation_starter", {"topic": "climate"})
for i, message in enumerate(result.messages):
print(f"Message {i + 1}:")
print(f" Role: {message.role}")
print(f" Content: {message.content.text if hasattr(message.content, 'text') else message.content}")
```
Prompts can generate different message types. System messages configure LLM behavior:
```python
async with client:
result = await client.get_prompt("system_configuration", {
"role": "helpful assistant",
"expertise": "python programming"
})
# Access the returned messages
message = result.messages[0]
print(f"Prompt: {message.content}")
```
Conversation templates generate multi-turn flows:
```python
async with client:
result = await client.get_prompt("interview_template", {
"candidate_name": "Alice",
"position": "Senior Developer"
})
# Multiple messages for a conversation flow
for message in result.messages:
print(f"{message.role}: {message.content}")
```
## Version Selection
<VersionBadge version="3.0.0" />
When a server exposes multiple versions of a prompt, you can request a specific version:
```python
async with client:
# Get the highest version (default)
result = await client.get_prompt("summarize", {"text": "..."})
# Get a specific version
result_v1 = await client.get_prompt("summarize", {"text": "..."}, version="1.0")
```
See [Metadata](/servers/versioning#version-discovery) for how to discover available versions.
## Multi-Server Clients
When using multi-server clients, prompts are accessible directly without prefixing:
```python
async with client: # Multi-server client
result1 = await client.get_prompt("weather_prompt", {"city": "London"})
result2 = await client.get_prompt("assistant_prompt", {"query": "help"})
```
## Raw Protocol Access
For complete control, use `get_prompt_mcp()` which returns the full MCP protocol object:
```python
async with client:
result = await client.get_prompt_mcp("example_prompt", {"arg": "value"})
# result -> mcp.types.GetPromptResult
```

View file

@ -0,0 +1,110 @@
---
title: Reading Resources
sidebarTitle: Resources
description: Access static and templated data sources from MCP servers.
icon: folder-open
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
Use this when you need to read data from server-exposed resources like configuration files, generated content, or external data sources.
Resources are data sources exposed by MCP servers. They can be static files with fixed content, or dynamic templates that generate content based on parameters in the URI.
## Reading Resources
Read a resource using its URI:
```python
async with client:
content = await client.read_resource("file:///path/to/README.md")
# content -> list[TextResourceContents | BlobResourceContents]
# Access text content
if hasattr(content[0], 'text'):
print(content[0].text)
# Access binary content
if hasattr(content[0], 'blob'):
print(f"Binary data: {len(content[0].blob)} bytes")
```
Resource templates generate content based on URI parameters. The template defines a pattern like `weather://{{city}}/current`, and you fill in the parameters when reading:
```python
async with client:
# Read from a resource template
weather_content = await client.read_resource("weather://london/current")
print(weather_content[0].text)
```
## Content Types
Resources return different content types depending on what they expose.
Text resources include configuration files, JSON data, and other human-readable content:
```python
async with client:
content = await client.read_resource("resource://config/settings.json")
for item in content:
if hasattr(item, 'text'):
print(f"Text content: {item.text}")
print(f"MIME type: {item.mimeType}")
```
Binary resources include images, PDFs, and other non-text data:
```python
async with client:
content = await client.read_resource("resource://images/logo.png")
for item in content:
if hasattr(item, 'blob'):
print(f"Binary content: {len(item.blob)} bytes")
print(f"MIME type: {item.mimeType}")
# Save to file
with open("downloaded_logo.png", "wb") as f:
f.write(item.blob)
```
## Multi-Server Clients
When using multi-server clients, resource URIs are prefixed with the server name:
```python
async with client: # Multi-server client
weather_icons = await client.read_resource("weather://weather/icons/sunny")
templates = await client.read_resource("resource://assistant/templates/list")
```
## Version Selection
<VersionBadge version="3.0.0" />
When a server exposes multiple versions of a resource, you can request a specific version:
```python
async with client:
# Read the highest version (default)
content = await client.read_resource("data://config")
# Read a specific version
content_v1 = await client.read_resource("data://config", version="1.0")
```
See [Metadata](/servers/versioning#version-discovery) for how to discover available versions.
## Raw Protocol Access
For complete control, use `read_resource_mcp()` which returns the full MCP protocol object:
```python
async with client:
result = await client.read_resource_mcp("resource://example")
# result -> mcp.types.ReadResourceResult
```

45
docs/v3/clients/roots.mdx Normal file
View file

@ -0,0 +1,45 @@
---
title: Client Roots
sidebarTitle: Roots
description: Provide local context and resource boundaries to MCP servers.
icon: folder-tree
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
Use this when you need to tell servers what local resources the client has access to.
Roots inform servers about resources the client can provide. Servers can use this information to adjust behavior or provide more relevant responses.
## Static Roots
Provide a list of roots when creating the client:
```python
from fastmcp import Client
client = Client(
"my_mcp_server.py",
roots=["/path/to/root1", "/path/to/root2"]
)
```
## Dynamic Roots
Use a callback to compute roots dynamically when the server requests them:
```python
from fastmcp import Client
from fastmcp.client.roots import RequestContext
async def roots_callback(context: RequestContext) -> list[str]:
print(f"Server requested roots (Request ID: {context.request_id})")
return ["/path/to/root1", "/path/to/root2"]
client = Client(
"my_mcp_server.py",
roots=roots_callback
)
```

View file

@ -0,0 +1,190 @@
---
title: LLM Sampling
sidebarTitle: Sampling
description: Handle server-initiated LLM completion requests.
icon: robot
---
import { VersionBadge } from "/snippets/version-badge.mdx";
<VersionBadge version="2.0.0" />
Use this when you need to respond to server requests for LLM completions.
MCP servers can request LLM completions from clients during tool execution. This enables servers to delegate AI reasoning to the client, which controls which LLM is used and how requests are made.
## Handler Template
```python
from fastmcp import Client
from fastmcp.client.sampling import SamplingMessage, SamplingParams, RequestContext
async def sampling_handler(
messages: list[SamplingMessage],
params: SamplingParams,
context: RequestContext
) -> str:
"""
Handle server requests for LLM completions.
Args:
messages: Conversation messages to send to the LLM
params: Sampling parameters (temperature, max_tokens, etc.)
context: Request context with metadata
Returns:
Generated text response from your LLM
"""
# Extract message content
conversation = []
for message in messages:
content = message.content.text if hasattr(message.content, 'text') else str(message.content)
conversation.append(f"{message.role}: {content}")
# Use the system prompt if provided
system_prompt = params.systemPrompt or "You are a helpful assistant."
# Integrate with your LLM service here
return "Generated response based on the messages"
client = Client(
"my_mcp_server.py",
sampling_handler=sampling_handler,
)
```
## Handler Parameters
<Card icon="code" title="SamplingMessage">
<ResponseField name="role" type='Literal["user", "assistant"]'>
The role of the message
</ResponseField>
<ResponseField name="content" type="TextContent | ImageContent | AudioContent">
The content of the message. TextContent has a `.text` attribute.
</ResponseField>
</Card>
<Card icon="code" title="SamplingParams">
<ResponseField name="systemPrompt" type="str | None">
Optional system prompt the server wants to use
</ResponseField>
<ResponseField name="modelPreferences" type="ModelPreferences | None">
Server preferences for model selection (hints, cost/speed/intelligence priorities)
</ResponseField>
<ResponseField name="temperature" type="float | None">
Sampling temperature
</ResponseField>
<ResponseField name="maxTokens" type="int">
Maximum tokens to generate
</ResponseField>
<ResponseField name="stopSequences" type="list[str] | None">
Stop sequences for sampling
</ResponseField>
<ResponseField name="tools" type="list[Tool] | None">
Tools the LLM can use during sampling
</ResponseField>
<ResponseField name="toolChoice" type="ToolChoice | None">
Tool usage behavior (`auto`, `required`, or `none`)
</ResponseField>
</Card>
## Built-in Handlers
FastMCP provides built-in handlers for OpenAI, Anthropic, and Google Gemini APIs that support the full sampling API including tool use.
### OpenAI Handler
<VersionBadge version="2.11.0" />
```python
from fastmcp import Client
from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler
client = Client(
"my_mcp_server.py",
sampling_handler=OpenAISamplingHandler(default_model="gpt-4o"),
)
```
For OpenAI-compatible APIs (like local models):
```python
from openai import AsyncOpenAI
client = Client(
"my_mcp_server.py",
sampling_handler=OpenAISamplingHandler(
default_model="llama-3.1-70b",
client=AsyncOpenAI(base_url="http://localhost:8000/v1"),
),
)
```
<Note>
Install the OpenAI handler with `pip install fastmcp[openai]`.
</Note>
### Anthropic Handler
<VersionBadge version="2.14.1" />
```python
from fastmcp import Client
from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler
client = Client(
"my_mcp_server.py",
sampling_handler=AnthropicSamplingHandler(default_model="claude-sonnet-4-5"),
)
```
<Note>
Install the Anthropic handler with `pip install fastmcp[anthropic]`.
</Note>
### Google Gemini Handler
<VersionBadge version="3.1.0" />
```python
from fastmcp import Client
from fastmcp.client.sampling.handlers.google_genai import GoogleGenaiSamplingHandler
client = Client(
"my_mcp_server.py",
sampling_handler=GoogleGenaiSamplingHandler(default_model="gemini-2.0-flash"),
)
```
<Note>
Install the Google Gemini handler with `pip install fastmcp[gemini]`.
</Note>
## Sampling Capabilities
When you provide a `sampling_handler`, FastMCP automatically advertises full sampling capabilities to the server, including tool support. To disable tool support for simpler handlers:
```python
from mcp.types import SamplingCapability
client = Client(
"my_mcp_server.py",
sampling_handler=basic_handler,
sampling_capabilities=SamplingCapability(), # No tool support
)
```
## Tool Execution
Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results.
<Tip>
To implement a custom sampling handler, see the [handler source code](https://github.com/PrefectHQ/fastmcp/tree/main/fastmcp_slim/fastmcp/client/sampling/handlers) as a reference.
</Tip>

182
docs/v3/clients/tasks.mdx Normal file
View file

@ -0,0 +1,182 @@
---
title: Background Tasks
sidebarTitle: Tasks
description: Execute operations asynchronously and track their progress.
icon: clock
tag: "NEW"
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.14.0" />
Use this when you need to run long operations asynchronously while doing other work.
The MCP task protocol lets you request operations to run in the background. The call returns a Task object immediately, letting you track progress, cancel operations, or await results.
## Requesting Background Execution
Pass `task=True` to run an operation as a background task:
```python
from fastmcp import Client
async with Client(server) as client:
# Start a background task
task = await client.call_tool("slow_computation", {"duration": 10}, task=True)
print(f"Task started: {task.task_id}")
# Do other work while it runs...
# Get the result when ready
result = await task.result()
```
This works with tools, resources, and prompts:
```python
tool_task = await client.call_tool("my_tool", args, task=True)
resource_task = await client.read_resource("file://large.txt", task=True)
prompt_task = await client.get_prompt("my_prompt", args, task=True)
```
## Task API
All task types share a common interface.
### Getting Results
Call `await task.result()` or simply `await task` to block until the task completes:
```python
task = await client.call_tool("analyze", {"text": "hello"}, task=True)
# Wait for result (blocking)
result = await task.result()
# or: result = await task
```
### Checking Status
Check the current status without blocking:
```python
status = await task.status()
print(f"{status.status}: {status.statusMessage}")
# status.status is "working", "completed", "failed", or "cancelled"
```
### Waiting with Control
Use `task.wait()` for more control over waiting:
```python
# Wait up to 30 seconds for completion
status = await task.wait(timeout=30.0)
# Wait for a specific state
status = await task.wait(state="completed", timeout=30.0)
```
### Cancellation
Cancel a running task:
```python
await task.cancel()
```
## Status Updates
Register callbacks to receive real-time status updates as the server reports progress:
```python
def on_status_change(status):
print(f"Task {status.taskId}: {status.status} - {status.statusMessage}")
task.on_status_change(on_status_change)
# Async callbacks work too
async def on_status_async(status):
await log_status(status)
task.on_status_change(on_status_async)
```
### Handler Template
```python
from fastmcp import Client
def status_handler(status):
"""
Handle task status updates.
Args:
status: Task status object with:
- taskId: Unique task identifier
- status: "working", "completed", "failed", or "cancelled"
- statusMessage: Optional progress message from server
"""
if status.status == "working":
print(f"Progress: {status.statusMessage}")
elif status.status == "completed":
print("Task completed")
elif status.status == "failed":
print(f"Task failed: {status.statusMessage}")
task.on_status_change(status_handler)
```
## Graceful Degradation
You can always pass `task=True` regardless of whether the server supports background tasks. Per the MCP specification, servers without task support execute the operation immediately and return the result inline.
```python
task = await client.call_tool("my_tool", args, task=True)
if task.returned_immediately:
print("Server executed immediately (no background support)")
else:
print("Running in background")
# Either way, this works
result = await task.result()
```
This lets you write task-aware client code without worrying about server capabilities.
## Example
```python
import asyncio
from fastmcp import Client
async def main():
async with Client(server) as client:
# Start background task
task = await client.call_tool(
"slow_computation",
{"duration": 10},
task=True,
)
# Subscribe to updates
def on_update(status):
print(f"Progress: {status.statusMessage}")
task.on_status_change(on_update)
# Do other work while task runs
print("Doing other work...")
await asyncio.sleep(2)
# Wait for completion and get result
result = await task.result()
print(f"Result: {result.content}")
asyncio.run(main())
```
See [Server Background Tasks](/servers/tasks) for how to enable background task support on the server side.

183
docs/v3/clients/tools.mdx Normal file
View file

@ -0,0 +1,183 @@
---
title: Calling Tools
sidebarTitle: Tools
description: Execute server-side tools and handle structured results.
icon: wrench
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
Use this when you need to execute server-side functions and process their results.
Tools are executable functions exposed by MCP servers. The client's `call_tool()` method executes a tool by name with arguments and returns structured results.
## Basic Execution
```python
async with client:
result = await client.call_tool("add", {"a": 5, "b": 3})
# result -> CallToolResult with structured and unstructured data
# Access structured data (automatically deserialized)
print(result.data) # 8
# Access traditional content blocks
print(result.content[0].text) # "8"
```
Arguments are passed as a dictionary. For multi-server clients, tool names are automatically prefixed with the server name (e.g., `weather_get_forecast` for a tool named `get_forecast` on the `weather` server).
## Execution Options
The `call_tool()` method supports timeout control and progress monitoring:
```python
async with client:
# With timeout (aborts if execution takes longer than 2 seconds)
result = await client.call_tool(
"long_running_task",
{"param": "value"},
timeout=2.0
)
# With progress handler
result = await client.call_tool(
"long_running_task",
{"param": "value"},
progress_handler=my_progress_handler
)
```
## Structured Results
<VersionBadge version="2.10.0" />
Tool execution returns a `CallToolResult` object. The `.data` property provides fully hydrated Python objects including complex types like datetimes and UUIDs, reconstructed from the server's output schema.
```python
from datetime import datetime
from uuid import UUID
async with client:
result = await client.call_tool("get_weather", {"city": "London"})
# FastMCP reconstructs complete Python objects
weather = result.data
print(f"Temperature: {weather.temperature}C at {weather.timestamp}")
# Complex types are properly deserialized
assert isinstance(weather.timestamp, datetime)
assert isinstance(weather.station_id, UUID)
# Raw structured JSON is also available
print(f"Raw JSON: {result.structured_content}")
```
<Card icon="code" title="CallToolResult Properties">
<ResponseField name=".data" type="Any">
Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). FastMCP exclusive.
</ResponseField>
<ResponseField name=".content" type="list[mcp.types.ContentBlock]">
Standard MCP content blocks (`TextContent`, `ImageContent`, `AudioContent`, etc.).
</ResponseField>
<ResponseField name=".structured_content" type="dict[str, Any] | None">
Standard MCP structured JSON data as sent by the server.
</ResponseField>
<ResponseField name=".is_error" type="bool">
Boolean indicating if the tool execution failed.
</ResponseField>
</Card>
For tools without output schemas or when deserialization fails, `.data` will be `None`. Fall back to content blocks in that case:
```python
async with client:
result = await client.call_tool("legacy_tool", {"param": "value"})
if result.data is not None:
print(f"Structured: {result.data}")
else:
for content in result.content:
if hasattr(content, 'text'):
print(f"Text result: {content.text}")
```
<Tip>
FastMCP servers automatically wrap primitive results (like `int`, `str`, `bool`) in a `{"result": value}` structure. FastMCP clients automatically unwrap this, so you get the original value in `.data`.
</Tip>
## Error Handling
By default, `call_tool()` raises a `ToolError` if the tool execution fails:
```python
from fastmcp.exceptions import ToolError
async with client:
try:
result = await client.call_tool("potentially_failing_tool", {"param": "value"})
print("Tool succeeded:", result.data)
except ToolError as e:
print(f"Tool failed: {e}")
```
To handle errors manually instead of catching exceptions, disable automatic error raising:
```python
async with client:
result = await client.call_tool(
"potentially_failing_tool",
{"param": "value"},
raise_on_error=False
)
if result.is_error:
print(f"Tool failed: {result.content[0].text}")
else:
print(f"Tool succeeded: {result.data}")
```
## Sending Metadata
<VersionBadge version="2.13.1" />
The `meta` parameter sends ancillary information alongside tool calls for observability, debugging, or client identification:
```python
async with client:
result = await client.call_tool(
name="send_email",
arguments={
"to": "user@example.com",
"subject": "Hello",
"body": "Welcome!"
},
meta={
"trace_id": "abc-123",
"request_source": "mobile_app"
}
)
```
See [Client Metadata](/servers/context#client-metadata) to learn how servers access this data.
## Raw Protocol Access
For complete control, use `call_tool_mcp()` which returns the raw MCP protocol object:
```python
async with client:
result = await client.call_tool_mcp("my_tool", {"param": "value"})
# result -> mcp.types.CallToolResult
if result.isError:
print(f"Tool failed: {result.content}")
else:
print(f"Tool succeeded: {result.content}")
# Note: No automatic deserialization with call_tool_mcp()
```

View file

@ -0,0 +1,267 @@
---
title: Client Transports
sidebarTitle: Transports
description: Configure how clients connect to and communicate with MCP servers.
icon: link
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.0.0" />
Transports handle the underlying connection between your client and MCP servers. While the client can automatically select a transport based on what you pass to it, instantiating transports explicitly gives you full control over configuration.
## STDIO Transport
STDIO transport communicates with MCP servers through subprocess pipes. When using STDIO, your client launches and manages the server process, controlling its lifecycle and environment.
<Warning>
STDIO servers run in isolated environments by default. They do not inherit your shell's environment variables. You must explicitly pass any configuration the server needs.
</Warning>
```python
from fastmcp import Client
from fastmcp.client.transports import StdioTransport
transport = StdioTransport(
command="python",
args=["my_server.py", "--verbose"],
env={"API_KEY": "secret", "LOG_LEVEL": "DEBUG"},
cwd="/path/to/server"
)
client = Client(transport)
```
For convenience, the client can infer STDIO transport from file paths, though this limits configuration options:
```python
from fastmcp import Client
client = Client("my_server.py") # Limited - no configuration options
```
### Environment Variables
Since STDIO servers do not inherit your environment, you need strategies for passing configuration.
**Selective forwarding** passes only the variables your server needs:
```python
import os
from fastmcp.client.transports import StdioTransport
required_vars = ["API_KEY", "DATABASE_URL", "REDIS_HOST"]
env = {var: os.environ[var] for var in required_vars if var in os.environ}
transport = StdioTransport(command="python", args=["server.py"], env=env)
client = Client(transport)
```
**Loading from .env files** keeps configuration separate from code:
```python
from dotenv import dotenv_values
from fastmcp.client.transports import StdioTransport
env = dotenv_values(".env")
transport = StdioTransport(command="python", args=["server.py"], env=env)
client = Client(transport)
```
### Session Persistence
STDIO transports maintain sessions across multiple client contexts by default (`keep_alive=True`). This reuses the same subprocess for multiple connections, improving performance.
```python
from fastmcp.client.transports import StdioTransport
transport = StdioTransport(command="python", args=["server.py"])
client = Client(transport)
async def efficient_multiple_operations():
async with client:
await client.ping()
async with client: # Reuses the same subprocess
await client.call_tool("process_data", {"file": "data.csv"})
```
For complete isolation between connections, disable session persistence:
```python
transport = StdioTransport(command="python", args=["server.py"], keep_alive=False)
```
## HTTP Transport
<VersionBadge version="2.3.0" />
HTTP transport connects to MCP servers running as web services. This is the recommended transport for production deployments.
```python
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport
transport = StreamableHttpTransport(
url="https://api.example.com/mcp",
headers={
"Authorization": "Bearer your-token-here",
"X-Custom-Header": "value"
}
)
client = Client(transport)
```
FastMCP also provides authentication helpers:
```python
from fastmcp import Client
from fastmcp.client.auth import BearerAuth
client = Client(
"https://api.example.com/mcp",
auth=BearerAuth("your-token-here")
)
```
### SSL Verification
By default, HTTPS connections verify the server's SSL certificate. You can customize this behavior with the `verify` parameter, which accepts the same values as [httpx](https://www.python-httpx.org/advanced/ssl/):
```python
from fastmcp import Client
# Disable SSL verification (e.g., for self-signed certs in development)
client = Client("https://dev-server.internal/mcp", verify=False)
# Use a custom CA bundle
client = Client("https://corp-server.internal/mcp", verify="/path/to/ca-bundle.pem")
# Use a custom SSL context for full control
import ssl
ctx = ssl.create_default_context()
ctx.load_verify_locations("/path/to/internal-ca.pem")
client = Client("https://corp-server.internal/mcp", verify=ctx)
```
The `verify` parameter is also available directly on `StreamableHttpTransport` and `SSETransport`:
```python
from fastmcp.client.transports import StreamableHttpTransport
transport = StreamableHttpTransport(
url="https://dev-server.internal/mcp",
verify=False,
)
client = Client(transport)
```
### SSE Transport
Server-Sent Events transport is maintained for backward compatibility. Use Streamable HTTP for new deployments unless you have specific infrastructure requirements.
```python
from fastmcp.client.transports import SSETransport
transport = SSETransport(
url="https://api.example.com/sse",
headers={"Authorization": "Bearer token"}
)
client = Client(transport)
```
## In-Memory Transport
In-memory transport connects directly to a FastMCP server instance within the same Python process. This eliminates both subprocess management and network overhead, making it ideal for testing.
```python
from fastmcp import FastMCP, Client
import os
mcp = FastMCP("TestServer")
@mcp.tool
def greet(name: str) -> str:
prefix = os.environ.get("GREETING_PREFIX", "Hello")
return f"{prefix}, {name}!"
client = Client(mcp)
async with client:
result = await client.call_tool("greet", {"name": "World"})
```
<Note>
Unlike STDIO transports, in-memory servers share the same memory space and environment variables as your client code.
</Note>
## Multi-Server Configuration
<VersionBadge version="2.4.0" />
Connect to multiple servers defined in a configuration dictionary:
```python
from fastmcp import Client
config = {
"mcpServers": {
"weather": {
"url": "https://weather.example.com/mcp",
"transport": "http"
},
"assistant": {
"command": "python",
"args": ["./assistant.py"],
"env": {"LOG_LEVEL": "INFO"}
}
}
}
client = Client(config)
async with client:
# Tools are namespaced by server
weather = await client.call_tool("weather_get_forecast", {"city": "NYC"})
answer = await client.call_tool("assistant_ask", {"question": "What?"})
```
### Tool Transformations
FastMCP supports tool transformations within the configuration. You can change names, descriptions, tags, and arguments for tools from a server.
```python
config = {
"mcpServers": {
"weather": {
"url": "https://weather.example.com/mcp",
"transport": "http",
"tools": {
"weather_get_forecast": {
"name": "miami_weather",
"description": "Get the weather for Miami",
"arguments": {
"city": {
"default": "Miami",
"hide": True,
}
}
}
}
}
}
}
```
To filter tools by tag, use `include_tags` or `exclude_tags` at the server level:
```python
config = {
"mcpServers": {
"weather": {
"url": "https://weather.example.com/mcp",
"include_tags": ["forecast"] # Only tools with this tag
}
}
}
```

View file

@ -0,0 +1,22 @@
# Community Section
This directory contains community-contributed content and showcases for FastMCP.
## Structure
- `showcase.mdx` - Main community showcase page featuring high-quality projects and examples
## Adding Content
To add new community content:
1. Create a new MDX file in this directory
2. Update `docs.json` to include it in the navigation
3. Follow the existing format for consistency
## Guidelines
Community content should:
- Demonstrate best practices
- Provide educational value
- Include proper documentation
- Be maintained and up-to-date

View file

@ -0,0 +1,65 @@
---
title: 'Community Showcase'
description: 'High-quality projects and examples from the FastMCP community'
icon: 'users'
---
import { YouTubeEmbed } from '/snippets/youtube-embed.mdx'
## Join the Community
<Card title="FastMCP Discord" icon="discord" href="https://discord.gg/uu8dJCgttd">
Connect with other FastMCP developers, share your projects, and discuss ideas.
</Card>
## Featured Projects
Discover exemplary MCP servers and implementations created by our community. These projects demonstrate best practices and innovative uses of FastMCP.
### Learning Resources
<Card title="MCP Dummy Server" icon="graduation-cap" href="https://github.com/WaiYanNyeinNaing/mcp-dummy-server">
A comprehensive educational example demonstrating FastMCP best practices with professional dual-transport server implementation, interactive test client, and detailed documentation.
</Card>
#### Video Tutorials
**Build Remote MCP Servers w/ Python & FastMCP** - Claude Integrations Tutorial by Greg + Code
<YouTubeEmbed
videoId="bOYkbXP-GGo"
title="Build Remote MCP Servers w/ Python & FastMCP"
/>
**FastMCP — the best way to build an MCP server with Python** - Tutorial by ZazenCodes
<YouTubeEmbed
videoId="rnljvmHorQw"
title="FastMCP — the best way to build an MCP server with Python"
/>
**Speedrun a MCP server for Claude Desktop (fastmcp)** - Tutorial by Nate from Prefect
<YouTubeEmbed
videoId="67ZwpkUEtSI"
title="Speedrun a MCP server for Claude Desktop (fastmcp)"
/>
### Community Examples
Have you built something interesting with FastMCP? We'd love to feature high-quality examples here! Start a [discussion on GitHub](https://github.com/PrefectHQ/fastmcp/discussions) to share your project.
## Contributing
To get your project featured:
1. Ensure your project demonstrates best practices
2. Include comprehensive documentation
3. Add clear usage examples
4. Open a discussion in our [GitHub Discussions](https://github.com/PrefectHQ/fastmcp/discussions)
We review submissions regularly and feature projects that provide value to the FastMCP community.
## Further Reading
- [Contrib Modules](/patterns/contrib) - Community-contributed modules that are distributed with FastMCP itself

924
docs/v3/deployment/http.mdx Normal file
View file

@ -0,0 +1,924 @@
---
title: HTTP Deployment
sidebarTitle: HTTP Deployment
description: Deploy your FastMCP server over HTTP for remote access
icon: server
---
import { VersionBadge } from "/snippets/version-badge.mdx";
<Tip>
STDIO transport is perfect for local development and desktop applications. But to unlock the full potential of MCP—centralized services, multi-client access, and network availability—you need remote HTTP deployment.
</Tip>
This guide walks you through deploying your FastMCP server as a remote MCP service that's accessible via a URL. Once deployed, your MCP server will be available over the network, allowing multiple clients to connect simultaneously and enabling integration with cloud-based LLM applications. This guide focuses specifically on remote MCP deployment, not local STDIO servers.
## Choosing Your Approach
FastMCP provides two ways to deploy your server as an HTTP service. Understanding the trade-offs helps you choose the right approach for your needs.
The **direct HTTP server** approach is simpler and perfect for getting started quickly. You modify your server's `run()` method to use HTTP transport, and FastMCP handles all the web server configuration. This approach works well for standalone deployments where you want your MCP server to be the only service running on a port.
The **ASGI application** approach gives you more control and flexibility. Instead of running the server directly, you create an ASGI application that can be served by Uvicorn. This approach is better when you need advanced server features like multiple workers, custom middleware, or when you're integrating with existing web applications.
### Direct HTTP Server
The simplest way to get your MCP server online is to use the built-in `run()` method with HTTP transport. This approach handles all the server configuration for you and is ideal when you want a standalone MCP server without additional complexity.
```python server.py
from fastmcp import FastMCP
mcp = FastMCP("My Server")
@mcp.tool
def process_data(input: str) -> str:
"""Process data on the server"""
return f"Processed: {input}"
if __name__ == "__main__":
mcp.run(transport="http", host="0.0.0.0", port=8000)
```
Run your server with a simple Python command:
```bash
python server.py
```
Your server is now accessible at `http://localhost:8000/mcp` (or use your server's actual IP address for remote access).
This approach is ideal when you want to get online quickly with minimal configuration. It's perfect for internal tools, development environments, or simple deployments where you don't need advanced server features. The built-in server handles all the HTTP details, letting you focus on your MCP implementation.
### ASGI Application
For production deployments, you'll often want more control over how your server runs. FastMCP can create a standard ASGI application that works with any ASGI server like Uvicorn, Gunicorn, or Hypercorn. This approach is particularly useful when you need to configure advanced server options, run multiple workers, or integrate with existing infrastructure.
```python app.py
from fastmcp import FastMCP
mcp = FastMCP("My Server")
@mcp.tool
def process_data(input: str) -> str:
"""Process data on the server"""
return f"Processed: {input}"
# Create ASGI application
app = mcp.http_app()
```
Run with any ASGI server - here's an example with Uvicorn:
```bash
uvicorn app:app --host 0.0.0.0 --port 8000
```
Your server is accessible at the same URL: `http://localhost:8000/mcp` (or use your server's actual IP address for remote access).
The ASGI approach shines in production environments where you need reliability and performance. You can run multiple worker processes to handle concurrent requests, add custom middleware for logging or monitoring, integrate with existing deployment pipelines, or mount your MCP server as part of a larger application.
## Configuring Your Server
### Custom Path
By default, your MCP server is accessible at `/mcp/` on your domain. You can customize this path to fit your URL structure or avoid conflicts with existing endpoints. This is particularly useful when integrating MCP into an existing application or following specific API conventions.
```python
# Option 1: With mcp.run()
mcp.run(transport="http", host="0.0.0.0", port=8000, path="/api/mcp/")
# Option 2: With ASGI app
app = mcp.http_app(path="/api/mcp/")
```
Now your server is accessible at `http://localhost:8000/api/mcp/`.
### Authentication
<Warning>
Authentication is **highly recommended** for remote MCP servers. Some LLM clients require authentication for remote servers and will refuse to connect without it.
</Warning>
FastMCP supports multiple authentication methods to secure your remote server. See the [Authentication Overview](/servers/auth/authentication) for complete configuration options including Bearer tokens, JWT, and OAuth.
If you're mounting an authenticated server under a path prefix, see [Mounting Authenticated Servers](#mounting-authenticated-servers) below for important routing considerations.
### Host and Origin Protection
FastMCP can validate `Host` and browser `Origin` headers for Streamable HTTP requests before they reach MCP session handling. This request guard protects localhost-bound servers from DNS rebinding attacks, and it remains opt-in in FastMCP 3.x to preserve compatibility with existing ASGI, serverless, and reverse-proxy deployments.
Think of this as a request guard rather than CORS middleware. It decides whether a request can reach MCP session handling. CORS remains a separate browser response-header policy; configure CORS middleware separately when browser JavaScript must read cross-origin responses.
Enable strict validation with `host_origin_protection=True`. When you deploy behind a public hostname, add the hostname clients use to reach your MCP endpoint. If a browser-based MCP client runs on a separate origin, add that origin as well:
```python
from fastmcp import FastMCP
mcp = FastMCP("My Server")
app = mcp.http_app(
host_origin_protection=True,
allowed_hosts=["mcp.example.com"],
allowed_origins=["https://app.example.com"],
)
```
For the direct server approach, pass the same values to `run()`:
```python
from fastmcp import FastMCP
mcp = FastMCP("My Server")
if __name__ == "__main__":
mcp.run(
transport="http",
host="0.0.0.0",
port=8000,
host_origin_protection=True,
allowed_hosts=["mcp.example.com"],
allowed_origins=["https://app.example.com"],
)
```
You can also configure these values with environment variables:
```bash
export FASTMCP_HTTP_HOST_ORIGIN_PROTECTION=true
export FASTMCP_HTTP_ALLOWED_HOSTS='["mcp.example.com"]'
export FASTMCP_HTTP_ALLOWED_ORIGINS='["https://app.example.com"]'
```
Use `host_origin_protection="auto"` to protect localhost-bound direct servers while allowing ASGI, serverless, and reverse-proxy deployments to keep their existing Host handling unless they configure explicit trust rules. Use `host_origin_protection=False` to keep the request guard disabled.
### Health Checks
Health check endpoints are essential for monitoring your deployed server and ensuring it's responding correctly. FastMCP allows you to add custom routes alongside your MCP endpoints, making it easy to implement health checks that work with both deployment approaches.
```python
from starlette.responses import JSONResponse
@mcp.custom_route("/health", methods=["GET"])
async def health_check(request):
return JSONResponse({"status": "healthy", "service": "mcp-server"})
```
This health endpoint will be available at `http://localhost:8000/health` and can be used by load balancers, monitoring systems, or deployment platforms to verify your server is running.
<Note>
Custom routes are never protected by the server's authentication middleware, even when an `AuthProvider` is configured. This is by design — the primary use case for custom routes is unauthenticated operational endpoints like health checks and readiness probes. If you need authenticated HTTP endpoints alongside your MCP server, [mount it in a FastAPI app](/integrations/fastapi) and use FastAPI's `Depends()` for auth on your routes.
</Note>
### Custom Middleware
<VersionBadge version="2.3.2" />
Add custom Starlette middleware to your FastMCP ASGI apps:
```python
from fastmcp import FastMCP
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
# Create your FastMCP server
mcp = FastMCP("MyServer")
# Define middleware
middleware = [
Middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
]
# Create ASGI app with middleware
http_app = mcp.http_app(middleware=middleware)
```
### CORS for Browser-Based Clients
<Tip>
Most MCP clients, including those that you access through a browser like ChatGPT or Claude, don't need CORS configuration. Only enable CORS if you're working with an MCP client that connects directly from a browser, such as debugging tools or inspectors.
</Tip>
CORS (Cross-Origin Resource Sharing) is needed when JavaScript running in a web browser connects directly to your MCP server. This is different from using an LLM through a browser—in that case, the browser connects to the LLM service, and the LLM service connects to your MCP server (no CORS needed).
Host and Origin protection runs before CORS when it is active for a request. Add browser client origins to `allowed_origins` so trusted browser requests reach the CORS middleware, then configure CORS to let browser JavaScript read the MCP response headers it needs. Setting `allowed_origins` trusts the request; it does not emit `Access-Control-Allow-Origin` or other CORS response headers.
Browser-based MCP clients that need CORS include:
- **MCP Inspector** - Browser-based debugging tool for testing MCP servers
- **Custom browser-based MCP clients** - If you're building a web app that directly connects to MCP servers
For these scenarios, add CORS middleware with the specific headers required for MCP protocol:
```python
from fastmcp import FastMCP
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
mcp = FastMCP("MyServer")
# Configure CORS for browser-based clients
middleware = [
Middleware(
CORSMiddleware,
allow_origins=["*"], # Allow all origins; use specific origins for security
allow_methods=["GET", "POST", "DELETE", "OPTIONS"],
allow_headers=[
"mcp-protocol-version",
"mcp-session-id",
"Authorization",
"Content-Type",
],
expose_headers=["mcp-session-id"],
)
]
app = mcp.http_app(middleware=middleware)
```
**Key configuration details:**
- **`allow_origins`**: Specify exact origins (e.g., `["http://localhost:3000"]`) rather than `["*"]` for production deployments
- **`allow_headers`**: Must include `mcp-protocol-version`, `mcp-session-id`, and `Authorization` (for authenticated servers)
- **`expose_headers`**: Must include `mcp-session-id` so JavaScript can read the session ID from responses and send it in subsequent requests
Without `expose_headers=["mcp-session-id"]`, browsers will receive the session ID but JavaScript won't be able to access it, causing session management to fail.
<Warning>
**Production Security**: Never use `allow_origins=["*"]` in production. Specify the exact origins of your browser-based clients. Using wildcards exposes your server to unauthorized access from any website.
</Warning>
### SSE Polling for Long-Running Operations
<VersionBadge version="2.14.0" />
<Note>
This feature only applies to the **StreamableHTTP transport** (the default for `http_app()`). It does not apply to the legacy SSE transport (`transport="sse"`).
</Note>
When running tools that take a long time to complete, you may encounter issues with load balancers or proxies terminating connections that stay idle too long. [SEP-1699](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699) introduces SSE polling to solve this by allowing the server to gracefully close connections and have clients automatically reconnect.
To enable SSE polling, configure an `EventStore` when creating your HTTP application:
```python
from fastmcp import FastMCP, Context
from fastmcp.server.event_store import EventStore
mcp = FastMCP("My Server")
@mcp.tool
async def long_running_task(ctx: Context) -> str:
"""A task that takes several minutes to complete."""
for i in range(100):
await ctx.report_progress(i, 100)
# Periodically close the connection to avoid load balancer timeouts
# Client will automatically reconnect and resume receiving progress
if i % 30 == 0 and i > 0:
await ctx.close_sse_stream()
await do_expensive_work()
return "Done!"
# Configure with EventStore for resumability
event_store = EventStore()
app = mcp.http_app(
event_store=event_store,
retry_interval=2000, # Client reconnects after 2 seconds
)
```
**How it works:**
1. When `event_store` is configured, the server stores all events (progress updates, results) with unique IDs
2. Calling `ctx.close_sse_stream()` gracefully closes the HTTP connection
3. The client automatically reconnects with a `Last-Event-ID` header
4. The server replays any events the client missed during the disconnection
The `retry_interval` parameter (in milliseconds) controls how long clients wait before reconnecting. Choose a value that balances responsiveness with server load.
<Note>
`close_sse_stream()` is a no-op if called without an `EventStore` configured, so you can safely include it in tools that may run in different deployment configurations.
</Note>
#### Custom Storage Backends
By default, `EventStore` uses in-memory storage. For production deployments with multiple server instances, you can provide a custom storage backend using the `key_value` package:
```python
from fastmcp.server.event_store import EventStore
from key_value.aio.stores.redis import RedisStore
# Use Redis for distributed deployments
redis_store = RedisStore(url="redis://localhost:6379")
event_store = EventStore(
storage=redis_store,
max_events_per_stream=100, # Keep last 100 events per stream
ttl=3600, # Events expire after 1 hour
)
app = mcp.http_app(event_store=event_store)
```
## Integration with Web Frameworks
If you already have a web application running, you can add MCP capabilities by mounting a FastMCP server as a sub-application. This allows you to expose MCP tools alongside your existing API endpoints, sharing the same domain and infrastructure. The MCP server becomes just another route in your application, making it easy to manage and deploy.
### Mounting in Starlette
Mount your FastMCP server in a Starlette application:
```python
from fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount
# Create your FastMCP server
mcp = FastMCP("MyServer")
@mcp.tool
def analyze(data: str) -> dict:
return {"result": f"Analyzed: {data}"}
# Create the ASGI app
mcp_app = mcp.http_app(path='/mcp')
# Create a Starlette app and mount the MCP server
app = Starlette(
routes=[
Mount("/mcp-server", app=mcp_app),
# Add other routes as needed
],
lifespan=mcp_app.lifespan,
)
```
The MCP endpoint will be available at `/mcp-server/mcp/` of the resulting Starlette app.
<Warning>
For Streamable HTTP transport, you **must** pass the lifespan context from the FastMCP app to the resulting Starlette app, as nested lifespans are not recognized. Otherwise, the FastMCP server's session manager will not be properly initialized.
</Warning>
#### Nested Mounts
You can create complex routing structures by nesting mounts:
```python
from fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount
# Create your FastMCP server
mcp = FastMCP("MyServer")
# Create the ASGI app
mcp_app = mcp.http_app(path='/mcp')
# Create nested application structure
inner_app = Starlette(routes=[Mount("/inner", app=mcp_app)])
app = Starlette(
routes=[Mount("/outer", app=inner_app)],
lifespan=mcp_app.lifespan,
)
```
In this setup, the MCP server is accessible at the `/outer/inner/mcp/` path.
### FastAPI Integration
For FastAPI-specific integration patterns including both mounting MCP servers into FastAPI apps and generating MCP servers from FastAPI apps, see the [FastAPI Integration guide](/integrations/fastapi).
Here's a quick example showing how to add MCP to an existing FastAPI application:
```python
from fastapi import FastAPI
from fastmcp import FastMCP
# Create your MCP server
mcp = FastMCP("API Tools")
@mcp.tool
def query_database(query: str) -> dict:
"""Run a database query"""
return {"result": "data"}
# Create the MCP ASGI app with path="/" since we'll mount at /mcp
mcp_app = mcp.http_app(path="/")
# Create FastAPI app with MCP lifespan (required for session management)
api = FastAPI(lifespan=mcp_app.lifespan)
@api.get("/api/status")
def status():
return {"status": "ok"}
# Mount MCP at /mcp
api.mount("/mcp", mcp_app)
# Run with: uvicorn app:api --host 0.0.0.0 --port 8000
```
Your existing API remains at `http://localhost:8000/api` while MCP is available at `http://localhost:8000/mcp`.
<Warning>
Just like with Starlette, you **must** pass the lifespan from the MCP app to FastAPI. Without this, the session manager won't initialize properly and requests will fail.
</Warning>
## Mounting Authenticated Servers
<VersionBadge version="2.13.0" />
<Tip>
This section only applies if you're **mounting an OAuth-protected FastMCP server under a path prefix** (like `/api`) inside another application using `Mount()`.
If you're deploying your FastMCP server at root level without any `Mount()` prefix, the well-known routes are automatically included in `mcp.http_app()` and you don't need to do anything special.
</Tip>
OAuth specifications (RFC 8414 and RFC 9728) require discovery metadata to be accessible at well-known paths under the root level of your domain. When you mount an OAuth-protected FastMCP server under a path prefix like `/api`, this creates a routing challenge: your operational OAuth endpoints move under the prefix, but discovery endpoints must remain at the root.
<Warning>
**Common Mistakes to Avoid:**
1. **Forgetting to mount `.well-known` routes at root** - FastMCP cannot do this automatically when your server is mounted under a path prefix. You must explicitly mount well-known routes at the root level.
2. **Including mount prefix in both base_url AND mcp_path** - The mount prefix (like `/api`) should only be in `base_url`, not in `mcp_path`. Otherwise you'll get double paths.
✅ **Correct:**
```python
base_url = "http://localhost:8000/api"
mcp_path = "/mcp"
# Result: /api/mcp
```
❌ **Wrong:**
```python
base_url = "http://localhost:8000/api"
mcp_path = "/api/mcp"
# Result: /api/api/mcp (double prefix!)
```
Follow the configuration instructions below to set up mounting correctly.
</Warning>
<Warning>
**CORS Middleware Conflicts:**
If you're integrating FastMCP into an existing application with its own CORS middleware, be aware that layering CORS middleware can cause conflicts (such as 404 errors on `.well-known` routes or OPTIONS requests).
FastMCP and the MCP SDK already handle CORS for OAuth routes. If you need CORS on your own application routes, consider using the sub-app pattern: mount FastMCP and your routes as separate apps, each with their own middleware, rather than adding application-wide CORS middleware.
</Warning>
### Route Types
OAuth-protected MCP servers expose two categories of routes:
**Operational routes** handle the OAuth flow and MCP protocol:
- `/authorize` - OAuth authorization endpoint
- `/token` - Token exchange endpoint
- `/auth/callback` - OAuth callback handler
- `/mcp` - MCP protocol endpoint
**Discovery routes** provide metadata for OAuth clients:
- `/.well-known/oauth-authorization-server` - Authorization server metadata
- `/.well-known/oauth-protected-resource/*` - Protected resource metadata
When you mount your MCP app under a prefix, operational routes move with it, but discovery routes must stay at root level for RFC compliance.
### Configuration Parameters
Three parameters control where routes are located and how they combine:
**`base_url`** tells clients where to find operational endpoints. This includes any Starlette `Mount()` path prefix (e.g., `/api`):
```python
base_url="http://localhost:8000/api" # Includes mount prefix
```
**`mcp_path`** is the internal FastMCP endpoint path, which gets appended to `base_url`:
```python
mcp_path="/mcp" # Internal MCP path, NOT the mount prefix
```
**`issuer_url`** (optional) controls the authorization server identity for OAuth discovery. Defaults to `base_url`.
```python
# Usually not needed - just set base_url and it works
issuer_url="http://localhost:8000" # Only if you want root-level discovery
```
When `issuer_url` has a path (either explicitly or by defaulting from `base_url`), FastMCP creates path-aware discovery routes per RFC 8414. For example, if `base_url` is `http://localhost:8000/api`, the authorization server metadata will be at `/.well-known/oauth-authorization-server/api`.
**Key Invariant:** `base_url + mcp_path = actual externally-accessible MCP URL`
Example:
- `base_url`: `http://localhost:8000/api` (mount prefix `/api`)
- `mcp_path`: `/mcp` (internal path)
- Result: `http://localhost:8000/api/mcp` (final MCP endpoint)
Note that the mount prefix (`/api` from `Mount("/api", ...)`) goes in `base_url`, while `mcp_path` is just the internal MCP route. Don't include the mount prefix in both places or you'll get `/api/api/mcp`.
### Mounting Strategy
When mounting an OAuth-protected server under a path prefix, declare your URLs upfront to make the relationships clear:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider
from starlette.applications import Starlette
from starlette.routing import Mount
# Define the routing structure
ROOT_URL = "http://localhost:8000"
MOUNT_PREFIX = "/api"
MCP_PATH = "/mcp"
```
Create the auth provider with `base_url`:
```python
auth = GitHubProvider(
client_id="your-client-id",
client_secret="your-client-secret",
base_url=f"{ROOT_URL}{MOUNT_PREFIX}", # Operational endpoints under prefix
# issuer_url defaults to base_url - path-aware discovery works automatically
)
```
Create the MCP app, which generates operational routes at the specified path:
```python
mcp = FastMCP("Protected Server", auth=auth)
mcp_app = mcp.http_app(path=MCP_PATH)
```
Retrieve the discovery routes from the auth provider. The `mcp_path` argument should match the path used when creating the MCP app:
```python
well_known_routes = auth.get_well_known_routes(mcp_path=MCP_PATH)
```
Finally, mount everything in the Starlette app with discovery routes at root and the MCP app under the prefix:
```python
app = Starlette(
routes=[
*well_known_routes, # Discovery routes at root level
Mount(MOUNT_PREFIX, app=mcp_app), # Operational routes under prefix
],
lifespan=mcp_app.lifespan,
)
```
This configuration produces the following URL structure:
- MCP endpoint: `http://localhost:8000/api/mcp`
- OAuth authorization: `http://localhost:8000/api/authorize`
- OAuth callback: `http://localhost:8000/api/auth/callback`
- Authorization server metadata: `http://localhost:8000/.well-known/oauth-authorization-server/api`
- Protected resource metadata: `http://localhost:8000/.well-known/oauth-protected-resource/api/mcp`
Both discovery endpoints use path-aware URLs per RFC 8414 and RFC 9728, matching the `base_url` path.
### Complete Example
Here's a complete working example showing all the pieces together:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider
from starlette.applications import Starlette
from starlette.routing import Mount
import uvicorn
# Define routing structure
ROOT_URL = "http://localhost:8000"
MOUNT_PREFIX = "/api"
MCP_PATH = "/mcp"
# Create OAuth provider
auth = GitHubProvider(
client_id="your-client-id",
client_secret="your-client-secret",
base_url=f"{ROOT_URL}{MOUNT_PREFIX}",
# issuer_url defaults to base_url - path-aware discovery works automatically
)
# Create MCP server
mcp = FastMCP("Protected Server", auth=auth)
@mcp.tool
def analyze(data: str) -> dict:
return {"result": f"Analyzed: {data}"}
# Create MCP app
mcp_app = mcp.http_app(path=MCP_PATH)
# Get discovery routes for root level
well_known_routes = auth.get_well_known_routes(mcp_path=MCP_PATH)
# Assemble the application
app = Starlette(
routes=[
*well_known_routes,
Mount(MOUNT_PREFIX, app=mcp_app),
],
lifespan=mcp_app.lifespan,
)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
```
For more details on OAuth authentication, see the [Authentication guide](/servers/auth/authentication).
## Production Deployment
### Running with Uvicorn
When deploying to production, you'll want to optimize your server for performance and reliability. Uvicorn provides several options to improve your server's capabilities:
```bash
# Run with basic configuration
uvicorn app:app --host 0.0.0.0 --port 8000
# Run with multiple workers for production (requires stateless mode - see below)
uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4
```
### Horizontal Scaling
<VersionBadge version="2.10.2" />
When deploying FastMCP behind a load balancer or running multiple server instances, you need to understand how the HTTP transport handles sessions and configure your server appropriately.
#### Understanding Sessions
By default, FastMCP's Streamable HTTP transport maintains server-side sessions. Sessions enable stateful MCP features like [elicitation](/servers/elicitation) and [sampling](/servers/sampling), where the server needs to maintain context across multiple requests from the same client.
This works perfectly for single-instance deployments. However, sessions are stored in memory on each server instance, which creates challenges when scaling horizontally.
#### Without Stateless Mode
When running multiple server instances behind a load balancer (Traefik, nginx, HAProxy, Kubernetes, etc.), requests from the same client may be routed to different instances:
1. Client connects to Instance A → session created on Instance A
2. Next request routes to Instance B → session doesn't exist → **request fails**
You might expect sticky sessions (session affinity) to solve this, but they don't work reliably with MCP clients.
<Warning>
**Why sticky sessions don't work:** Most MCP clients—including Cursor and Claude Code—use `fetch()` internally and don't properly forward `Set-Cookie` headers. Without cookies, load balancers can't identify which instance should handle subsequent requests. This is a limitation in how these clients implement HTTP, not something you can fix with load balancer configuration.
</Warning>
#### Enabling Stateless Mode
For horizontally scaled deployments, enable stateless HTTP mode. In stateless mode, each request creates a fresh transport context, eliminating the need for session affinity entirely.
**Option 1: Via `http_app()`**
```python
from fastmcp import FastMCP
mcp = FastMCP("My Server")
@mcp.tool
def process(data: str) -> str:
return f"Processed: {data}"
app = mcp.http_app(stateless_http=True)
```
**Option 2: Via `run()`**
```python
if __name__ == "__main__":
mcp.run(transport="http", stateless_http=True)
```
**Option 3: Via environment variable**
```bash
FASTMCP_STATELESS_HTTP=true uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4
```
### Environment Variables
Production deployments should never hardcode sensitive information like API keys or authentication tokens. Instead, use environment variables to configure your server at runtime. This keeps your code secure and makes it easy to deploy the same code to different environments with different configurations.
Here's an example using static token authentication for development (OAuth is recommended for production):
```python
import os
from fastmcp import FastMCP
from fastmcp.server.auth import StaticTokenVerifier
# Read configuration from environment
auth_token = os.environ.get("MCP_AUTH_TOKEN")
if auth_token:
auth = StaticTokenVerifier(tokens={auth_token: {"sub": "admin", "client_id": "cli"}})
mcp = FastMCP("Production Server", auth=auth)
else:
mcp = FastMCP("Production Server")
app = mcp.http_app()
```
Deploy with your secrets safely stored in environment variables:
```bash
MCP_AUTH_TOKEN=secret uvicorn app:app --host 0.0.0.0 --port 8000
```
### OAuth Token Security
<VersionBadge version="2.13.0" />
If you're using the [OAuth Proxy](/servers/auth/oauth-proxy), FastMCP issues its own JWT tokens to clients instead of forwarding upstream provider tokens. This maintains proper OAuth 2.0 token boundaries.
**Default Behavior (Development Only):**
By default, FastMCP automatically manages cryptographic keys:
- **Mac/Windows**: Keys are generated and stored in your system keyring, surviving server restarts. Suitable **only** for development and local testing.
- **Linux**: Keys are ephemeral (random salt at startup), so tokens are invalidated on restart.
This automatic approach is convenient for development but not suitable for production deployments.
**For Production:**
Production requires explicit key management to ensure tokens survive restarts and can be shared across multiple server instances. This requires the following two things working together:
1. **Explicit JWT signing key** for signing tokens issued to clients
3. **Persistent network-accessible storage** for upstream tokens (wrapped in `FernetEncryptionWrapper` to encrypt sensitive data at rest)
**Configuration:**
Add two parameters to your auth provider:
```python {8-12}
from key_value.aio.stores.redis import RedisStore
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
client_storage=FernetEncryptionWrapper(
key_value=RedisStore(host="redis.example.com", port=6379),
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
),
base_url="https://your-server.com" # use HTTPS
)
```
Both parameters are required for production. Without an explicit signing key, keys are signed using a key derived from the client_secret, which will cause invalidation upon rotation of the client secret. Without persistent storage, tokens are local to the server and won't be trusted across hosts. **Wrap your storage backend in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without encryption, tokens are stored in plaintext.
For more details on the token architecture and key management, see [OAuth Proxy Key and Storage Management](/servers/auth/oauth-proxy#key-and-storage-management).
## Reverse Proxy (nginx)
In production, you'll typically run your FastMCP server behind a reverse proxy like nginx. A reverse proxy provides TLS termination, domain-based routing, static file serving, and an additional layer of security between the internet and your application.
### Running FastMCP as a Linux Service
Before configuring nginx, you need your FastMCP server running as a background service. A systemd unit file ensures your server starts automatically and restarts on failure.
Create a file at `/etc/systemd/system/fastmcp.service`:
```ini
[Unit]
Description=FastMCP Server
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/fastmcp
ExecStart=/opt/fastmcp/.venv/bin/uvicorn app:app --host 127.0.0.1 --port 8000
Restart=always
RestartSec=5
Environment="PATH=/opt/fastmcp/.venv/bin"
[Install]
WantedBy=multi-user.target
```
Enable and start the service:
```bash
sudo systemctl daemon-reload
sudo systemctl enable fastmcp
sudo systemctl start fastmcp
```
This assumes your ASGI application is in `/opt/fastmcp/app.py` with a virtual environment at `/opt/fastmcp/.venv`. Adjust paths to match your deployment layout.
### nginx Configuration
FastMCP's Streamable HTTP transport uses Server-Sent Events (SSE) for streaming responses. This requires specific nginx settings to prevent buffering from breaking the event stream.
Create a site configuration at `/etc/nginx/sites-available/fastmcp`:
```nginx
server {
listen 80;
server_name mcp.example.com;
# Redirect HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name mcp.example.com;
ssl_certificate /etc/letsencrypt/live/mcp.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Required for SSE (Server-Sent Events) streaming
proxy_buffering off;
proxy_cache off;
# Allow long-lived connections for streaming responses
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
```
Enable the site and reload nginx:
```bash
sudo ln -s /etc/nginx/sites-available/fastmcp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
Your FastMCP server is now accessible at `https://mcp.example.com/mcp`.
<Warning>
**SSE buffering is the most common issue.** If clients connect but never receive streaming responses (progress updates, tool results), verify that `proxy_buffering off` is set. Without it, nginx buffers the entire SSE stream and delivers it only when the connection closes, which breaks real-time communication.
</Warning>
### Key Considerations
When deploying FastMCP behind a reverse proxy, keep these points in mind:
- **Disable buffering**: SSE requires `proxy_buffering off` so events reach clients immediately. This is the single most important setting.
- **Increase timeouts**: The default nginx `proxy_read_timeout` is 60 seconds. Long-running MCP tools will cause the connection to drop. Set timeouts to at least 300 seconds, or higher if your tools run longer. For tools that may exceed any timeout, use [SSE Polling](#sse-polling-for-long-running-operations) to gracefully handle proxy disconnections.
- **Use HTTP/1.1**: Set `proxy_http_version 1.1` and `proxy_set_header Connection ''` to enable keep-alive connections between nginx and your server. Clearing the `Connection` header prevents clients from sending `Connection: close` to your upstream, which would break SSE streams. Both settings are required for proper SSE support.
- **Forward headers**: Pass `X-Forwarded-For` and `X-Forwarded-Proto` so your FastMCP server can determine the real client IP and protocol. This is important for logging and for OAuth redirect URLs.
- **TLS termination**: Let nginx handle TLS certificates (e.g., via Let's Encrypt with Certbot). Your FastMCP server can then run on plain HTTP internally.
### Mounting Under a Path Prefix
If you want your MCP server available at a subpath like `https://example.com/api/mcp` instead of at the root domain, adjust the nginx `location` block:
```nginx
location /api/ {
proxy_pass http://127.0.0.1:8000/;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Required for SSE streaming
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
```
Note the trailing `/` on both `location /api/` and `proxy_pass http://127.0.0.1:8000/` — this ensures nginx strips the `/api` prefix before forwarding to your server. If you're using OAuth authentication with a mount prefix, see [Mounting Authenticated Servers](#mounting-authenticated-servers) for additional configuration.
## Testing Your Deployment
Once your server is deployed, you'll need to verify it's accessible and functioning correctly. For comprehensive testing strategies including connectivity tests, client testing, and authentication testing, see the [Testing Your Server](/development/tests) guide.
## Hosting Your Server
This guide has shown you how to create an HTTP-accessible MCP server, but you'll still need a hosting provider to make it available on the internet. Your FastMCP server can run anywhere that supports Python web applications:
- **Cloud VMs** (AWS EC2, Google Compute Engine, Azure VMs)
- **Container platforms** (Cloud Run, Container Instances, ECS)
- **Platform-as-a-Service** (Railway, Render, Vercel)
- **Edge platforms** (Cloudflare Workers)
- **Kubernetes clusters** (self-managed or managed)
The key requirements are Python 3.10+ support and the ability to expose an HTTP port. Most providers will require you to package your server (requirements.txt, Dockerfile, etc.) according to their deployment format. For managed, zero-configuration deployment, see [Prefect Horizon](/deployment/prefect-horizon).

View file

@ -0,0 +1,120 @@
---
title: Prefect Horizon
sidebarTitle: Prefect Horizon
description: The MCP platform from the FastMCP team
icon: cloud
---
[Prefect Horizon](https://www.prefect.io/horizon) is a platform for deploying and managing MCP servers. Built by the FastMCP team at [Prefect](https://www.prefect.io), Horizon provides managed hosting, authentication, access control, and a registry of MCP capabilities.
Horizon includes a **free personal tier for FastMCP users**, making it the fastest way to get a secure, production-ready server URL with built-in OAuth authentication.
<Info>
Horizon is free for personal projects. Enterprise governance features are available for teams deploying to thousands of users.
</Info>
## The Platform
Horizon is organized into four integrated pillars:
- **Deploy**: Managed hosting with CI/CD, scaling, monitoring, and rollbacks. Push code and get a live, governed endpoint in 60 seconds.
- **Registry**: A central catalog of MCP servers across your organization—first-party, third-party, and curated remix servers composed from multiple sources.
- **Gateway**: Role-based access control, authentication, and audit logs. Define what agents can see and do at the tool level.
- **Agents**: A permissioned chat interface for interacting with any MCP server or curated combination of servers.
This guide focuses on **Horizon Deploy**, the managed hosting layer that gives you the fastest path from a FastMCP server to a production URL.
## Prerequisites
To use Horizon, you'll need a [GitHub](https://github.com) account and a GitHub repo containing a FastMCP server. If you don't have one yet, Horizon can create a starter repo for you during onboarding.
Your repo can be public or private, but must include at least a Python file containing a FastMCP server instance.
<Tip>
To verify your file is compatible with Horizon, run `fastmcp inspect <file.py:server_object>` to see what Horizon will see when it runs your server.
</Tip>
If you have a `requirements.txt` or `pyproject.toml` in the repo, Horizon will automatically detect your server's dependencies and install them. Your file *can* have an `if __name__ == "__main__"` block, but it will be ignored by Horizon.
For example, a minimal server file might look like:
```python
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"
```
## Getting Started
There are just three steps to deploying a server to Horizon:
### Step 1: Select a Repository
Visit [horizon.prefect.io](https://horizon.prefect.io?utm_source=gofastmcp&utm_medium=docs) and sign in with your GitHub account. Connect your GitHub account to grant Horizon access to your repositories, then select the repo you want to deploy.
<img src="/assets/images/horizon/select-repo.png" alt="Horizon repository selection" />
### Step 2: Configure Your Server
Next, you'll configure how Horizon should build and deploy your server.
<img src="/assets/images/horizon/configure-server.png" alt="Horizon server configuration" />
The configuration screen lets you specify:
- **Server name**: A unique name for your server. This determines your server's URL.
- **Description**: A brief description of what your server does.
- **Entrypoint**: The Python file containing your FastMCP server (e.g., `main.py`). This field has the same syntax as the `fastmcp run` command—use `main.py:mcp` to specify a specific object in the file.
- **Authentication**: When enabled, only authenticated users in your organization can connect. Horizon handles all the OAuth complexity for you.
Horizon will automatically detect your server's Python dependencies from either a `requirements.txt` or `pyproject.toml` file.
### Step 3: Deploy and Connect
Click **Deploy Server** and Horizon will clone your repository, build your server, and deploy it to a unique URL—typically in under 60 seconds.
<img src="/assets/images/horizon/deployment-live.png" alt="Horizon deployment view showing live server" />
Once deployed, your server is accessible at a URL like:
```
https://your-server-name.fastmcp.app/mcp
```
Horizon monitors your repo and redeploys automatically whenever you push to `main`. It also builds preview deployments for every PR, so you can test changes before they go live.
## Testing Your Server
Horizon provides two ways to verify your server is working before connecting external clients.
### Inspector
The Inspector gives you a structured view of everything your server exposes—tools, resources, and prompts. You can click any tool, fill in the inputs, execute it, and see the output. This is useful for systematically validating each capability and debugging specific behaviors.
### ChatMCP
For quick end-to-end testing, ChatMCP lets you interact with your server conversationally. It uses a fast model optimized for rapid iteration—you can verify the server works, test tool calls in context, and confirm the overall behavior before sharing it with others.
<img src="/assets/images/horizon/chat.png" alt="Horizon ChatMCP interface" />
ChatMCP is designed for testing, not as a daily work environment. Once you've confirmed your server works, you can copy connection snippets for Claude Desktop, Cursor, Claude Code, and other MCP clients—or use the FastMCP client library to connect programmatically.
## Horizon Agents
Beyond testing individual servers, Horizon lets you create **Agents**—chat interfaces backed by one or more MCP servers. While ChatMCP tests a single server, Agents let you compose capabilities from multiple servers into a unified experience.
<img src="/assets/images/horizon/agent-detail.png" alt="Horizon Agent configuration" />
To create an agent:
1. Navigate to **Agents** in the sidebar
2. Click **Create Agent** and give it a name and description
3. Add MCP servers to the agent—these can be servers you've deployed to Horizon or external servers in the registry
Once configured, you can chat with your agent directly in Horizon:
<img src="/assets/images/horizon/agent-chat.png" alt="Chatting with a Horizon Agent" />
Agents are useful for creating purpose-built interfaces that combine tools from different servers. For example, you might create an agent that has access to both your company's internal data server and a general-purpose utilities server.

View file

@ -0,0 +1,286 @@
---
title: Running Your Server
sidebarTitle: Running Your Server
description: Learn how to run your FastMCP server locally for development and testing
icon: circle-play
---
import { VersionBadge } from '/snippets/version-badge.mdx'
FastMCP servers can be run in different ways depending on your needs. This guide focuses on running servers locally for development and testing. For production deployment to a URL, see the [HTTP Deployment](/deployment/http) guide.
## The `run()` Method
Every FastMCP server needs to be started to accept connections. The simplest way to run a server is by calling the `run()` method on your FastMCP instance. This method starts the server and blocks until it's stopped, handling all the connection management for you.
<Tip>
For maximum compatibility, it's best practice to place the `run()` call within an `if __name__ == "__main__":` block. This ensures the server starts only when the script is executed directly, not when imported as a module.
</Tip>
```python {9-10} my_server.py
from fastmcp import FastMCP
mcp = FastMCP(name="MyServer")
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()
```
You can now run this MCP server by executing `python my_server.py`.
## Transport Protocols
MCP servers communicate with clients through different transport protocols. Think of transports as the "language" your server speaks to communicate with clients. FastMCP supports three main transport protocols, each designed for specific use cases and deployment scenarios.
The choice of transport determines how clients connect to your server, what network capabilities are available, and how many clients can connect simultaneously. Understanding these transports helps you choose the right approach for your application.
### STDIO Transport (Default)
STDIO (Standard Input/Output) is the default transport for FastMCP servers. When you call `run()` without arguments, your server uses STDIO transport. This transport communicates through standard input and output streams, making it perfect for command-line tools and desktop applications like Claude Desktop.
With STDIO transport, the client spawns a new server process for each session and manages its lifecycle. The server reads MCP messages from stdin and writes responses to stdout. This is why STDIO servers don't stay running - they're started on-demand by the client.
```python
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run() # Uses STDIO transport by default
```
STDIO is ideal for:
- Local development and testing
- Claude Desktop integration
- Command-line tools
- Single-user applications
### HTTP Transport (Streamable)
HTTP transport turns your MCP server into a web service accessible via a URL. This transport uses the Streamable HTTP protocol, which allows clients to connect over the network. Unlike STDIO where each client gets its own process, an HTTP server can handle multiple clients simultaneously.
The Streamable HTTP protocol provides full bidirectional communication between client and server, supporting all MCP operations including streaming responses. This makes it the recommended choice for network-based deployments.
To use HTTP transport, specify it in the `run()` method along with networking options:
```python
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
# Start an HTTP server on port 8000
mcp.run(transport="http", host="127.0.0.1", port=8000)
```
Your server is now accessible at `http://localhost:8000/mcp`. This URL is the MCP endpoint that clients will connect to. HTTP transport enables:
- Network accessibility
- Multiple concurrent clients
- Integration with web infrastructure
- Remote deployment capabilities
For production HTTP deployment with authentication and advanced configuration, see the [HTTP Deployment](/deployment/http) guide.
### SSE Transport (Legacy)
Server-Sent Events (SSE) transport was the original HTTP-based transport for MCP. While still supported for backward compatibility, it has limitations compared to the newer Streamable HTTP transport. SSE only supports server-to-client streaming, making it less efficient for bidirectional communication.
```python
if __name__ == "__main__":
# SSE transport - use HTTP instead for new projects
mcp.run(transport="sse", host="127.0.0.1", port=8000)
```
We recommend using HTTP transport instead of SSE for all new projects. SSE remains available only for compatibility with older clients that haven't upgraded to Streamable HTTP.
### Choosing the Right Transport
Each transport serves different needs. STDIO is perfect when you need simple, local execution - it's what Claude Desktop and most command-line tools expect. HTTP transport is essential when you need network access, want to serve multiple clients, or plan to deploy your server remotely. SSE exists only for backward compatibility and shouldn't be used in new projects.
Consider your deployment scenario: Are you building a tool for local use? STDIO is your best choice. Need a centralized service that multiple clients can access? HTTP transport is the way to go.
## The FastMCP CLI
FastMCP provides a powerful command-line interface for running servers without modifying the source code. The CLI can automatically find and run your server with different transports, manage dependencies, and handle development workflows:
```bash
fastmcp run server.py
```
The CLI automatically finds a FastMCP instance in your file (named `mcp`, `server`, or `app`) and runs it with the specified options. This is particularly useful for testing different transports or configurations without changing your code.
### Dependency Management
The CLI integrates with `uv` to manage Python environments and dependencies:
```bash
# Run with a specific Python version
fastmcp run server.py --python 3.11
# Run with additional packages
fastmcp run server.py --with pandas --with numpy
# Run with dependencies from a requirements file
fastmcp run server.py --with-requirements requirements.txt
# Combine multiple options
fastmcp run server.py --python 3.10 --with httpx --transport http
# Run within a specific project directory
fastmcp run server.py --project /path/to/project
```
<Note>
When using `--python`, `--with`, `--project`, or `--with-requirements`, the server runs via `uv run` subprocess instead of using your local environment.
</Note>
### Passing Arguments to Servers
When servers accept command line arguments (using argparse, click, or other libraries), you can pass them after `--`:
```bash
fastmcp run config_server.py -- --config config.json
fastmcp run database_server.py -- --database-path /tmp/db.sqlite --debug
```
This is useful for servers that need configuration files, database paths, API keys, or other runtime options.
For more CLI features including development mode with the MCP Inspector, see the [CLI documentation](/cli/running).
### Auto-Reload for Development
<VersionBadge version="3.0.0" />
During development, you can use the `--reload` flag to automatically restart your server when source files change:
```bash
fastmcp run server.py --reload
```
The server watches for changes to Python files in the current directory and restarts automatically when you save changes. This provides a fast feedback loop during development without manually stopping and starting the server.
```bash
# Watch specific directories for changes
fastmcp run server.py --reload --reload-dir ./src --reload-dir ./lib
# Combine with other options
fastmcp run server.py --reload --transport http --port 8080
```
<Note>
Auto-reload uses stateless mode to enable seamless restarts. For stdio transport, this is fully featured. For HTTP transport, some bidirectional features like elicitation are not available during reload mode.
</Note>
SSE transport does not support auto-reload due to session limitations. Use HTTP transport instead if you need both network access and auto-reload.
### Async Usage
FastMCP servers are built on async Python, but the framework provides both synchronous and asynchronous APIs to fit your application's needs. The `run()` method we've been using is actually a synchronous wrapper around the async server implementation.
For applications that are already running in an async context, FastMCP provides the `run_async()` method:
```python {10-12}
from fastmcp import FastMCP
import asyncio
mcp = FastMCP(name="MyServer")
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"
async def main():
# Use run_async() in async contexts
await mcp.run_async(transport="http", port=8000)
if __name__ == "__main__":
asyncio.run(main())
```
<Warning>
The `run()` method cannot be called from inside an async function because it creates its own async event loop internally. If you attempt to call `run()` from inside an async function, you'll get an error about the event loop already running.
Always use `run_async()` inside async functions and `run()` in synchronous contexts.
</Warning>
Both `run()` and `run_async()` accept the same transport arguments, so all the examples above apply to both methods.
## Custom Routes
When using HTTP transport, you might want to add custom web endpoints alongside your MCP server. This is useful for health checks, status pages, or simple APIs. FastMCP lets you add custom routes using the `@custom_route` decorator:
```python
from fastmcp import FastMCP
from starlette.requests import Request
from starlette.responses import PlainTextResponse
mcp = FastMCP("MyServer")
@mcp.custom_route("/health", methods=["GET"])
async def health_check(request: Request) -> PlainTextResponse:
return PlainTextResponse("OK")
@mcp.tool
def process(data: str) -> str:
return f"Processed: {data}"
if __name__ == "__main__":
mcp.run(transport="http") # Health check at http://localhost:8000/health
```
Custom routes are served by the same web server as your MCP endpoint. They're available at the root of your domain while the MCP endpoint is at `/mcp/`. For more complex web applications, consider [mounting your MCP server into a FastAPI or Starlette app](/deployment/http#integration-with-web-frameworks).
## Alternative Initialization Patterns
The `if __name__ == "__main__"` pattern works well for standalone scripts, but some deployment scenarios require different approaches. FastMCP handles these cases automatically.
### CLI-Only Servers
When using the FastMCP CLI, you don't need the `if __name__` block at all. The CLI will find your FastMCP instance and run it:
```python
# server.py
from fastmcp import FastMCP
mcp = FastMCP("MyServer") # CLI looks for 'mcp', 'server', or 'app'
@mcp.tool
def process(data: str) -> str:
return f"Processed: {data}"
# No if __name__ block needed - CLI will find and run 'mcp'
```
### ASGI Applications
For ASGI deployment (running with Uvicorn or similar), you'll want to create an ASGI application object. This approach is common in production deployments where you need more control over the server configuration:
```python
# app.py
from fastmcp import FastMCP
def create_app():
mcp = FastMCP("MyServer")
@mcp.tool
def process(data: str) -> str:
return f"Processed: {data}"
return mcp.http_app()
app = create_app() # Uvicorn will use this
```
See the [HTTP Deployment](/deployment/http) guide for more ASGI deployment patterns.

View file

@ -0,0 +1,262 @@
---
title: Sandboxed Agents
sidebarTitle: Sandboxed Agents
description: Expose MCP tools to isolated agents without giving the sandbox long-lived credentials.
icon: box-open
---
This guide is for deployments where an agent runs inside an isolated container, subprocess, or remote worker and still needs MCP access. In that setup, the sandbox itself becomes part of your trust boundary.
The core recommendation is simple: use FastMCP as the capability boundary. Run a remote FastMCP server, authenticate the sandbox with short-lived scoped credentials, and keep privileged credentials on the server side.
## When to Use This Pattern
This pattern is useful when:
- your agent runs in an ephemeral container or subprocess
- you do not want long-lived credentials inside that sandbox
- you need per-run, per-tenant, or per-job scoping
- the sandbox must call internal APIs, databases, or upstream MCP servers indirectly
If you are building a local desktop integration, STDIO and normal local configuration may be enough. This guide is for cases where the sandbox is isolated enough that secret distribution, credential lifetimes, and privilege boundaries become part of the design.
## What Changes in a Sandboxed Deployment
A desktop MCP client usually runs on a developer's machine and launches local servers with configuration the developer controls. A sandboxed agent is different:
- It often runs in an ephemeral container or subprocess.
- Its filesystem may be inspected after the fact.
- Its environment variables may be broader than you intend.
- You may launch many sandboxes concurrently for different users, tenants, or jobs.
That means convenience patterns that are acceptable locally become risky in sandboxes. Passing a GitHub token, database password, or cloud credentials directly into the sandbox creates a secret distribution problem you do not need to have.
The safer approach is to make your FastMCP server the only component with privileged access and let the sandbox call it over MCP.
## Recommended Architecture
Use this shape by default:
```mermaid
flowchart LR
A["Sandboxed agent"] -->|"short-lived token"| B["FastMCP server"]
B --> C["internal APIs"]
B --> D["databases"]
B --> E["other MCP servers"]
```
The sandbox gets:
- the MCP server URL
- a short-lived token scoped to its job, tenant, or run
- no long-lived upstream credentials
The FastMCP server does the privileged work:
- verifies the sandbox token
- authorizes the request from token claims, scopes, or other server-side policy
- exposes only the tools that sandbox should see
- talks to internal APIs, databases, or upstream MCP servers on the sandbox's behalf
The key design rule is simple:
<Tip>
Give the sandbox capabilities, not credentials.
</Tip>
With that boundary in place, the next questions are how the sandbox connects, how the server verifies and authorizes it, and how you design the tools the sandbox is allowed to call.
## Prefer HTTP for Sandboxed Agents
For sandboxes, prefer a remote HTTP server over a local STDIO server.
STDIO is still excellent for local development, but a remote HTTP server is usually the better production boundary for sandboxed agents because:
- authentication is explicit
- the server lifecycle is independent from the sandbox lifecycle
- secrets stay on the server
- one deployment can safely serve many sandboxes
- auditing and revocation happen in one place
This means the sandbox should connect as a client:
```python
from fastmcp import Client
from fastmcp.client.auth import BearerAuth
client = Client(
"https://sandbox-tools.example.com/mcp",
auth=BearerAuth("short-lived-sandbox-token"),
)
```
And your FastMCP server should run remotely:
```python
from fastmcp import FastMCP
mcp = FastMCP("Sandbox Tools")
if __name__ == "__main__":
mcp.run(transport="http", host="0.0.0.0", port=8000)
```
For production transport setup, see [HTTP Deployment](/deployment/http).
## Use Short-Lived, Scoped Credentials
For sandboxed agents, it is usually cleaner to issue credentials for the sandbox session than to place long-lived upstream credentials directly inside the container.
In practice, that usually means issuing a short-lived bearer token for each sandbox, run, or tenant and validating it on your FastMCP server with a token verifier.
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.jwt import JWTVerifier
auth = JWTVerifier(
jwks_uri="https://auth.example.com/.well-known/jwks.json",
issuer="https://auth.example.com",
audience="sandbox-mcp",
)
mcp = FastMCP("Sandbox Tools", auth=auth)
```
The token should identify the sandbox's scope. Depending on your system, it may represent a job, a tenant, a run, or a user-authorized session. Useful claims often include:
- sandbox or run id
- tenant or installation id
- user or actor id when applicable
- expiration
- optional capability scopes
Avoid shared static tokens across many sandboxes. If one sandbox token leaks, you want the blast radius to be small and the lifetime to be short.
Token verification is only one half of the boundary. Authorization still belongs on the FastMCP server: use scopes, claims, middleware, or custom auth checks to decide which tools and resources that sandbox can actually access.
For example, you can verify the token globally and still require a narrower scope on a specific tool:
```python
from fastmcp import FastMCP
from fastmcp.server.auth import require_scopes
from fastmcp.server.auth.providers.jwt import JWTVerifier
auth = JWTVerifier(
jwks_uri="https://auth.example.com/.well-known/jwks.json",
issuer="https://auth.example.com",
audience="sandbox-mcp",
)
mcp = FastMCP("Sandbox Tools", auth=auth)
@mcp.tool(auth=require_scopes("write:summary"))
def write_summary(content: str) -> str:
return f"Stored summary with {len(content)} characters"
```
For validation patterns, see [Token Verification](/servers/auth/token-verification). For policy enforcement, see [Authorization](/servers/authorization).
## Expose Capabilities, Not Raw Access
The sandbox should not need:
- GitHub app private keys
- database passwords
- upstream OAuth client secrets
- cloud provider credentials
Instead, expose MCP tools that perform privileged work on the server side.
Good sandbox-facing tools tend to look like this:
- `get_recent_updates`
- `write_summary`
- `fetch_repo_context`
- `publish_review_comment`
These tools describe the capability the sandbox needs, not the low-level credentialed action required to perform it.
That distinction matters. A tool like `write_summary` lets the server decide where and how to persist the summary. A tool like `run_sql` or `call_internal_api` pushes privilege and policy into the sandbox where they are much harder to control.
Sandboxed agents behave best when those tools are narrow and structured:
```python
from fastmcp import FastMCP
mcp = FastMCP("Sandbox Tools")
@mcp.tool
def write_summary(content: str) -> str:
"""Store the final summary for the current run."""
return f"Stored summary with {len(content)} characters"
@mcp.tool
def publish_review_comment(pr_number: int, body: str) -> str:
"""Queue a review comment for a specific pull request."""
return f"Queued comment for PR #{pr_number}"
```
These are easier to audit, easier to authorize, and easier for agents to use reliably than a broad catch-all tool like `mutate_state(kind: str, payload: dict)`.
Narrow tools also let you express different policies per tool instead of creating one large privileged escape hatch.
## Use a Proxy When Upstream Systems Are More Privileged
If the sandbox needs access to other MCP servers or internal systems, put FastMCP in front of them instead of forwarding secrets into the sandbox.
This is where proxying becomes useful. Your public-facing FastMCP server can authenticate the sandbox, then forward allowed capabilities to upstream systems with stronger credentials.
Typical examples:
- a sandbox-safe MCP gateway in front of internal MCP servers
- a FastMCP layer in front of internal HTTP APIs
- a job-scoped server that fronts a Git provider, issue tracker, or storage system
If the upstream system is itself an MCP server, FastMCP's proxy support is a natural fit. See [MCP Proxy](/servers/providers/proxy).
## mcp.json for Sandboxed Clients
If your sandboxed agent is configured through `mcp.json`, keep that configuration minimal. Point it at the remote FastMCP server and pass only the values the sandbox actually needs.
```json
{
"mcpServers": {
"sandbox-tools": {
"url": "https://sandbox-tools.example.com/mcp",
"transport": "http"
}
}
}
```
In many systems, authentication is injected by the launcher or environment rather than hardcoded in `mcp.json`. That is usually the right tradeoff for sandboxes. Avoid baking long-lived credentials directly into generated config files, and avoid treating `mcp.json` as the place where secret material should live.
That is all this section needs to do: tell the sandbox where the server lives. Keep auth and secret handling elsewhere.
For configuration details, see [MCP.json](/integrations/mcp-json-configuration).
## Common Mistakes
The same few mistakes show up again and again in sandboxed deployments:
- passing long-lived API keys directly into the sandbox
- treating helper scripts in the sandbox as a security boundary
- exposing broad mutation tools instead of narrow capabilities
- using one shared token for every sandbox
- relying on STDIO inheritance for configuration in production
Each of these works at first. Each becomes painful once you have multiple tenants, multiple jobs, or an incident that requires revoking access quickly.
## Production Checklist
Before shipping a sandbox-facing FastMCP server, check these:
- The sandbox connects over HTTP, not with privileged local credentials.
- Tokens are short-lived and scoped to a run, tenant, or job.
- The FastMCP server verifies tokens on every request.
- Long-lived secrets stay on the server side.
- Tools are narrow, explicit, and structured.
- Upstream privileged systems sit behind the FastMCP server or proxy.
- Revocation and audit live at the server boundary, not inside the sandbox.
If you adopt those defaults, sandbox support stops being a special case and becomes a normal deployment pattern: isolated workers talk to a constrained FastMCP surface, and the server handles the privileged parts centrally.

View file

@ -0,0 +1,640 @@
---
title: "Project Configuration"
sidebarTitle: "Project Configuration"
description: Use fastmcp.json for portable, declarative project configuration
icon: file-code
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.12.0" />
FastMCP supports declarative configuration through `fastmcp.json` files. This is the canonical and preferred way to configure FastMCP projects, providing a single source of truth for server settings, dependencies, and deployment options that replaces complex command-line arguments.
The `fastmcp.json` file is designed to be a portable description of your server configuration that can be shared across environments and teams. When running from a `fastmcp.json` file, you can override any configuration values using CLI arguments.
## Overview
The `fastmcp.json` configuration file allows you to define all aspects of your FastMCP server in a structured, shareable format. Instead of remembering command-line arguments or writing shell scripts, you declare your server's configuration once and use it everywhere.
When you have a `fastmcp.json` file, running your server becomes as simple as:
```bash
# Run the server using the configuration
fastmcp run fastmcp.json
# Or if fastmcp.json exists in the current directory
fastmcp run
```
This configuration approach ensures reproducible deployments across different environments, from local development to production servers. It works seamlessly with Claude Desktop, VS Code extensions, and any MCP-compatible client.
## File Structure
The `fastmcp.json` configuration answers three fundamental questions about your server:
- **Source** = WHERE does your server code live?
- **Environment** = WHAT environment setup does it require?
- **Deployment** = HOW should the server run?
This conceptual model helps you understand the purpose of each configuration section and organize your settings effectively. The configuration file maps directly to these three concerns:
```json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
// WHERE: Location of your server code
"type": "filesystem", // Optional, defaults to "filesystem"
"path": "server.py",
"entrypoint": "mcp"
},
"environment": {
// WHAT: Environment setup and dependencies
"type": "uv", // Optional, defaults to "uv"
"python": ">=3.10",
"dependencies": ["pandas", "numpy"]
},
"deployment": {
// HOW: Runtime configuration
"transport": "stdio",
"log_level": "INFO"
}
}
```
Only the `source` field is required. The `environment` and `deployment` sections are optional and provide additional configuration when needed.
### JSON Schema Support
FastMCP provides JSON schemas for IDE autocomplete and validation. Add the schema reference to your `fastmcp.json` for enhanced developer experience:
```json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "server.py",
"entrypoint": "mcp"
}
}
```
Two schema URLs are available:
- **Version-specific**: `https://gofastmcp.com/public/schemas/fastmcp.json/v1.json`
- **Latest version**: `https://gofastmcp.com/public/schemas/fastmcp.json/latest.json`
Modern IDEs like VS Code will automatically provide autocomplete suggestions, validation, and inline documentation when the schema is specified.
### Source Configuration
The source configuration determines **WHERE** your server code lives. It tells FastMCP how to find and load your server, whether it's a local Python file, a remote repository, or hosted in the cloud. This section is required and forms the foundation of your configuration.
<Card icon="code" title="Source">
<ParamField body="source" type="object" required>
The server source configuration that determines where your server code lives.
<ParamField body="type" type="string" default="filesystem">
The source type identifier that determines which implementation to use. Currently supports `"filesystem"` for local files. Future releases will add support for `"git"` and `"cloud"` source types.
</ParamField>
<Expandable title="FileSystemSource">
When `type` is `"filesystem"` (or omitted), the source points to a local Python file containing your FastMCP server:
<ParamField body="path" type="string" required>
Path to the Python file containing your FastMCP server.
</ParamField>
<ParamField body="entrypoint" type="string">
Name of the server instance or factory function within the module:
- Can be a FastMCP server instance (e.g., `mcp = FastMCP("MyServer")`)
- Can be a function with no arguments that returns a FastMCP server
- If not specified, FastMCP searches for common names: `mcp`, `server`, or `app`
</ParamField>
**Example:**
```json
"source": {
"type": "filesystem",
"path": "src/server.py",
"entrypoint": "mcp"
}
```
Note: File paths are resolved relative to the configuration file's location.
</Expandable>
</ParamField>
</Card>
<Note>
**Future Source Types**
Future releases will support additional source types:
- **Git repositories** (`type: "git"`) for loading server code directly from version control
- **Prefect Horizon** (`type: "cloud"`) for hosted servers with automatic scaling and management
</Note>
### Environment Configuration
The environment configuration determines **WHAT** environment setup your server requires. It controls the build-time setup of your Python environment, ensuring your server runs with the exact Python version and dependencies it requires. This section creates isolated, reproducible environments across different systems.
FastMCP uses an extensible environment system with a base `Environment` class that can be implemented by different environment providers. Currently, FastMCP supports the `UVEnvironment` for Python environment management using `uv`'s powerful dependency resolver.
<Card icon="code" title="Environment">
<ParamField body="environment" type="object">
Optional environment configuration. When specified, FastMCP uses the appropriate environment implementation to set up your server's runtime.
<ParamField body="type" type="string" default="uv">
The environment type identifier that determines which implementation to use. Currently supports `"uv"` for Python environments managed by uv. If omitted, defaults to `"uv"`.
</ParamField>
<Expandable title="UVEnvironment">
When `type` is `"uv"` (or omitted), the environment uses uv to manage Python dependencies:
<ParamField body="python" type="string">
Python version constraint. Examples:
- Exact version: `"3.12"`
- Minimum version: `">=3.10"`
- Version range: `">=3.10,<3.13"`
</ParamField>
<ParamField body="dependencies" type="list[str]">
List of pip packages with optional version specifiers (PEP 508 format).
```json
"dependencies": ["pandas>=2.0", "requests", "httpx"]
```
</ParamField>
<ParamField body="requirements" type="string">
Path to a requirements.txt file, resolved relative to the config file location.
```json
"requirements": "requirements.txt"
```
</ParamField>
<ParamField body="project" type="string">
Path to a project directory containing pyproject.toml for uv project management.
```json
"project": "."
```
</ParamField>
<ParamField body="editable" type="list[string]">
List of paths to packages to install in editable/development mode. Useful for local development when you want changes to be reflected immediately. Supports multiple packages for monorepo setups or shared libraries.
```json
"editable": ["."]
```
Or with multiple packages:
```json
"editable": [".", "../shared-lib", "/path/to/another-package"]
```
</ParamField>
**Example:**
```json
"environment": {
"type": "uv",
"python": ">=3.10",
"dependencies": ["pandas", "numpy"],
"editable": ["."]
}
```
Note: When any UVEnvironment field is specified, FastMCP automatically creates an isolated environment using `uv` before running your server.
</Expandable>
</ParamField>
</Card>
When environment configuration is provided, FastMCP:
1. Detects the environment type (defaults to `"uv"` if not specified)
2. Creates an isolated environment using the appropriate provider
3. Installs the specified dependencies
4. Runs your server in this clean environment
This build-time setup ensures your server always has the dependencies it needs, without polluting your system Python or conflicting with other projects.
<Note>
**Future Environment Types**
Similar to source types, future releases may support additional environment types for different runtime requirements, such as Docker containers or language-specific environments beyond Python.
</Note>
### Deployment Configuration
The deployment configuration controls **HOW** your server runs. It defines the runtime behavior including network settings, environment variables, and execution context. These settings determine how your server operates when it executes, from transport protocols to logging levels.
Environment variables are included in this section because they're runtime configuration that affects how your server behaves when it executes, not how its environment is built. The deployment configuration is applied every time your server starts, controlling its operational characteristics.
<Card icon="code" title="Deployment Fields">
<ParamField body="deployment" type="object">
Optional runtime configuration for the server.
<Expandable title="Deployment Fields">
<ParamField body="transport" type="string" default="stdio">
Protocol for client communication:
- `"stdio"`: Standard input/output for desktop clients
- `"http"`: Network-accessible HTTP server
- `"sse"`: Server-sent events
</ParamField>
<ParamField body="host" type="string" default="127.0.0.1">
Network interface to bind (HTTP transport only):
- `"127.0.0.1"`: Local connections only
- `"0.0.0.0"`: All network interfaces
</ParamField>
<ParamField body="port" type="integer" default="3000">
Port number for HTTP transport.
</ParamField>
<ParamField body="path" type="string" default="/mcp/">
URL path for the MCP endpoint when using HTTP transport.
</ParamField>
<ParamField body="log_level" type="string" default="INFO">
Server logging verbosity. Options:
- `"DEBUG"`: Detailed debugging information
- `"INFO"`: General informational messages
- `"WARNING"`: Warning messages
- `"ERROR"`: Error messages only
- `"CRITICAL"`: Critical errors only
</ParamField>
<ParamField body="env" type="object">
Environment variables to set when running the server. Supports `${VAR_NAME}` syntax for runtime interpolation.
```json
"env": {
"API_KEY": "secret-key",
"DATABASE_URL": "postgres://${DB_USER}@${DB_HOST}/mydb"
}
```
</ParamField>
<ParamField body="cwd" type="string">
Working directory for the server process. Relative paths are resolved from the config file location.
</ParamField>
<ParamField body="args" type="list[str]">
Command-line arguments to pass to the server, passed after `--` to the server's argument parser.
```json
"args": ["--config", "server-config.json"]
```
</ParamField>
</Expandable>
</ParamField>
</Card>
#### Environment Variable Interpolation
The `env` field in deployment configuration supports runtime interpolation of environment variables using `${VAR_NAME}` syntax. This enables dynamic configuration based on your deployment environment:
```json
{
"deployment": {
"env": {
"API_URL": "https://api.${ENVIRONMENT}.example.com",
"DATABASE_URL": "postgres://${DB_USER}:${DB_PASS}@${DB_HOST}/myapp",
"CACHE_KEY": "myapp_${ENVIRONMENT}_${VERSION}"
}
}
}
```
When the server starts, FastMCP replaces `${ENVIRONMENT}`, `${DB_USER}`, etc. with values from your system's environment variables. If a variable doesn't exist, the placeholder is preserved as-is.
**Example**: If your system has `ENVIRONMENT=production` and `DB_HOST=db.example.com`:
```json
// Configuration
{
"deployment": {
"env": {
"API_URL": "https://api.${ENVIRONMENT}.example.com",
"DB_HOST": "${DB_HOST}"
}
}
}
// Result at runtime
{
"API_URL": "https://api.production.example.com",
"DB_HOST": "db.example.com"
}
```
This feature is particularly useful for:
- Deploying the same configuration across development, staging, and production
- Keeping sensitive values out of configuration files
- Building dynamic URLs and connection strings
- Creating environment-specific prefixes or suffixes
## Usage with CLI Commands
FastMCP automatically detects and uses a file specifically named `fastmcp.json` in the current directory, making server execution simple and consistent. Files with FastMCP configuration format but different names are not auto-detected and must be specified explicitly:
```bash
# Auto-detect fastmcp.json in current directory
cd my-project
fastmcp run # No arguments needed!
# Or specify a configuration file explicitly
fastmcp run prod.fastmcp.json
# Skip environment setup when already in a uv environment
fastmcp run fastmcp.json --skip-env
# Skip source preparation when source is already prepared
fastmcp run fastmcp.json --skip-source
# Skip both environment and source preparation
fastmcp run fastmcp.json --skip-env --skip-source
```
### Pre-building Environments
You can use `fastmcp project prepare` to create a persistent uv project with all dependencies pre-installed:
```bash
# Create a persistent environment
fastmcp project prepare fastmcp.json --output-dir ./env
# Use the pre-built environment to run the server
fastmcp run fastmcp.json --project ./env
```
This pattern separates environment setup (slow) from server execution (fast), useful for deployment scenarios.
### Using an Existing Environment
By default, FastMCP creates an isolated environment with `uv` based on your configuration. When you already have a suitable Python environment, use the `--skip-env` flag to skip environment creation:
```bash
fastmcp run fastmcp.json --skip-env
```
**When you already have an environment:**
- You're in an activated virtual environment with all dependencies installed
- You're inside a Docker container with pre-installed dependencies
- You're in a CI/CD pipeline that pre-builds the environment
- You're using a system-wide installation with all required packages
- You're in a uv-managed environment (prevents infinite recursion)
This flag tells FastMCP: "I already have everything installed, just run the server."
### Using an Existing Source
When working with source types that require preparation (future support for git repositories or cloud sources), use the `--skip-source` flag when you already have the source code available:
```bash
fastmcp run fastmcp.json --skip-source
```
**When you already have the source:**
- You've previously cloned a git repository and don't need to re-fetch
- You have a cached copy of a cloud-hosted server
- You're in a CI/CD pipeline where source checkout is a separate step
- You're iterating locally on already-downloaded code
This flag tells FastMCP: "I already have the source code, skip any download/clone steps."
Note: For filesystem sources (local Python files), this flag has no effect since they don't require preparation.
The configuration file works with all FastMCP commands:
- **`run`** - Start the server in production mode
- **`dev`** - Launch with the Inspector UI for development
- **`inspect`** - View server capabilities and configuration
- **`install`** - Install to Claude Desktop, Cursor, or other MCP clients
When no file argument is provided, FastMCP searches the current directory for `fastmcp.json`. This means you can simply navigate to your project directory and run `fastmcp run` to start your server with all its configured settings.
### CLI Override Behavior
Command-line arguments take precedence over configuration file values, allowing ad-hoc adjustments without modifying the file:
```bash
# Config specifies port 3000, CLI overrides to 8080
fastmcp run fastmcp.json --port 8080
# Config specifies stdio, CLI overrides to HTTP
fastmcp run fastmcp.json --transport http
# Add extra dependencies not in config
fastmcp run fastmcp.json --with requests --with httpx
```
This precedence order enables:
- Quick testing of different settings
- Environment-specific overrides in deployment scripts
- Debugging with increased log levels
- Temporary configuration changes
### Custom Naming Patterns
You can use different configuration files for different environments:
- `fastmcp.json` - Default configuration
- `dev.fastmcp.json` - Development settings
- `prod.fastmcp.json` - Production settings
- `test_fastmcp.json` - Test configuration
Any file with "fastmcp.json" in the name is recognized as a configuration file.
## Examples
<Tabs>
<Tab title="Basic Configuration">
A minimal configuration for a simple server:
```json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "server.py",
"entrypoint": "mcp"
}
}
```
This configuration explicitly specifies the server entrypoint (`mcp`), making it clear which server instance or factory function to use. Uses all defaults: STDIO transport, no special dependencies, standard logging.
</Tab>
<Tab title="Development Configuration">
A configuration optimized for local development:
```json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
// WHERE does the server live?
"source": {
"path": "src/server.py",
"entrypoint": "app"
},
// WHAT dependencies does it need?
"environment": {
"type": "uv",
"python": "3.12",
"dependencies": ["fastmcp[dev]"],
"editable": "."
},
// HOW should it run?
"deployment": {
"transport": "http",
"host": "127.0.0.1",
"port": 8000,
"log_level": "DEBUG",
"env": {
"DEBUG": "true",
"ENV": "development"
}
}
}
```
</Tab>
<Tab title="Production Configuration">
A production-ready configuration with full dependency management:
```json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
// WHERE does the server live?
"source": {
"path": "app/main.py",
"entrypoint": "mcp_server"
},
// WHAT dependencies does it need?
"environment": {
"python": "3.11",
"requirements": "requirements/production.txt",
"project": "."
},
// HOW should it run?
"deployment": {
"transport": "http",
"host": "0.0.0.0",
"port": 3000,
"path": "/api/mcp/",
"log_level": "INFO",
"env": {
"ENV": "production",
"API_BASE_URL": "https://api.example.com",
"DATABASE_URL": "postgresql://user:pass@db.example.com/prod"
},
"cwd": "/app",
"args": ["--workers", "4"]
}
}
```
</Tab>
<Tab title="Data Science Server">
Configuration for a data analysis server with scientific packages:
```json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "analysis_server.py",
"entrypoint": "mcp"
},
"environment": {
"python": "3.11",
"dependencies": [
"pandas>=2.0",
"numpy",
"scikit-learn",
"matplotlib",
"jupyterlab"
]
},
"deployment": {
"transport": "stdio",
"env": {
"MATPLOTLIB_BACKEND": "Agg",
"DATA_PATH": "./datasets"
}
}
}
```
</Tab>
<Tab title="Multi-Environment Setup">
You can maintain multiple configuration files for different environments:
**dev.fastmcp.json**:
```json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "server.py",
"entrypoint": "mcp"
},
"deployment": {
"transport": "http",
"log_level": "DEBUG"
}
}
```
**prod.fastmcp.json**:
```json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "server.py",
"entrypoint": "mcp"
},
"environment": {
"requirements": "requirements/production.txt"
},
"deployment": {
"transport": "http",
"host": "0.0.0.0",
"log_level": "WARNING"
}
}
```
Run different configurations:
```bash
fastmcp run dev.fastmcp.json # Development
fastmcp run prod.fastmcp.json # Production
```
</Tab>
</Tabs>
## Migrating from CLI Arguments
If you're currently using command-line arguments or shell scripts, migrating to `fastmcp.json` simplifies your workflow. Here's how common CLI patterns map to configuration:
**CLI Command**:
```bash
uv run --with pandas --with requests \
fastmcp run server.py \
--transport http \
--port 8000 \
--log-level INFO
```
**Equivalent fastmcp.json**:
```json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "server.py",
"entrypoint": "mcp"
},
"environment": {
"dependencies": ["pandas", "requests"]
},
"deployment": {
"transport": "http",
"port": 8000,
"log_level": "INFO"
}
}
```
Now simply run:
```bash
fastmcp run # Automatically finds and uses fastmcp.json
```
The configuration file approach provides better documentation, easier sharing, and consistent execution across different environments while maintaining the flexibility to override settings when needed.

View file

@ -0,0 +1,198 @@
---
title: "Contributing"
description: "Development workflow for FastMCP contributors"
icon: code-pull-request
---
Contributing to FastMCP means joining a community that values clean, maintainable code and thoughtful API design. All contributions are valued - from fixing typos in documentation to implementing major features.
## Design Principles
Every contribution should advance these principles:
- 🚀 **Fast** — High-level interfaces mean less code and faster development
- 🍀 **Simple** — Minimal boilerplate; the obvious way should be the right way
- 🐍 **Pythonic** — Feels natural to Python developers; no surprising patterns
- 🔍 **Complete** — Everything needed for production: auth, testing, deployment, observability
PRs are evaluated against these principles. Code that makes FastMCP slower, harder to reason about, less Pythonic, or less complete will be rejected.
## Issues
### Issue First, Code Second
**Every pull request requires a corresponding issue - no exceptions.** This requirement creates a collaborative space where approach, scope, and alignment are established before code is written. Issues serve as design documents where maintainers and contributors discuss implementation strategy, identify potential conflicts with existing patterns, and ensure proposed changes advance FastMCP's vision.
**FastMCP is an opinionated framework, not a kitchen sink.** The maintainers have strong beliefs about what FastMCP should and shouldn't do. Just because something takes N lines of code and you want it in fewer lines doesn't mean FastMCP should take on the maintenance burden or endorse that pattern. This is judged at the maintainers' discretion.
Use issues to understand scope BEFORE opening PRs. The issue discussion determines whether a feature belongs in core, contrib, or not at all.
### Writing Good Issues
FastMCP is an extremely highly-trafficked repository maintained by a very small team. Issues that appear to transfer burden to maintainers without any effort to validate the problem will be closed. Please help the maintainers help you by always providing a minimal reproducible example and clearly describing the problem.
**LLM-generated issues will be closed immediately.** Issues that contain paragraphs of unnecessary explanation, verbose problem descriptions, or obvious LLM authorship patterns obfuscate the actual problem and transfer burden to maintainers.
Write clear, concise issues that:
- State the problem directly
- Provide a minimal reproducible example
- Skip unnecessary background or context
- Take responsibility for clear communication
Issues may be labeled "Invalid" simply due to confusion caused by verbosity or not adhering to the guidelines outlined here.
## Pull Requests
PRs that deviate from FastMCP's core principles will be rejected regardless of implementation quality. **PRs are NOT for iterating on ideas** - they should only be opened for ideas that already have a bias toward acceptance based on issue discussion.
### Development Environment
#### Installation
To contribute to FastMCP, you'll need to set up a development environment with all necessary tools and dependencies.
```bash
# Clone the repository
git clone https://github.com/PrefectHQ/fastmcp.git
cd fastmcp
# Install all dependencies including dev tools
uv sync
# Install prek hooks
uv run prek install
```
In addition, some development commands require [just](https://github.com/casey/just) to be installed.
Prek hooks will run automatically on every commit to catch issues before they reach CI. If you see failures, fix them before committing - never commit broken code expecting to fix it later.
### Development Standards
#### Scope
Large pull requests create review bottlenecks and quality risks. Unless you're fixing a discrete bug or making an incredibly well-scoped change, keep PRs small and focused.
A PR that changes 50 lines across 3 files can be thoroughly reviewed in minutes. A PR that changes 500 lines across 20 files requires hours of careful analysis and often hides subtle issues.
Breaking large features into smaller PRs:
- Creates better review experiences
- Makes git history clear
- Simplifies debugging with bisect
- Reduces merge conflicts
- Gets your code merged faster
#### Code Quality
FastMCP values clarity over cleverness. Every line you write will be maintained by someone else - possibly years from now, possibly without context about your decisions.
**PRs can be rejected for two opposing reasons:**
1. **Insufficient quality** - Code that doesn't meet our standards for clarity, maintainability, or idiomaticity
2. **Overengineering** - Code that is overbearing, unnecessarily complex, or tries to be too clever
The focus is on idiomatic, high-quality Python. FastMCP uses patterns like `NotSet` type as an alternative to `None` in certain situations - follow existing patterns.
#### Required Practices
**Full type annotations** on all functions and methods. They catch bugs before runtime and serve as inline documentation.
**Async/await patterns** for all I/O operations. Even if your specific use case doesn't need concurrency, consistency means users can compose features without worrying about blocking operations.
**Descriptive names** make code self-documenting. `auth_token` is clear; `tok` requires mental translation.
**Specific exception types** make error handling predictable. Catching `ValueError` tells readers exactly what error you expect. Never use bare `except` clauses.
#### Anti-Patterns to Avoid
**Complex one-liners** are hard to debug and modify. Break operations into clear steps.
**Mutable default arguments** cause subtle bugs. Use `None` as the default and create the mutable object inside the function.
**Breaking established patterns** confuses readers. If you must deviate, discuss in the issue first.
### Prek Checks
```bash
# Runs automatically on commit, or manually:
uv run prek run --all-files
```
This runs three critical tools:
- **Ruff**: Linting and formatting
- **Prettier**: Code formatting
- **ty**: Static type checking
Pytest runs separately as a distinct workflow step after prek checks pass. CI will reject PRs that fail these checks. Always run them locally first.
### Testing
Tests are documentation that shows how features work. Good tests give reviewers confidence and help future maintainers understand intent.
```bash
# Run specific test directory
uv run pytest tests/server/ -v
# Run all tests before submitting PR
uv run pytest
```
Every new feature needs tests. See the [Testing Guide](/development/tests) for patterns and requirements.
### Documentation
A feature doesn't exist unless it's documented. Note that FastMCP's hosted documentation always tracks the main branch - users who want historical documentation can clone the repo, checkout a specific tag, and host it themselves.
```bash
# Preview documentation locally
just docs
```
Documentation requirements:
- **Explain concepts in prose first** - Code without context is just syntax
- **Complete, runnable examples** - Every code block should be copy-pasteable
- **Register in docs.json** - Makes pages appear in navigation
- **Version badges** - Mark when features were added using `<VersionBadge />`
#### SDK Documentation
FastMCP's SDK documentation is auto-generated from the source code docstrings and type annotations. It is automatically updated on every merge to main by a GitHub Actions workflow, so users are *not* responsible for keeping the documentation up to date. However, to generate it proactively, you can use the following command:
```bash
just api-ref-all
```
### Submitting Your PR
#### Before Submitting
1. **Run all checks**: `uv run prek run --all-files && uv run pytest`
2. **Keep scope small**: One feature or fix per PR
3. **Write clear description**: Your PR description becomes permanent documentation
4. **Update docs**: Include documentation for API changes
#### PR Description
Write PR descriptions that explain:
- What problem you're solving
- Why you chose this approach
- Any trade-offs or alternatives considered
- Migration path for breaking changes
Focus on the "why" - the code shows the "what". Keep it concise but complete.
#### What We Look For
**Framework Philosophy**: FastMCP is NOT trying to do all things or provide all shortcuts. Features are rejected when they don't align with the framework's vision, even if perfectly implemented. The burden of proof is on the PR to demonstrate value.
**Code Quality**: We verify code follows existing patterns. Consistency reduces cognitive load. When every module works similarly, developers understand new code quickly.
**Test Coverage**: Not every line needs testing, but every behavior does. Tests document intent and protect against regressions.
**Breaking Changes**: May be acceptable in minor versions but must be clearly documented. See the [versioning policy](/development/releases#versioning-policy).
## Special Modules
**`contrib`**: Community-maintained patterns and utilities. Original authors maintain their contributions. Not representative of the core framework.
**`experimental`**: Maintainer-developed features that may preview future functionality. Can break or be deleted at any time without notice. Pin your FastMCP version when using these features.

View file

@ -0,0 +1,79 @@
---
title: "Releases"
description: "FastMCP versioning and release process"
icon: "truck-fast"
---
FastMCP releases frequently to deliver features quickly in the rapidly evolving MCP ecosystem. We use semantic versioning pragmatically - the Model Context Protocol is young, patterns are still emerging, and waiting for perfect stability would mean missing opportunities to empower developers with better tools.
## Versioning Policy
### Semantic Versioning
**Major (x.0.0)**: Complete API redesigns
Major versions represent fundamental shifts. FastMCP 2.x is entirely different from 1.x in both implementation and design philosophy.
**Minor (2.x.0)**: New features and evolution
<Warning>
Unlike traditional semantic versioning, minor versions **may** include [breaking changes](#breaking-changes) when necessary for the ecosystem's evolution. This flexibility is essential in a young ecosystem where perfect backwards compatibility would prevent important improvements.
</Warning>
FastMCP always targets the most current MCP Protocol version. Breaking changes in the MCP spec or MCP SDK automatically flow through to FastMCP - we prioritize staying current with the latest features and conventions over maintaining compatibility with older protocol versions.
**Patch (2.0.x)**: Bug fixes and refinements
Patch versions contain only bug fixes without breaking changes. These are safe updates you can apply with confidence.
### Breaking Changes
We permit breaking changes in minor versions because the MCP ecosystem is rapidly evolving. Refusing to break problematic APIs would accumulate design debt that eventually makes the framework unusable. Each breaking change represents a deliberate decision to keep FastMCP aligned with the ecosystem's evolution.
When breaking changes occur:
- They only happen in minor versions (e.g., 2.3.x to 2.4.0)
- Release notes explain what changed and how to migrate
- We provide deprecation warnings at least 1 minor version in advance when possible
- Changes must substantially benefit users to justify disruption
The public API is what's covered by our compatibility guarantees - these are the parts of FastMCP you can rely on to remain stable within a minor version. The public API consists of:
- `FastMCP` server class, `Client` class, and FastMCP `Context`
- Core MCP components: `Tool`, `Prompt`, `Resource`, `ResourceTemplate`, and transports
- Their public methods and documented behaviors
Everything else (utilities, private methods, internal modules) may change without notice. This boundary lets us refactor internals and improve implementation details without breaking your code. For production stability, pin to specific versions.
<Warning>
The `fastmcp.server.auth` module was introduced in 2.12.0 and is exempted from this policy temporarily, meaning it is *expected* to have breaking changes even on patch versions. This is because auth is a rapidly evolving part of the MCP spec and it would be dangerous to be beholden to old decisions. Please pin your FastMCP version if using authentication in production.
We expect this exemption to last through at least the 2.12.x and 2.13.x release series.
</Warning>
### Production Use
Pin to exact versions:
```
fastmcp==2.11.0 # Good
fastmcp>=2.11.0 # Bad - will install breaking changes
```
## Creating Releases
Our release process is intentionally simple:
1. Create GitHub release with tag `vMAJOR.MINOR.PATCH` (e.g., `v2.11.0`)
2. Generate release notes automatically, and curate or add additional editorial information as needed
3. GitHub releases automatically trigger PyPI deployments
This automation lets maintainers focus on code quality rather than release mechanics.
### Release Cadence
We follow a feature-driven release cadence rather than a fixed schedule. Minor versions ship approximately every 3-4 weeks when significant functionality is ready.
Patch releases ship promptly for:
- Critical bug fixes
- Security updates (immediate release)
- Regression fixes
This approach means you get improvements as soon as they're ready rather than waiting for arbitrary release dates.

View file

@ -0,0 +1,396 @@
---
title: "Tests"
description: "Testing patterns and requirements for FastMCP"
icon: vial
---
import { VersionBadge } from "/snippets/version-badge.mdx"
Good tests are the foundation of reliable software. In FastMCP, we treat tests as first-class documentation that demonstrates how features work while protecting against regressions. Every new capability needs comprehensive tests that demonstrate correctness.
## FastMCP Tests
### Running Tests
```bash
# Run all tests
uv run pytest
# Run specific test file
uv run pytest tests/server/test_auth.py
# Run with coverage
uv run pytest --cov=fastmcp
# Skip integration tests for faster runs
uv run pytest -m "not integration"
# Skip tests that spawn processes
uv run pytest -m "not integration and not client_process"
```
Tests should complete in under 1 second unless marked as integration tests. This speed encourages running them frequently, catching issues early.
### Test Organization
Our test organization mirrors the source package structure, creating a predictable mapping between code and tests. When you're working on `fastmcp_slim/fastmcp/server/auth.py`, you'll find its tests in `tests/server/test_auth.py`. In rare cases tests are split further - for example, the OpenAPI tests are so comprehensive they're split across multiple files.
### Test Markers
We use pytest markers to categorize tests that require special resources or take longer to run:
```python
@pytest.mark.integration
async def test_github_api_integration():
"""Test GitHub API integration with real service."""
token = os.getenv("FASTMCP_GITHUB_TOKEN")
if not token:
pytest.skip("FASTMCP_GITHUB_TOKEN not available")
# Test against real GitHub API
client = GitHubClient(token)
repos = await client.list_repos("prefecthq")
assert "fastmcp" in [repo.name for repo in repos]
@pytest.mark.client_process
async def test_stdio_transport():
"""Test STDIO transport with separate process."""
# This spawns a subprocess
async with Client("python examples/simple_echo.py") as client:
result = await client.call_tool("echo", {"message": "test"})
assert result.content[0].text == "test"
```
## Writing Tests
### Test Requirements
Following these practices creates maintainable, debuggable test suites that serve as both documentation and regression protection.
#### Single Behavior Per Test
Each test should verify exactly one behavior. When it fails, you need to know immediately what broke. A test that checks five things gives you five potential failure points to investigate. A test that checks one thing points directly to the problem.
<CodeGroup>
```python Good: Atomic Test
async def test_tool_registration():
"""Test that tools are properly registered with the server."""
mcp = FastMCP("test-server")
@mcp.tool
def add(a: int, b: int) -> int:
return a + b
tools = mcp.list_tools()
assert len(tools) == 1
assert tools[0].name == "add"
```
```python Bad: Multi-Behavior Test
async def test_server_functionality():
"""Test multiple server features at once."""
mcp = FastMCP("test-server")
# Tool registration
@mcp.tool
def add(a: int, b: int) -> int:
return a + b
# Resource creation
@mcp.resource("config://app")
def get_config():
return {"version": "1.0"}
# Authentication setup
mcp.auth = BearerTokenProvider({"token": "user"})
# What exactly are we testing? If this fails, what broke?
assert mcp.list_tools()
assert mcp.list_resources()
assert mcp.auth is not None
```
</CodeGroup>
#### Self-Contained Setup
Every test must create its own setup. Tests should be runnable in any order, in parallel, or in isolation. When a test fails, you should be able to run just that test to reproduce the issue.
<CodeGroup>
```python Good: Self-Contained
async def test_tool_execution_with_error():
"""Test that tool errors are properly handled."""
mcp = FastMCP("test-server")
@mcp.tool
def divide(a: int, b: int) -> float:
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
async with Client(mcp) as client:
with pytest.raises(Exception):
await client.call_tool("divide", {"a": 10, "b": 0})
```
```python Bad: Test Dependencies
# Global state that tests depend on
test_server = None
def test_setup_server():
"""Setup for other tests."""
global test_server
test_server = FastMCP("shared-server")
def test_server_works():
"""Test server functionality."""
# Depends on test_setup_server running first
assert test_server is not None
```
</CodeGroup>
#### Clear Intent
Test names and assertions should make the verified behavior obvious. A developer reading your test should understand what feature it validates and how that feature should behave.
```python
async def test_authenticated_tool_requires_valid_token():
"""Test that authenticated users can access protected tools."""
mcp = FastMCP("test-server")
mcp.auth = BearerTokenProvider({"secret-token": "test-user"})
@mcp.tool
def protected_action() -> str:
return "success"
async with Client(mcp, auth=BearerAuth("secret-token")) as client:
result = await client.call_tool("protected_action", {})
assert result.content[0].text == "success"
```
#### Using Fixtures
Use fixtures to create reusable data, server configurations, or other resources for your tests. Note that you should **not** open FastMCP clients in your fixtures as it can create hard-to-diagnose issues with event loops.
```python
import pytest
from fastmcp import FastMCP, Client
@pytest.fixture
def weather_server():
server = FastMCP("WeatherServer")
@server.tool
def get_temperature(city: str) -> dict:
temps = {"NYC": 72, "LA": 85, "Chicago": 68}
return {"city": city, "temp": temps.get(city, 70)}
return server
async def test_temperature_tool(weather_server):
async with Client(weather_server) as client:
result = await client.call_tool("get_temperature", {"city": "LA"})
assert result.data == {"city": "LA", "temp": 85}
```
#### Effective Assertions
Assertions should be specific and provide context on failure. When a test fails during CI, the assertion message should tell you exactly what went wrong.
```python
# Basic assertion - minimal context on failure
assert result.status == "success"
# Better - explains what was expected
assert result.status == "success", f"Expected successful operation, got {result.status}: {result.error}"
```
Try not to have too many assertions in a single test unless you truly need to check various aspects of the same behavior. In general, assertions of different behaviors should be in separate tests.
#### Inline Snapshots
FastMCP uses `inline-snapshot` for testing complex data structures. On first run of `pytest --inline-snapshot=create` with an empty `snapshot()`, pytest will auto-populate the expected value. To update snapshots after intentional changes, run `pytest --inline-snapshot=fix`. This is particularly useful for testing JSON schemas and API responses.
```python
from inline_snapshot import snapshot
async def test_tool_schema_generation():
"""Test that tool schemas are generated correctly."""
mcp = FastMCP("test-server")
@mcp.tool
def calculate_tax(amount: float, rate: float = 0.1) -> dict:
"""Calculate tax on an amount."""
return {"amount": amount, "tax": amount * rate, "total": amount * (1 + rate)}
tools = mcp.list_tools()
schema = tools[0].inputSchema
# First run: snapshot() is empty, gets auto-populated
# Subsequent runs: compares against stored snapshot
assert schema == snapshot({
"type": "object",
"properties": {
"amount": {"type": "number"},
"rate": {"type": "number", "default": 0.1}
},
"required": ["amount"]
})
```
### In-Memory Testing
FastMCP uses in-memory transport for testing, where servers and clients communicate directly. The majority of functionality can be tested in a deterministic fashion this way. We use more complex setups only when testing transports themselves.
The in-memory transport runs the real MCP protocol implementation without network overhead. Instead of deploying your server or managing network connections, you pass your server instance directly to the client. Everything runs in the same Python process - you can set breakpoints anywhere and step through with your debugger.
```python
from fastmcp import FastMCP, Client
# Create your server
server = FastMCP("WeatherServer")
@server.tool
def get_temperature(city: str) -> dict:
"""Get current temperature for a city"""
temps = {"NYC": 72, "LA": 85, "Chicago": 68}
return {"city": city, "temp": temps.get(city, 70)}
async def test_weather_operations():
# Pass server directly - no deployment needed
async with Client(server) as client:
result = await client.call_tool("get_temperature", {"city": "NYC"})
assert result.data == {"city": "NYC", "temp": 72}
```
This pattern makes tests deterministic and fast - typically completing in milliseconds rather than seconds.
### Mocking External Dependencies
FastMCP servers are standard Python objects, so you can mock external dependencies using your preferred approach:
```python
from unittest.mock import AsyncMock
async def test_database_tool():
server = FastMCP("DataServer")
# Mock the database
mock_db = AsyncMock()
mock_db.fetch_users.return_value = [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
@server.tool
async def list_users() -> list:
return await mock_db.fetch_users()
async with Client(server) as client:
result = await client.call_tool("list_users", {})
assert len(result.data) == 2
assert result.data[0]["name"] == "Alice"
mock_db.fetch_users.assert_called_once()
```
### Testing Network Transports
While in-memory testing covers most unit testing needs, you'll occasionally need to test actual network transports like HTTP or SSE. FastMCP provides two approaches: in-process async servers (preferred), and separate subprocess servers (for special cases).
#### In-Process Network Testing (Preferred)
<VersionBadge version="2.13.0" />
For most network transport tests, use `run_server_async` as an async context manager. This runs the server as a task in the same process, providing fast, deterministic tests with full debugger support:
```python
import pytest
from fastmcp import FastMCP, Client
from fastmcp.client.transports import StreamableHttpTransport
from fastmcp.utilities.tests import run_server_async
def create_test_server() -> FastMCP:
"""Create a test server instance."""
server = FastMCP("TestServer")
@server.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
return server
@pytest.fixture
async def http_server() -> str:
"""Start server in-process for testing."""
server = create_test_server()
async with run_server_async(server) as url:
yield url
async def test_http_transport(http_server: str):
"""Test actual HTTP transport behavior."""
async with Client(
transport=StreamableHttpTransport(http_server)
) as client:
result = await client.ping()
assert result is True
greeting = await client.call_tool("greet", {"name": "World"})
assert greeting.data == "Hello, World!"
```
The `run_server_async` context manager automatically handles server lifecycle and cleanup. This approach is faster than subprocess-based testing and provides better error messages.
#### Subprocess Testing (Special Cases)
For tests that require complete process isolation (like STDIO transport or testing subprocess behavior), use `run_server_in_process`:
```python
import pytest
from fastmcp.utilities.tests import run_server_in_process
from fastmcp import FastMCP, Client
from fastmcp.client.transports import StreamableHttpTransport
def run_server(host: str, port: int) -> None:
"""Function to run in subprocess."""
server = FastMCP("TestServer")
@server.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
server.run(host=host, port=port)
@pytest.fixture
async def http_server():
"""Fixture that runs server in subprocess."""
with run_server_in_process(run_server, transport="http") as url:
yield f"{url}/mcp"
async def test_http_transport(http_server: str):
"""Test actual HTTP transport behavior."""
async with Client(
transport=StreamableHttpTransport(http_server)
) as client:
result = await client.ping()
assert result is True
```
The `run_server_in_process` utility handles server lifecycle, port allocation, and cleanup automatically. Use this only when subprocess isolation is truly necessary, as it's slower and harder to debug than in-process testing. FastMCP uses the `client_process` marker to isolate these tests in CI.
### Documentation Testing
Documentation requires the same validation as code. The `just docs` command launches a local Mintlify server that renders your documentation exactly as users will see it:
```bash
# Start local documentation server with hot reload
just docs
# Or run Mintlify directly
mintlify dev
```
The local server watches for changes and automatically refreshes. This preview catches formatting issues and helps you see documentation as users will experience it.

View file

@ -0,0 +1,73 @@
---
title: Auth Provider Environment Variables
---
## Decision: Remove automatic environment variable loading from auth providers
You can still use environment variables for configuration - you just read them yourself with `os.environ` instead of relying on FastMCP's automatic loading.
**Status:** Implemented in v3.0.0
### Background
Auth providers in v2.x used `pydantic-settings` to automatically load configuration from environment variables with a `FASTMCP_SERVER_AUTH_<PROVIDER>_` prefix. For example, `GitHubProvider` would read from:
- `FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID`
- `FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET`
- `FASTMCP_SERVER_AUTH_GITHUB_BASE_URL`
- etc.
This was implemented via a `*ProviderSettings(BaseSettings)` class in each provider, combined with a `NotSet` sentinel pattern to distinguish between "not provided" and `None`.
### Why remove it
1. **Maintenance burden**: Every new provider needed to implement the settings class, validators, and the `NotSet` merging logic. This was ~50-100 lines of boilerplate per provider.
2. **Documentation complexity**: Each provider needed documentation explaining both the parameter and the corresponding environment variable. This doubled the surface area to document and maintain.
3. **Contributor friction**: New contributors adding providers had to understand and replicate this pattern, which was a source of inconsistency and bugs.
4. **Marginal user value**: Python developers are comfortable with `os.environ["VAR"]` or `os.environ.get("VAR", default)`. The automatic loading saved a single line of code per parameter while adding significant complexity.
5. **Implicit behavior**: Magic environment variable loading makes it harder to understand where values come from. Explicit `os.environ` calls are more traceable.
### Migration path
The migration is trivial - users add explicit environment variable reads:
```python
# Before (v2.x)
auth = GitHubProvider() # Relied on env vars
# After (v3.0)
import os
auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
base_url=os.environ["MY_BASE_URL"],
)
```
Users can also use `os.environ.get()` with defaults, or any other configuration library they prefer (dotenv, dynaconf, etc.).
### Backwards compatibility
We chose not to provide backwards compatibility because:
1. This is a major version bump (v3.0), which is the appropriate time for breaking changes
2. The migration is straightforward (add `os.environ` calls)
3. Maintaining compatibility would require keeping all the boilerplate we're trying to remove
4. The pattern was likely not heavily used - most production deployments pass secrets explicitly rather than relying on magic prefixes
### What was removed
- `*ProviderSettings(BaseSettings)` classes from all auth providers
- `NotSet` sentinel usage in provider constructors
- `pydantic-settings` dependency for auth providers
- Environment variable documentation from provider docs
- Related test cases for env var loading
### Result
Provider constructors are now simple and explicit. Required parameters are actually required (Python raises `TypeError` if missing), and optional parameters have clear defaults. The code is more readable and easier to maintain.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,118 @@
---
title: Installation
description: Install FastMCP and verify your setup
icon: arrow-down-to-line
---
## Install FastMCP
We recommend using [uv](https://docs.astral.sh/uv/getting-started/installation/) to install and manage FastMCP.
```bash
pip install fastmcp
```
Or with uv:
```bash
uv add fastmcp
```
### Optional Dependencies
FastMCP provides optional extras for specific features. For example, to install the background tasks extra:
```bash
pip install "fastmcp[tasks]"
```
See [Background Tasks](/servers/tasks) for details on the task system.
### Verify Installation
To verify that FastMCP is installed correctly, you can run the following command:
```bash
fastmcp version
```
You should see output like the following:
```bash
$ fastmcp version
FastMCP version: 3.0.0
MCP version: 1.25.0
Python version: 3.12.2
Platform: macOS-15.3.1-arm64-arm-64bit
FastMCP root path: ~/Developer/fastmcp
```
### Dependency Licensing
<Info>
FastMCP depends on Cyclopts for CLI functionality. Cyclopts v4 includes docutils as a transitive dependency, which has complex licensing that may trigger compliance reviews in some organizations.
If this is a concern, you can install Cyclopts v5 alpha which removes this dependency:
```bash
pip install "cyclopts>=5.0.0a1"
```
Alternatively, wait for the stable v5 release. See [this issue](https://github.com/BrianPugh/cyclopts/issues/672) for details.
</Info>
## Upgrading
### From FastMCP 2.0
See the [Upgrade Guide](/getting-started/upgrading/from-fastmcp-2) for a complete list of breaking changes and migration steps.
### From the MCP SDK
#### From FastMCP 1.0
If you're using FastMCP 1.0 via the `mcp` package (meaning you import FastMCP as `from mcp.server.fastmcp import FastMCP`), upgrading is straightforward — for most servers, it's a single import change. See the [full upgrade guide](/getting-started/upgrading/from-mcp-sdk) for details.
#### From the Low-Level Server API
If you built your server directly on the `mcp` package's `Server` class — with `list_tools()`/`call_tool()` handlers and hand-written JSON Schema — see the [migration guide](/getting-started/upgrading/from-low-level-sdk) for a full walkthrough.
## Troubleshooting
### `import fastmcp` fails after a pip upgrade
This affects one specific case: upgrading to FastMCP 3.3 or later from FastMCP 3.2 or earlier with `pip`. Fresh installs and `uv` upgrades are unaffected, so you can skip this unless you did exactly that.
If `import fastmcp` raises `ModuleNotFoundError`, or `from fastmcp import FastMCP` raises `ImportError`, immediately after the upgrade, your install is in a half-removed state. Reinstall in a single step:
```bash
pip install --force-reinstall fastmcp
```
If that doesn't resolve it, remove both distributions and reinstall from a clean state:
```bash
pip uninstall -y fastmcp fastmcp-slim
pip install fastmcp
```
FastMCP 3.3 moved the importable code from the `fastmcp` distribution into `fastmcp-slim`. During a single-command `pip` upgrade, pip can install the new files and then delete them while uninstalling the old `fastmcp` distribution, whose file manifest still lists those paths. `uv` uninstalls before it installs, so it is unaffected.
## Versioning Policy
FastMCP follows semantic versioning with pragmatic adaptations for the rapidly evolving MCP ecosystem. Breaking changes may occur in minor versions (e.g., 2.3.x to 2.4.0) when necessary to stay current with the MCP Protocol.
For production use, always pin to exact versions:
```
fastmcp==3.0.0 # Good
fastmcp>=3.0.0 # Bad - may install breaking changes
```
See the full [versioning and release policy](/development/releases#versioning-policy) for details on our public API, deprecation practices, and breaking change philosophy.
## Contributing to FastMCP
Interested in contributing to FastMCP? See the [Contributing Guide](/development/contributing) for details on:
- Setting up your development environment
- Running tests and pre-commit hooks
- Submitting issues and pull requests
- Code standards and review process

View file

@ -0,0 +1,164 @@
---
title: Quickstart
icon: rocket-launch
---
Welcome! This guide will help you quickly set up FastMCP, run your first MCP server, give it a visual UI, and deploy it to Prefect Horizon.
If you haven't already installed FastMCP, follow the [installation instructions](/getting-started/installation).
## Create a FastMCP Server
A FastMCP server is a collection of tools, resources, and other MCP components. To create a server, start by instantiating the `FastMCP` class.
Create a new file called `my_server.py` and add the following code:
```python my_server.py
from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
```
That's it! You've created a FastMCP server, albeit a very boring one. Let's add a tool to make it more interesting.
## Add a Tool
To add a tool that returns a simple greeting, write a function and decorate it with `@mcp.tool` to register it with the server:
```python my_server.py {5-7}
from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
```
## Run the Server
The simplest way to run your FastMCP server is to call its `run()` method. You can choose between different transports, like `stdio` for local servers, or `http` for remote access:
<CodeGroup>
```python my_server.py (stdio) {9, 10}
from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()
```
```python my_server.py (HTTP) {9, 10}
from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run(transport="http", port=8000)
```
</CodeGroup>
This lets us run the server with `python my_server.py`. The stdio transport is the traditional way to connect MCP servers to clients, while the HTTP transport enables remote connections.
<Tip>
Why do we need the `if __name__ == "__main__":` block?
The `__main__` block is recommended for consistency and compatibility, ensuring your server works with all MCP clients that execute your server file as a script. Users who will exclusively run their server with the FastMCP CLI can omit it, as the CLI imports the server object directly.
</Tip>
### Using the FastMCP CLI
You can also use the `fastmcp run` command to start your server. Note that the FastMCP CLI **does not** execute the `__main__` block of your server file. Instead, it imports your server object and runs it with whatever transport and options you provide.
For example, to run this server with the default stdio transport (no matter how you called `mcp.run()`), you can use the following command:
```bash
fastmcp run my_server.py:mcp
```
To run this server with the HTTP transport, you can use the following command:
```bash
fastmcp run my_server.py:mcp --transport http --port 8000
```
## Call Your Server
Once your server is running with HTTP transport, you can connect to it with a FastMCP client or any LLM client that supports the MCP protocol:
```python my_client.py
import asyncio
from fastmcp import Client
client = Client("http://localhost:8000/mcp")
async def call_tool(name: str):
async with client:
result = await client.call_tool("greet", {"name": name})
print(result)
asyncio.run(call_tool("Ford"))
```
Note that:
- FastMCP clients are asynchronous, so we need to use `asyncio.run` to run the client
- We must enter a client context (`async with client:`) before using the client
- You can make multiple client calls within the same context
## Give Your Tool a UI
Tools normally return text, but any tool can return an interactive UI instead. Add `app=True` to your tool decorator and return a [Prefab](https://prefab.prefect.io) component — the host renders it as a chart, table, form, or any other visual element right in the conversation. This requires the `apps` extra (`pip install "fastmcp[apps]"`).
The `app=True` flag tells FastMCP to wire up the renderer and protocol metadata automatically. The tool still works like any other MCP tool — it receives arguments and returns a result — but the result is a component tree that the host displays visually instead of as plain text.
```python my_server.py
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, Heading, Text, Badge, Row
from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
@mcp.tool(app=True)
def greet(name: str) -> PrefabApp:
"""Greet someone with a visual card."""
with Column(gap=4, css_class="p-6") as view:
Heading(f"Hello, {name}!")
with Row(gap=2, align="center"):
Text("Status")
Badge("Greeted", variant="success")
return PrefabApp(view=view)
```
You can preview app tools locally with `fastmcp dev apps my_server.py` — no MCP host required. See the [Apps overview](/apps/overview) for the full guide, including state management, forms, charts, and server-connected interactivity.
## Deploy to Prefect Horizon
[Prefect Horizon](https://horizon.prefect.io?utm_source=gofastmcp&utm_medium=docs) is the enterprise MCP platform built by the FastMCP team at [Prefect](https://www.prefect.io). It provides managed hosting, authentication, access control, and observability for MCP servers.
<Info>
Horizon is **free for personal projects** and offers enterprise governance for teams.
</Info>
To deploy your server, you'll need a [GitHub account](https://github.com). Once you have one, you can deploy your server in three steps:
1. Push your `my_server.py` file to a GitHub repository
2. Sign in to [Prefect Horizon](https://horizon.prefect.io?utm_source=gofastmcp&utm_medium=docs) with your GitHub account
3. Create a new project from your repository and enter `my_server.py:mcp` as the server entrypoint
That's it! Horizon will build and deploy your server, making it available at a URL like `https://your-project.fastmcp.app/mcp`. You can chat with it to test its functionality, or connect to it from any LLM client that supports the MCP protocol.
For more details, see the [Prefect Horizon guide](/deployment/prefect-horizon).

View file

@ -0,0 +1,444 @@
---
title: Upgrading from FastMCP 2
sidebarTitle: "From FastMCP 2"
description: Migration instructions for upgrading between FastMCP versions
icon: up
---
This guide covers breaking changes and migration steps when upgrading FastMCP.
## v3.0.0
For most servers, upgrading to v3 is straightforward. The breaking changes below affect deprecated constructor kwargs, sync-to-async shifts, a few renamed methods, and some less commonly used features.
### Install
Since you already have `fastmcp` installed, you need to explicitly request the new version — `pip install fastmcp` won't upgrade an existing installation:
```bash
pip install --upgrade fastmcp
# or
uv add --upgrade fastmcp
```
If you pin versions in a requirements file or `pyproject.toml`, update your pin to `fastmcp>=3.0.0,<4`.
<Info>
**New repository home.** As part of the v3 release, FastMCP's GitHub repository has moved from `jlowin/fastmcp` to [`PrefectHQ/fastmcp`](https://github.com/PrefectHQ/fastmcp) under [Prefect](https://prefect.io)'s stewardship. GitHub automatically redirects existing clones and bookmarks, so nothing breaks — but you can update your local remote whenever convenient:
```bash
git remote set-url origin https://github.com/PrefectHQ/fastmcp.git
```
If you reference the repository URL in dependency specifications (e.g., `git+https://github.com/jlowin/fastmcp.git`), update those to the new location.
</Info>
<Prompt description="Copy this prompt into any LLM along with your server code to get automated upgrade guidance.">
You are upgrading a FastMCP v2 server to FastMCP v3.0. Analyze the provided code and identify every change needed. The full upgrade guide is at https://gofastmcp.com/getting-started/upgrading/from-fastmcp-2 and the complete FastMCP documentation is at https://gofastmcp.com — fetch these for complete context.
BREAKING CHANGES (will crash at import or runtime):
1. CONSTRUCTOR KWARGS REMOVED: FastMCP() no longer accepts these kwargs (raises TypeError):
- Transport settings: host, port, log_level, debug, sse_path, streamable_http_path, json_response, stateless_http
Fix: pass to run() or run_http_async() instead, e.g. mcp.run(transport="http", host="0.0.0.0", port=8080)
- message_path: set via environment variable FASTMCP_MESSAGE_PATH only (not a run() kwarg)
- Duplicate handling: on_duplicate_tools, on_duplicate_resources, on_duplicate_prompts
Fix: use unified on_duplicate= parameter
- Tool settings: tool_serializer, include_tags, exclude_tags, tool_transformations
Fix: use ToolResult returns, server.enable()/disable(), server.add_transform()
2. COMPONENT METHODS REMOVED:
- tool.enable()/disable() raises NotImplementedError
Fix: server.disable(names={"tool_name"}, components={"tool"}) or server.disable(tags={"tag"})
- get_tools()/get_resources()/get_prompts()/get_resource_templates() removed
Fix: use list_tools()/list_resources()/list_prompts()/list_resource_templates() — these return lists, not dicts
3. ASYNC STATE: ctx.set_state() and ctx.get_state() are now async (must be awaited).
State values must be JSON-serializable unless serializable=False is passed.
Each FastMCP instance has its own state store, so serializable state set by parent middleware isn't visible to mounted tools by default.
Fix: pass the same session_state_store to both servers, or use serializable=False (request-scoped state is always shared).
4. PROMPTS: mcp.types.PromptMessage replaced by fastmcp.prompts.Message.
Before: PromptMessage(role="user", content=TextContent(type="text", text="Hello"))
After: Message("Hello") # role defaults to "user", accepts plain strings
Also: if prompts return raw dicts like `{"role": "user", "content": "..."}`, these must become Message objects.
v2 silently coerced dicts; v3 requires typed Message objects or plain strings.
5. AUTH PROVIDERS: No longer auto-load from env vars. Pass client_id, client_secret explicitly via os.environ.
6. WSTRANSPORT: Removed. Use StreamableHttpTransport.
7. OPENAPI: timeout parameter removed from OpenAPIProvider. Set timeout on the httpx.AsyncClient instead.
8. METADATA: Namespace changed from "_fastmcp" to "fastmcp" in tool.meta. The include_fastmcp_meta parameter is removed (always included).
9. ENV VAR: FASTMCP_SHOW_CLI_BANNER renamed to FASTMCP_SHOW_SERVER_BANNER.
10. DECORATORS: @mcp.tool, @mcp.resource, @mcp.prompt now return the original function, not a component object. Code that accesses .name, .description, or other component attributes on the decorated result will crash with AttributeError.
Fix: set FASTMCP_DECORATOR_MODE=object for v2 compat (itself deprecated).
11. OAUTH STORAGE: Default OAuth client storage changed from DiskStore to FileTreeStore due to pickle deserialization vulnerability in diskcache (CVE-2025-69872). Clients using default storage will re-register automatically on first connection. If using DiskStore explicitly, switch to FileTreeStore (with key/collection sanitization strategies) or add pip install 'py-key-value-aio[disk]'.
12. REPO MOVE: GitHub repository moved from jlowin/fastmcp to PrefectHQ/fastmcp. Update git remotes and dependency URLs that reference the old location.
13. BACKGROUND TASKS: FastMCP's background task system (SEP-1686) is now an optional dependency. If the code uses task=True or TaskConfig, add pip install "fastmcp[tasks]".
DEPRECATIONS (still work but emit warnings):
- mount(prefix="x") -> mount(namespace="x")
- import_server(sub) -> mount(sub)
- FastMCP.as_proxy(url) -> from fastmcp.server import create_proxy; create_proxy(url)
- from fastmcp.server.proxy -> from fastmcp.server.providers.proxy
- from fastmcp.server.openapi import FastMCPOpenAPI -> from fastmcp.server.providers.openapi import OpenAPIProvider; use FastMCP("name", providers=[OpenAPIProvider(...)])
- mcp.add_tool_transformation(name, cfg) -> from fastmcp.server.transforms import ToolTransform; mcp.add_transform(ToolTransform(...))
For each issue found, show the original line, explain why it breaks, and provide the corrected code.
</Prompt>
### Breaking Changes
**Transport and server settings removed from constructor**
In v2, you could configure transport settings directly in the `FastMCP()` constructor. In v3, `FastMCP()` is purely about your server's identity and behavior — transport configuration happens when you actually start serving. Passing any of the old kwargs now raises `TypeError` with a migration hint.
```python
# Before
mcp = FastMCP("server", host="0.0.0.0", port=8080)
mcp.run()
# After
mcp = FastMCP("server")
mcp.run(transport="http", host="0.0.0.0", port=8080)
```
The full list of removed kwargs and their replacements:
- `host`, `port`, `log_level`, `debug`, `sse_path`, `streamable_http_path`, `json_response`, `stateless_http` — pass to `run()`, `run_http_async()`, or `http_app()`, or set via environment variables (e.g. `FASTMCP_HOST`)
- `message_path` — set via environment variable `FASTMCP_MESSAGE_PATH` only (not a `run()` kwarg)
- `on_duplicate_tools`, `on_duplicate_resources`, `on_duplicate_prompts` — consolidated into a single `on_duplicate=` parameter
- `tool_serializer` — return [`ToolResult`](/servers/tools#custom-serialization) from your tools instead
- `include_tags` / `exclude_tags` — use `server.enable(tags=..., only=True)` / `server.disable(tags=...)` after construction
- `tool_transformations` — use `server.add_transform(ToolTransform(...))` after construction
**OAuth storage backend changed (diskcache CVE)**
The default OAuth client storage has moved from `DiskStore` to `FileTreeStore` to address a pickle deserialization vulnerability in diskcache ([CVE-2025-69872](https://github.com/PrefectHQ/fastmcp/issues/3166)).
If you were using the default storage (i.e., not passing an explicit `client_storage`), clients will need to re-register on their first connection after upgrading. This happens automatically — no user action required, and it's the same flow that already occurs whenever a server restarts with in-memory storage.
If you were passing a `DiskStore` explicitly, you can either [switch to `FileTreeStore`](/servers/storage-backends) (recommended) or keep using `DiskStore` by adding the dependency yourself.
<Warning>
When switching to `FileTreeStore`, you **must** configure key and collection sanitization strategies. Without them, keys containing special characters (such as URL-based OAuth client IDs) will cause filesystem errors. See the [File Storage](/servers/storage-backends#file-storage) section for the recommended setup.
</Warning>
<Warning>
Keeping `DiskStore` requires `pip install 'py-key-value-aio[disk]'`, which re-introduces the vulnerable `diskcache` package into your dependency tree.
</Warning>
**Component enable()/disable() moved to server**
In v2, you could enable or disable individual components by calling methods on the component object itself. In v3, visibility is controlled through the server (or provider), which lets you target components by name, tag, or type without needing a reference to the object:
```python
# Before
tool = await server.get_tool("my_tool")
tool.disable()
# After
server.disable(names={"my_tool"}, components={"tool"})
```
Calling `.enable()` or `.disable()` on a component object now raises `NotImplementedError`. See [Visibility](/servers/visibility) for the full API, including tag-based filtering and per-session visibility.
**Listing methods renamed and return lists**
The `get_tools()`, `get_resources()`, `get_prompts()`, and `get_resource_templates()` methods have been renamed to `list_tools()`, `list_resources()`, `list_prompts()`, and `list_resource_templates()`. More importantly, they now return lists instead of dicts — so code that indexes by name needs to change:
```python
# Before
tools = await server.get_tools()
tool = tools["my_tool"]
# After
tools = await server.list_tools()
tool = next((t for t in tools if t.name == "my_tool"), None)
```
**Prompts use Message class**
Prompt functions now use FastMCP's `Message` class instead of `mcp.types.PromptMessage`. The new class is simpler — it accepts a plain string and defaults to `role="user"`, so most prompts become one-liners:
```python
# Before
from mcp.types import PromptMessage, TextContent
@mcp.prompt
def my_prompt() -> PromptMessage:
return PromptMessage(role="user", content=TextContent(type="text", text="Hello"))
# After
from fastmcp.prompts import Message
@mcp.prompt
def my_prompt() -> Message:
return Message("Hello")
```
If your prompt functions return raw dicts with `role` and `content` keys, those also need to change. v2 silently coerced dicts into prompt messages, but v3 requires typed `Message` objects (or plain strings for single user messages):
```python
# Before (v2 accepted this)
@mcp.prompt
def my_prompt():
return [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "How can I help?"},
]
# After
from fastmcp.prompts import Message
@mcp.prompt
def my_prompt() -> list[Message]:
return [
Message("Hello"),
Message("How can I help?", role="assistant"),
]
```
**Context state methods are async**
`ctx.set_state()` and `ctx.get_state()` are now async because state in v3 is session-scoped and backed by a pluggable storage backend (rather than a simple dict). This means state persists across multiple tool calls within the same session:
```python
# Before
ctx.set_state("key", "value")
value = ctx.get_state("key")
# After
await ctx.set_state("key", "value")
value = await ctx.get_state("key")
```
State values must also be JSON-serializable by default (dicts, lists, strings, numbers, etc.). If you need to store non-serializable values like an HTTP client, pass `serializable=False` — these values are request-scoped and only available during the current tool call:
```python
await ctx.set_state("client", my_http_client, serializable=False)
```
**Mounted servers have isolated state stores**
Each `FastMCP` instance has its own state store. In v2 this wasn't noticeable because mounted tools ran in the parent's context, but in v3's provider architecture each server is isolated. Non-serializable state (`serializable=False`) is request-scoped and automatically shared across mount boundaries. For serializable state, pass the same `session_state_store` to both servers:
```python
from fastmcp import FastMCP
from key_value.aio.stores.memory import MemoryStore
store = MemoryStore()
parent = FastMCP("Parent", session_state_store=store)
child = FastMCP("Child", session_state_store=store)
parent.mount(child, namespace="child")
```
**Auth provider environment variables removed**
In v2, auth providers like `GitHubProvider` could auto-load configuration from environment variables with a `FASTMCP_SERVER_AUTH_*` prefix. This magic has been removed — pass values explicitly:
```python
# Before (v2) — client_id and client_secret loaded automatically
# from FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID, etc.
auth = GitHubProvider()
# After (v3) — pass values explicitly
import os
from fastmcp.server.auth.providers.github import GitHubProvider
auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
)
```
**WSTransport removed**
The deprecated WebSocket client transport has been removed. Use `StreamableHttpTransport` instead:
```python test="skip"
# Before
from fastmcp.client.transports import WSTransport
transport = WSTransport("ws://localhost:8000/ws")
# After
from fastmcp.client.transports import StreamableHttpTransport
transport = StreamableHttpTransport("http://localhost:8000/mcp")
```
**OpenAPI `timeout` parameter removed**
`OpenAPIProvider` no longer accepts a `timeout` parameter. Configure timeout on the httpx client directly. The `client` parameter is also now optional — when omitted, a default client is created from the spec's `servers` URL with a 30-second timeout:
```python
# Before
provider = OpenAPIProvider(spec, client, timeout=60)
# After
client = httpx.AsyncClient(base_url="https://api.example.com", timeout=60)
provider = OpenAPIProvider(spec, client)
```
**Metadata namespace renamed**
The FastMCP metadata key in component `meta` dicts changed from `_fastmcp` to `fastmcp`. If you read metadata from tool or resource objects, update the key:
```python
# Before
tags = tool.meta.get("_fastmcp", {}).get("tags", [])
# After
tags = tool.meta.get("fastmcp", {}).get("tags", [])
```
Metadata is now always included — the `include_fastmcp_meta` parameter has been removed from `FastMCP()` and `to_mcp_tool()`, so there is no way to suppress it.
**Server banner environment variable renamed**
`FASTMCP_SHOW_CLI_BANNER` is now `FASTMCP_SHOW_SERVER_BANNER`.
**Decorators return functions**
In v2, `@mcp.tool` transformed your function into a `FunctionTool` object. In v3, decorators return your original function unchanged — which means decorated functions stay callable for testing, reuse, and composition:
```python
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
greet("World") # Works! Returns "Hello, World!"
```
If you have code that treats the decorated result as a `FunctionTool` (e.g., accessing `.name` or `.description`), set `FASTMCP_DECORATOR_MODE=object` for v2 compatibility. This escape hatch is itself deprecated and will be removed in a future release.
**Background tasks require optional dependency**
FastMCP's background task system (SEP-1686) is now behind an optional extra. If your server uses background tasks, install with:
```bash
pip install "fastmcp[tasks]"
```
Without the extra, configuring a tool with `task=True` or `TaskConfig` will raise an import error at runtime. See [Background Tasks](/servers/tasks) for details.
### Deprecated Features
These still work but emit warnings. Update when convenient.
**mount() prefix → namespace**
```python
# Deprecated
main.mount(subserver, prefix="api")
# New
main.mount(subserver, namespace="api")
```
**import_server() → mount()**
```python
# Deprecated
main.import_server(subserver)
# New
main.mount(subserver)
```
**Module import paths for proxy and OpenAPI**
The proxy and OpenAPI modules have moved under `providers` to reflect v3's provider-based architecture:
```python test="skip"
# Deprecated
from fastmcp.server.proxy import FastMCPProxy
from fastmcp.server.openapi import FastMCPOpenAPI
# New
from fastmcp.server.providers.proxy import FastMCPProxy
from fastmcp.server.providers.openapi import OpenAPIProvider
```
`FastMCPOpenAPI` itself is deprecated — use `FastMCP` with an `OpenAPIProvider` instead:
```python test="skip"
# Deprecated
from fastmcp.server.openapi import FastMCPOpenAPI
server = FastMCPOpenAPI(spec, client)
# New
from fastmcp import FastMCP
from fastmcp.server.providers.openapi import OpenAPIProvider
server = FastMCP("my_api", providers=[OpenAPIProvider(spec, client)])
```
**add_tool_transformation() → add_transform()**
```python
# Deprecated
mcp.add_tool_transformation("name", config)
# New
from fastmcp.server.transforms import ToolTransform
mcp.add_transform(ToolTransform({"name": config}))
```
**FastMCP.as_proxy() → create_proxy()**
```python
# Deprecated
proxy = FastMCP.as_proxy("http://example.com/mcp")
# New
from fastmcp.server import create_proxy
proxy = create_proxy("http://example.com/mcp")
```
## v2.14.0
### OpenAPI Parser Promotion
The experimental OpenAPI parser is now standard. Update imports:
```python test="skip"
# Before
from fastmcp.experimental.server.openapi import FastMCPOpenAPI
# After
from fastmcp.server.openapi import FastMCPOpenAPI
```
### Removed Deprecated Features
- `BearerAuthProvider` → use `JWTVerifier`
- `Context.get_http_request()` → use `get_http_request()` from dependencies
- `from fastmcp import Image` → use `from fastmcp.utilities.types import Image`
- `FastMCP(dependencies=[...])` → use `fastmcp.json` configuration
- `FastMCPProxy(client=...)` → use `client_factory=lambda: ...`
- `output_schema=False` → use `output_schema=None`
## v2.13.0
### OAuth Token Key Management
The OAuth proxy now issues its own JWT tokens. For production, provide explicit keys:
```python
auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
base_url="https://your-server.com",
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
client_storage=RedisStore(host="redis.example.com"),
)
```
See [OAuth Token Security](/deployment/http#oauth-token-security) for details.

View file

@ -0,0 +1,592 @@
---
title: Upgrading from the MCP Low-Level SDK
sidebarTitle: "From MCP Low-Level SDK"
description: Upgrade your MCP server from the low-level Python SDK's Server class to FastMCP
icon: up
---
If you've been building MCP servers directly on the `mcp` package's `Server` class — writing `list_tools()` and `call_tool()` handlers, hand-crafting JSON Schema dicts, and wiring up transport boilerplate — this guide is for you. FastMCP replaces all of that machinery with a declarative, Pythonic API where your functions *are* the protocol surface.
The core idea: instead of telling the SDK what your tools look like and then separately implementing them, you write ordinary Python functions and let FastMCP derive the protocol layer from your code. Type hints become JSON Schema. Docstrings become descriptions. Return values are serialized automatically. The plumbing you wrote to satisfy the protocol just disappears.
<Note>
This guide covers upgrading from **v1** of the `mcp` package. We'll provide a separate guide when v2 ships.
</Note>
<Note>
Already using FastMCP 1.0 via `from mcp.server.fastmcp import FastMCP`? Your upgrade is simpler — see the [FastMCP 1.0 upgrade guide](/getting-started/upgrading/from-mcp-sdk) instead.
</Note>
<Prompt description="Copy this prompt into any LLM along with your server code to get automated upgrade guidance.">
You are upgrading an MCP server from the `mcp` package's low-level Server class (v1) to FastMCP 3.0. The server currently uses `mcp.server.Server` (or `mcp.server.lowlevel.server.Server`) with manual handler registration. Analyze the provided code and rewrite it using FastMCP's high-level API. The full guide is at https://gofastmcp.com/getting-started/upgrading/from-low-level-sdk and the complete FastMCP documentation is at https://gofastmcp.com — fetch these for complete context.
UPGRADE RULES:
1. IMPORTS: Replace all `mcp.*` imports with FastMCP equivalents.
- `from mcp.server import Server` or `from mcp.server.lowlevel.server import Server` → `from fastmcp import FastMCP`
- `import mcp.types as types` → remove (not needed for most code)
- `from mcp.server.stdio import stdio_server` → remove (handled by mcp.run())
- `from mcp.server.sse import SseServerTransport` → remove (handled by mcp.run())
2. SERVER: Replace `Server("name")` with `FastMCP("name")`.
3. TOOLS: Replace the list_tools + call_tool handler pair with individual @mcp.tool decorators.
- Delete the `@server.list_tools()` handler entirely
- Delete the `@server.call_tool()` handler entirely
- For each tool that was listed in list_tools and dispatched in call_tool, create a new function:
- Decorate it with `@mcp.tool`
- Use the tool name as the function name (or pass name= to the decorator)
- Use the docstring for the description (or pass description= to the decorator)
- Convert the inputSchema JSON Schema into typed Python parameters (e.g., `{"type": "integer"}` → `int`, `{"type": "string"}` → `str`, `{"type": "array", "items": {"type": "string"}}` → `list[str]`)
- Return plain Python values (`str`, `int`, `dict`, etc.) instead of `list[types.TextContent(...)]`
- If the tool returned `types.ImageContent` or `types.EmbeddedResource`, use `from fastmcp.utilities.types import Image` or return the appropriate type
4. RESOURCES: Replace the list_resources + list_resource_templates + read_resource handler trio with individual @mcp.resource decorators.
- Delete all three handlers
- For each static resource, create a function decorated with `@mcp.resource("uri://...")`
- For each resource template, use `@mcp.resource("uri://{param}/path")` with `{param}` in the URI and a matching function parameter
- Return str for text content, bytes for binary content
- Set `mime_type=` in the decorator if needed
5. PROMPTS: Replace the list_prompts + get_prompt handler pair with individual @mcp.prompt decorators.
- Delete both handlers
- For each prompt, create a function decorated with `@mcp.prompt`
- Convert PromptArgument definitions into typed function parameters
- Return str for simple single-message prompts (auto-wrapped as user message)
- Return `list[Message]` for multi-message prompts: `from fastmcp.prompts import Message`
- `Message("text")` defaults to `role="user"`; use `Message("text", role="assistant")` for assistant messages
6. TRANSPORT: Replace all transport boilerplate with mcp.run().
- `async with stdio_server() as (r, w): await server.run(r, w, ...)` → `mcp.run()` (`stdio` is the default)
- SSE/Starlette setup → `mcp.run(transport="sse", host="...", port=...)`
- Streamable HTTP setup → `mcp.run(transport="http", host="...", port=...)`
- Delete asyncio.run(main()) boilerplate — use `if __name__ == "__main__": mcp.run()`
7. CONTEXT: Replace `server.request_context` with FastMCP's Context parameter.
- Add `from fastmcp import Context` and add a `ctx: Context` parameter to any tool that needs it
- `server.request_context.session.send_log_message(...)` → `await ctx.info("message")` or `await ctx.warning("message")`
- Progress reporting → `await ctx.report_progress(current, total)`
For each change, show the original code, explain what it did, and provide the FastMCP equivalent.
</Prompt>
## Install
```bash
pip install --upgrade fastmcp
# or
uv add fastmcp
```
FastMCP includes the `mcp` package as a transitive dependency, so you don't lose access to anything.
## Server and Transport
The `Server` class requires you to choose a transport, connect streams, build initialization options, and run an event loop. FastMCP collapses all of that into a constructor and a `run()` call.
<CodeGroup>
```python Before
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
server = Server("my-server")
# ... register handlers ...
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
asyncio.run(main())
```
```python After
from fastmcp import FastMCP
mcp = FastMCP("my-server")
# ... register tools, resources, prompts ...
if __name__ == "__main__":
mcp.run()
```
</CodeGroup>
Need HTTP instead of stdio? With the `Server` class, you'd wire up Starlette routes and `SseServerTransport` or `StreamableHTTPSessionManager`. With FastMCP:
```python
mcp.run(transport="http", host="0.0.0.0", port=8000)
```
## Tools
This is where the difference is most dramatic. The `Server` class requires two handlers — one to describe your tools (with hand-written JSON Schema) and another to dispatch calls by name. FastMCP eliminates both by deriving everything from your function signature.
<CodeGroup>
```python Before
import mcp.types as types
from mcp.server import Server
server = Server("math")
@server.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="add",
description="Add two numbers",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"},
},
"required": ["a", "b"],
},
),
types.Tool(
name="multiply",
description="Multiply two numbers",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"},
},
"required": ["a", "b"],
},
),
]
@server.call_tool()
async def call_tool(
name: str, arguments: dict
) -> list[types.TextContent]:
if name == "add":
result = arguments["a"] + arguments["b"]
return [types.TextContent(type="text", text=str(result))]
elif name == "multiply":
result = arguments["a"] * arguments["b"]
return [types.TextContent(type="text", text=str(result))]
raise ValueError(f"Unknown tool: {name}")
```
```python After
from fastmcp import FastMCP
mcp = FastMCP("math")
@mcp.tool
def add(a: float, b: float) -> float:
"""Add two numbers"""
return a + b
@mcp.tool
def multiply(a: float, b: float) -> float:
"""Multiply two numbers"""
return a * b
```
</CodeGroup>
Each `@mcp.tool` function is self-contained: its name becomes the tool name, its docstring becomes the description, its type annotations become the JSON Schema, and its return value is serialized automatically. No routing. No schema dictionaries. No content-type wrappers.
### Type Mapping
When converting your `inputSchema` to Python type hints:
| JSON Schema | Python Type |
|---|---|
| `{"type": "string"}` | `str` |
| `{"type": "number"}` | `float` |
| `{"type": "integer"}` | `int` |
| `{"type": "boolean"}` | `bool` |
| `{"type": "array", "items": {"type": "string"}}` | `list[str]` |
| `{"type": "object"}` | `dict` |
| Optional property (not in `required`) | `param: str \| None = None` |
### Return Values
With the `Server` class, tools return `list[types.TextContent | types.ImageContent | ...]`. In FastMCP, return plain Python values — strings, numbers, dicts, lists, dataclasses, Pydantic models — and serialization is handled for you.
For images or other non-text content, FastMCP provides helpers:
```python
from fastmcp import FastMCP
from fastmcp.utilities.types import Image
mcp = FastMCP("media")
@mcp.tool
def create_chart(data: list[float]) -> Image:
"""Generate a chart from data."""
png_bytes = generate_chart(data) # your logic
return Image(data=png_bytes, format="png")
```
## Resources
The `Server` class uses three handlers for resources: `list_resources()` to enumerate them, `list_resource_templates()` for URI templates, and `read_resource()` to serve content — all with manual routing by URI. FastMCP replaces all three with per-resource decorators.
<CodeGroup>
```python Before
import json
import mcp.types as types
from mcp.server import Server
from pydantic import AnyUrl
server = Server("data")
@server.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri=AnyUrl("config://app"),
name="app_config",
description="Application configuration",
mimeType="application/json",
),
types.Resource(
uri=AnyUrl("config://features"),
name="feature_flags",
description="Active feature flags",
mimeType="application/json",
),
]
@server.list_resource_templates()
async def list_resource_templates() -> list[types.ResourceTemplate]:
return [
types.ResourceTemplate(
uriTemplate="users://{user_id}/profile",
name="user_profile",
description="User profile by ID",
),
types.ResourceTemplate(
uriTemplate="projects://{project_id}/status",
name="project_status",
description="Project status by ID",
),
]
@server.read_resource()
async def read_resource(uri: AnyUrl) -> str:
uri_str = str(uri)
if uri_str == "config://app":
return json.dumps({"debug": False, "version": "1.0"})
if uri_str == "config://features":
return json.dumps({"dark_mode": True, "beta": False})
if uri_str.startswith("users://"):
user_id = uri_str.split("/")[2]
return json.dumps({"id": user_id, "name": f"User {user_id}"})
if uri_str.startswith("projects://"):
project_id = uri_str.split("/")[2]
return json.dumps({"id": project_id, "status": "active"})
raise ValueError(f"Unknown resource: {uri}")
```
```python After
import json
from fastmcp import FastMCP
mcp = FastMCP("data")
@mcp.resource("config://app", mime_type="application/json")
def app_config() -> str:
"""Application configuration"""
return json.dumps({"debug": False, "version": "1.0"})
@mcp.resource("config://features", mime_type="application/json")
def feature_flags() -> str:
"""Active feature flags"""
return json.dumps({"dark_mode": True, "beta": False})
@mcp.resource("users://{user_id}/profile")
def user_profile(user_id: str) -> str:
"""User profile by ID"""
return json.dumps({"id": user_id, "name": f"User {user_id}"})
@mcp.resource("projects://{project_id}/status")
def project_status(project_id: str) -> str:
"""Project status by ID"""
return json.dumps({"id": project_id, "status": "active"})
```
</CodeGroup>
Static resources and URI templates use the same `@mcp.resource` decorator — FastMCP detects `{placeholders}` in the URI and automatically registers a template. The function parameter `user_id` maps directly to the `{user_id}` placeholder.
## Prompts
Same pattern: the `Server` class uses `list_prompts()` and `get_prompt()` with manual routing. FastMCP uses one decorator per prompt.
<CodeGroup>
```python Before
import mcp.types as types
from mcp.server import Server
server = Server("prompts")
@server.list_prompts()
async def list_prompts() -> list[types.Prompt]:
return [
types.Prompt(
name="review_code",
description="Review code for issues",
arguments=[
types.PromptArgument(
name="code",
description="The code to review",
required=True,
),
types.PromptArgument(
name="language",
description="Programming language",
required=False,
),
],
)
]
@server.get_prompt()
async def get_prompt(
name: str, arguments: dict[str, str] | None
) -> types.GetPromptResult:
if name == "review_code":
code = (arguments or {}).get("code", "")
language = (arguments or {}).get("language", "")
lang_note = f" (written in {language})" if language else ""
return types.GetPromptResult(
description="Code review prompt",
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"Please review this code{lang_note}:\n\n{code}",
),
)
],
)
raise ValueError(f"Unknown prompt: {name}")
```
```python After
from fastmcp import FastMCP
mcp = FastMCP("prompts")
@mcp.prompt
def review_code(code: str, language: str | None = None) -> str:
"""Review code for issues"""
lang_note = f" (written in {language})" if language else ""
return f"Please review this code{lang_note}:\n\n{code}"
```
</CodeGroup>
Returning a `str` from a prompt function automatically wraps it as a user message. For multi-turn prompts, return a `list[Message]`:
```python
from fastmcp import FastMCP
from fastmcp.prompts import Message
mcp = FastMCP("prompts")
@mcp.prompt
def debug_session(error: str) -> list[Message]:
"""Start a debugging conversation"""
return [
Message(f"I'm seeing this error:\n\n{error}"),
Message("I'll help you debug that. Can you share the relevant code?", role="assistant"),
]
```
## Request Context
The `Server` class exposes request context through `server.request_context`, which gives you the raw `ServerSession` for sending notifications. FastMCP replaces this with a typed `Context` object injected into any function that declares it.
<CodeGroup>
```python Before
import mcp.types as types
from mcp.server import Server
server = Server("worker")
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "process_data":
ctx = server.request_context
await ctx.session.send_log_message(
level="info", data="Starting processing..."
)
# ... do work ...
await ctx.session.send_log_message(
level="info", data="Done!"
)
return [types.TextContent(type="text", text="Processed")]
```
```python After
from fastmcp import FastMCP, Context
mcp = FastMCP("worker")
@mcp.tool
async def process_data(ctx: Context) -> str:
"""Process data with progress logging"""
await ctx.info("Starting processing...")
# ... do work ...
await ctx.info("Done!")
return "Processed"
```
</CodeGroup>
The `Context` object provides logging (`ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`), progress reporting (`ctx.report_progress()`), resource subscriptions, session state, and more. See [Context](/servers/context) for the full API.
## Complete Example
A full server upgrade, showing how all the pieces fit together:
<CodeGroup>
```python Before expandable
import asyncio
import json
import mcp.types as types
from mcp.server import Server
from mcp.server.stdio import stdio_server
from pydantic import AnyUrl
server = Server("demo")
@server.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="greet",
description="Greet someone by name",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string"},
},
"required": ["name"],
},
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name == "greet":
return [types.TextContent(type="text", text=f"Hello, {arguments['name']}!")]
raise ValueError(f"Unknown tool: {name}")
@server.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri=AnyUrl("info://version"),
name="version",
description="Server version",
)
]
@server.read_resource()
async def read_resource(uri: AnyUrl) -> str:
if str(uri) == "info://version":
return json.dumps({"version": "1.0.0"})
raise ValueError(f"Unknown resource: {uri}")
@server.list_prompts()
async def list_prompts() -> list[types.Prompt]:
return [
types.Prompt(
name="summarize",
description="Summarize text",
arguments=[
types.PromptArgument(name="text", required=True)
],
)
]
@server.get_prompt()
async def get_prompt(
name: str, arguments: dict[str, str] | None
) -> types.GetPromptResult:
if name == "summarize":
return types.GetPromptResult(
description="Summarize text",
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"Summarize:\n\n{(arguments or {}).get('text', '')}",
),
)
],
)
raise ValueError(f"Unknown prompt: {name}")
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream, write_stream,
server.create_initialization_options(),
)
asyncio.run(main())
```
```python After
import json
from fastmcp import FastMCP
mcp = FastMCP("demo")
@mcp.tool
def greet(name: str) -> str:
"""Greet someone by name"""
return f"Hello, {name}!"
@mcp.resource("info://version")
def version() -> str:
"""Server version"""
return json.dumps({"version": "1.0.0"})
@mcp.prompt
def summarize(text: str) -> str:
"""Summarize text"""
return f"Summarize:\n\n{text}"
if __name__ == "__main__":
mcp.run()
```
</CodeGroup>
## What's Next
Once you've upgraded, you have access to everything FastMCP provides beyond the basics:
- **[Server composition](/servers/composition)** — Mount sub-servers to build modular applications
- **[Middleware](/servers/middleware)** — Add logging, rate limiting, error handling, and caching
- **[Proxy servers](/servers/providers/proxy)** — Create a proxy to any existing MCP server
- **[OpenAPI integration](/integrations/openapi)** — Generate an MCP server from an OpenAPI spec
- **[Authentication](/servers/auth/authentication)** — Built-in OAuth and token verification
- **[Testing](/servers/testing)** — Test your server directly in Python without running a subprocess
Explore the full documentation at [gofastmcp.com](https://gofastmcp.com).

View file

@ -0,0 +1,166 @@
---
title: Upgrading from the MCP SDK
sidebarTitle: "From MCP SDK"
description: Upgrade from FastMCP in the MCP Python SDK to the standalone FastMCP framework
icon: up
---
If your server starts with `from mcp.server.fastmcp import FastMCP`, you're using FastMCP 1.0 — the version bundled with v1 of the `mcp` package. Upgrading to the standalone FastMCP framework is easy. **For most servers, it's a single import change.**
```python
# Before
from mcp.server.fastmcp import FastMCP
# After
from fastmcp import FastMCP
```
That's it. Your `@mcp.tool`, `@mcp.resource`, and `@mcp.prompt` decorators, your `mcp.run()` call, and the rest of your server code all work as-is.
<Tip>
**Why upgrade?** FastMCP 1.0 pioneered the Pythonic MCP server experience, and we're proud it was bundled into the `mcp` package. The standalone FastMCP project has since grown into a full framework for taking MCP servers from prototype to production — with composition, middleware, proxy servers, authentication, and much more. Upgrading gives you access to all of that, plus ongoing updates and fixes.
</Tip>
## Install
```bash
pip install --upgrade fastmcp
# or
uv add fastmcp
```
FastMCP includes the `mcp` package as a dependency, so you don't lose access to anything. Update your import, run your server, and if your tools work, you're done.
<Prompt description="Copy this prompt into any LLM along with your server code to get automated upgrade guidance.">
You are upgrading an MCP server from FastMCP 1.0 (bundled in the `mcp` package v1) to standalone FastMCP 3.0. Analyze the provided code and identify every change needed. The full upgrade guide is at https://gofastmcp.com/getting-started/upgrading/from-mcp-sdk and the complete FastMCP documentation is at https://gofastmcp.com — fetch these for complete context.
STEP 1 — IMPORT (required for all servers):
Change "from mcp.server.fastmcp import FastMCP" to "from fastmcp import FastMCP".
STEP 2 — CONSTRUCTOR KWARGS (only if FastMCP() receives transport settings):
FastMCP() no longer accepts: host, port, log_level, debug, sse_path, streamable_http_path, json_response, stateless_http.
Fix: pass these to run() instead.
Before: `mcp = FastMCP("server", host="0.0.0.0", port=8080); mcp.run()`
After: `mcp = FastMCP("server"); mcp.run(transport="http", host="0.0.0.0", port=8080)`
STEP 3 — PROMPTS (only if using PromptMessage directly or returning dicts):
mcp.types.PromptMessage is replaced by fastmcp.prompts.Message.
Before: `PromptMessage(role="user", content=TextContent(type="text", text="Hello"))`
After: `Message("Hello")` — role defaults to "user", accepts plain strings.
Also: if prompts return raw dicts like `{"role": "user", "content": "..."}`, these must become Message objects or plain strings.
The MCP SDK's FastMCP 1.0 silently coerced dicts; standalone FastMCP requires typed returns.
STEP 4 — OTHER MCP IMPORTS (only if importing from mcp.* directly):
Direct imports from the `mcp` package (e.g., `import mcp.types`, `from mcp.server.stdio import stdio_server`) still work because FastMCP includes `mcp` as a dependency. However, prefer FastMCP's own APIs where equivalents exist:
- mcp.types.TextContent for tool returns → just return plain Python values (str, int, dict, etc.)
- mcp.types.ImageContent → fastmcp.utilities.types.Image
- from mcp.server.stdio import stdio_server → not needed, mcp.run() handles transport
STEP 5 — DECORATORS (only if treating decorated functions as objects):
@mcp.tool, @mcp.resource, @mcp.prompt now return the original function, not a component object. Code that accesses .name or .description on the decorated result needs updating. Set FASTMCP_DECORATOR_MODE=object temporarily to restore v1 behavior (this compat setting is itself deprecated).
For each issue found, show the original line, explain what changed, and provide the corrected code.
</Prompt>
## What Might Need Updating
Most servers need nothing beyond the import change. Skim the sections below to see if any apply.
### Constructor Settings
If you passed transport settings like `host` or `port` directly to `FastMCP()`, those now belong on `run()`. This keeps your server definition independent of how it's deployed:
```python
# Before
mcp = FastMCP("my-server", host="0.0.0.0", port=8080)
mcp.run()
# After
mcp = FastMCP("my-server")
mcp.run(transport="http", host="0.0.0.0", port=8080)
```
If you pass the old kwargs, you'll get a clear `TypeError` with a migration hint.
### Prompts
If your prompt functions return `mcp.types.PromptMessage` objects or raw dicts with `role`/`content` keys, you'll need to upgrade to FastMCP's `Message` class. Or just return a plain string — it's automatically wrapped as a user message. The MCP SDK's bundled FastMCP 1.0 silently coerced dicts into messages; standalone FastMCP requires typed `Message` objects or strings.
```python
from fastmcp import FastMCP
mcp = FastMCP("prompts")
@mcp.prompt
def review(code: str) -> str:
"""Review code for issues"""
return f"Please review this code:\n\n{code}"
```
For multi-turn prompts:
```python
from fastmcp.prompts import Message
@mcp.prompt
def debug(error: str) -> list[Message]:
"""Start a debugging session"""
return [
Message(f"I'm seeing this error:\n\n{error}"),
Message("I'll help debug that. Can you share the relevant code?", role="assistant"),
]
```
### Other `mcp.*` Imports
If your server imports directly from the `mcp` package — like `import mcp.types` or `from mcp.server.stdio import stdio_server` — those still work. FastMCP includes `mcp` as a dependency, so nothing breaks.
Where FastMCP provides its own API for the same thing, it's worth switching over:
| mcp Package | FastMCP Equivalent |
|---|---|
| `mcp.types.TextContent(type="text", text=str(x))` | Just return `x` from your tool |
| `mcp.types.ImageContent(...)` | `from fastmcp.utilities.types import Image` |
| `mcp.types.PromptMessage(...)` | `from fastmcp.prompts import Message` |
| `from mcp.server.stdio import stdio_server` | Not needed — `mcp.run()` handles transport |
For anything without a FastMCP equivalent (e.g., specific protocol types you use directly), the `mcp.*` import is fine to keep.
### Decorated Functions
In FastMCP 1.0, `@mcp.tool` returned a `FunctionTool` object. Now decorators return your original function unchanged — so decorated functions stay callable for testing, reuse, and composition:
```python
@mcp.tool
def greet(name: str) -> str:
"""Greet someone"""
return f"Hello, {name}!"
# This works now — the function is still a regular function
assert greet("World") == "Hello, World!"
```
If you have code that accesses `.name`, `.description`, or other attributes on the decorated result, that will need updating. This is uncommon — most servers don't interact with the tool object directly. If you need the old behavior temporarily, set `FASTMCP_DECORATOR_MODE=object` to restore it (this compatibility setting is itself deprecated and will be removed in a future release).
## Verify the Upgrade
```bash
# Install
pip install --upgrade fastmcp
# Check version
fastmcp version
# Run your server
python my_server.py
```
You can also inspect your server's registered components with the FastMCP CLI:
```bash
fastmcp inspect my_server.py
```
## Looking Ahead
The MCP ecosystem is evolving fast. Part of FastMCP's job is to absorb that complexity on your behalf — as the protocol and its tooling grow, we do the work so your server code doesn't have to change.

View file

@ -0,0 +1,134 @@
---
title: "Welcome to FastMCP"
sidebarTitle: "Welcome!"
description: The fast, Pythonic way to build MCP servers, clients, and applications.
icon: hand-wave
mode: center
---
{/* <img
src="/assets/brand/f-watercolor-waves-4.png"
alt="'F' logo on a watercolor background"
noZoom
className="rounded-2xl block dark:hidden"
/>
<img
src="/assets/brand/f-watercolor-waves-4-dark.png"
alt="'F' logo on a watercolor background"
noZoom
className="rounded-2xl hidden dark:block"
/>
*/}
<video
autoPlay
muted
loop
playsInline
className="rounded-2xl block dark:hidden"
src="/assets/brand/f-watercolor-waves-4-animated.mp4"
></video>
<video
autoPlay
muted
loop
playsInline
className="rounded-2xl hidden dark:block"
src="/assets/brand/f-watercolor-waves-4-dark-animated.mp4"
></video>
**FastMCP is the standard framework for building MCP applications.** The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) connects LLMs to tools and data. FastMCP gives you everything you need to go from prototype to production — build servers that expose capabilities, connect clients to any MCP service, and give your tools interactive UIs:
```python {1}
from fastmcp import FastMCP
mcp = FastMCP("Demo 🚀")
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
if __name__ == "__main__":
mcp.run()
```
## Move Fast and Make Things
The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) lets you give agents access to your tools and data. But building an effective MCP application is harder than it looks.
FastMCP handles all of it. Declare a tool with a Python function, and the schema, validation, and documentation are generated automatically. Connect to a server with a URL, and transport negotiation, authentication, and protocol lifecycle are managed for you. You focus on your logic, and the MCP part just works: **with FastMCP, best practices are built in.**
**That's why FastMCP is the standard framework for working with MCP.** FastMCP 1.0 was incorporated into the official MCP Python SDK in 2024. Today, the actively maintained standalone project is downloaded a million times a day, and some version of FastMCP powers 70% of MCP servers across all languages.
FastMCP has three pillars:
<CardGroup cols={3}>
<Card title="Servers" img="/assets/images/servers-card.png" href="/servers/server">
Expose tools, resources, and prompts to LLMs.
</Card>
<Card title="Apps" img="/assets/images/apps-card.png" href="/apps/overview">
Give your tools interactive UIs rendered directly in the conversation.
</Card>
<Card title="Clients" img="/assets/images/clients-card.png" href="/clients/client">
Connect to any MCP server — local or remote, programmatic or CLI.
</Card>
</CardGroup>
**[Servers](/servers/server)** wrap your Python functions into MCP-compliant tools, resources, and prompts. **[Clients](/clients/client)** connect to any server with full protocol support. And **[Apps](/apps/overview)** give your tools interactive UIs rendered directly in the conversation.
Ready to build? Start with the [installation guide](/getting-started/installation) or jump straight to the [quickstart](/getting-started/quickstart).
FastMCP is made with 💙 by [Prefect](https://www.prefect.io/).
## Run FastMCP in production with Horizon
FastMCP is the standard way to build MCP servers. **[Prefect Horizon](https://www.prefect.io/horizon?utm_source=gofastmcp&utm_medium=docs&utm_campaign=docs_welcome&utm_content=welcome_body)** is the enterprise MCP gateway for running them safely.
Built by the FastMCP team, Horizon packages the best practices we've learned shipping the world's most popular MCP framework.
Deploy FastMCP servers from GitHub with branch previews and instant rollback. Create a private registry of every MCP your company uses. Secure access with SSO and tool-level RBAC. Get audit logs, observability, and governance across your MCP stack. Remix approved tools into purpose-built endpoints for teams and agents.
Start with FastMCP. [Scale with Horizon →](https://www.prefect.io/horizon?utm_source=gofastmcp&utm_medium=docs&utm_campaign=docs_welcome&utm_content=welcome_cta)
<Tip>
**This documentation reflects FastMCP's `main` branch**, meaning it always reflects the latest development version. Features are generally marked with version badges (e.g. `New in version: 3.0.0`) to indicate when they were introduced. Note that this may include features that are not yet released.
</Tip>
## LLM-Friendly Docs
The FastMCP documentation is available in multiple LLM-friendly formats:
### MCP Server
The FastMCP docs are accessible via MCP! The server URL is `https://gofastmcp.com/mcp`.
In fact, you can use FastMCP to search the FastMCP docs:
```python
import asyncio
from fastmcp import Client
async def main():
async with Client("https://gofastmcp.com/mcp") as client:
result = await client.call_tool(
name="search_fast_mcp",
arguments={"query": "deploy a FastMCP server"}
)
print(result)
asyncio.run(main())
```
### Text Formats
The docs are also available in [llms.txt format](https://llmstxt.org/):
- [llms.txt](https://gofastmcp.com/llms.txt) - A sitemap listing all documentation pages
- [llms-full.txt](https://gofastmcp.com/llms-full.txt) - The entire documentation in one file (may exceed context windows)
Any page can be accessed as markdown by appending `.md` to the URL. For example, this page becomes `https://gofastmcp.com/getting-started/welcome.md`.
You can also copy any page as markdown by pressing "Cmd+C" (or "Ctrl+C" on Windows) on your keyboard.

View file

@ -0,0 +1,228 @@
---
title: Anthropic API 🤝 FastMCP
sidebarTitle: Anthropic API
description: Connect FastMCP servers to the Anthropic API
icon: message-code
---
import { VersionBadge } from "/snippets/version-badge.mdx"
Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports MCP servers as remote tool sources. This tutorial will show you how to create a FastMCP server and deploy it to a public URL, then how to call it from the Messages API.
<Tip>
Currently, the MCP connector only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to Claude. Other MCP features like resources and prompts are not currently supported. You can read more about the MCP connector in the [Anthropic documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector).
</Tip>
## Create a Server
First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice.
```python server.py
import random
from fastmcp import FastMCP
mcp = FastMCP(name="Dice Roller")
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]
if __name__ == "__main__":
mcp.run(transport="http", port=8000)
```
## Deploy the Server
Your server must be deployed to a public URL in order for Anthropic to access it. The MCP connector supports both SSE and Streamable HTTP transports.
For development, you can use tools like `ngrok` to temporarily expose a locally-running server to the internet. We'll do that for this example (you may need to install `ngrok` and create a free account), but you can use any other method to deploy your server.
Assuming you saved the above code as `server.py`, you can run the following two commands in two separate terminals to deploy your server and expose it to the internet:
<CodeGroup>
```bash FastMCP server
python server.py
```
```bash ngrok
ngrok http 8000
```
</CodeGroup>
<Warning>
This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
</Warning>
## Call the Server
To use the Messages API with MCP servers, you'll need to install the Anthropic Python SDK (not included with FastMCP):
```bash
pip install anthropic
```
You'll also need to authenticate with Anthropic. You can do this by setting the `ANTHROPIC_API_KEY` environment variable. Consult the Anthropic SDK documentation for more information.
```bash
export ANTHROPIC_API_KEY="your-api-key"
```
Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/mcp/` as the endpoint because we deployed a streamable-HTTP server with the default path; you may need to use a different endpoint if you customized your server's deployment. **At this time you must also include the `extra_headers` parameter with the `anthropic-beta` header.**
```python {5, 13-22}
import anthropic
from rich import print
# Your server URL (replace with your actual URL)
url = 'https://your-server-url.com'
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1000,
messages=[{"role": "user", "content": "Roll a few dice!"}],
mcp_servers=[
{
"type": "url",
"url": f"{url}/mcp/",
"name": "dice-server",
}
],
extra_headers={
"anthropic-beta": "mcp-client-2025-04-04"
}
)
print(response.content)
```
If you run this code, you'll see something like the following output:
```text
I'll roll some dice for you! Let me use the dice rolling tool.
I rolled 3 dice and got: 4, 2, 6
The results were 4, 2, and 6. Would you like me to roll again or roll a different number of dice?
```
## Authentication
<VersionBadge version="2.6.0" />
The MCP connector supports OAuth authentication through authorization tokens, which means you can secure your server while still allowing Anthropic to access it.
### Server Authentication
The simplest way to add authentication to the server is to use a bearer token scheme.
For this example, we'll quickly generate our own tokens with FastMCP's `RSAKeyPair` utility, but this may not be appropriate for production use. For more details, see the complete server-side [Token Verification](/servers/auth/token-verification) documentation.
We'll start by creating an RSA key pair to sign and verify tokens.
```python
from fastmcp.server.auth.providers.jwt import RSAKeyPair
key_pair = RSAKeyPair.generate()
access_token = key_pair.create_token(audience="dice-server")
```
<Warning>
FastMCP's `RSAKeyPair` utility is for development and testing only.
</Warning>
Next, we'll create a `JWTVerifier` to authenticate the server.
```python
from fastmcp import FastMCP
from fastmcp.server.auth import JWTVerifier
auth = JWTVerifier(
public_key=key_pair.public_key,
audience="dice-server",
)
mcp = FastMCP(name="Dice Roller", auth=auth)
```
Here is a complete example that you can copy/paste. For simplicity and the purposes of this example only, it will print the token to the console. **Do NOT do this in production!**
```python server.py [expandable]
from fastmcp import FastMCP
from fastmcp.server.auth import JWTVerifier
from fastmcp.server.auth.providers.jwt import RSAKeyPair
import random
key_pair = RSAKeyPair.generate()
access_token = key_pair.create_token(audience="dice-server")
auth = JWTVerifier(
public_key=key_pair.public_key,
audience="dice-server",
)
mcp = FastMCP(name="Dice Roller", auth=auth)
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]
if __name__ == "__main__":
print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
mcp.run(transport="http", port=8000)
```
### Client Authentication
If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
```text
Error code: 400 - {
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "MCP server 'dice-server' requires authentication. Please provide an authorization_token.",
},
}
```
To authenticate the client, you can pass the token using the `authorization_token` parameter in your MCP server configuration:
```python {8, 21}
import anthropic
from rich import print
# Your server URL (replace with your actual URL)
url = 'https://your-server-url.com'
# Your access token (replace with your actual token)
access_token = 'your-access-token'
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1000,
messages=[{"role": "user", "content": "Roll a few dice!"}],
mcp_servers=[
{
"type": "url",
"url": f"{url}/mcp/",
"name": "dice-server",
"authorization_token": access_token
}
],
extra_headers={
"anthropic-beta": "mcp-client-2025-04-04"
}
)
print(response.content)
```
You should now see the dice roll results in the output.

View file

@ -0,0 +1,195 @@
---
title: Auth0 OAuth 🤝 FastMCP
sidebarTitle: Auth0
description: Secure your FastMCP server with Auth0 OAuth
icon: shield-check
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.12.4" />
This guide shows you how to secure your FastMCP server using **Auth0 OAuth**. While Auth0 does have support for Dynamic Client Registration, it is not enabled by default so this integration uses the [**OIDC Proxy**](/servers/auth/oidc-proxy) pattern to bridge Auth0's dynamic OIDC configuration with MCP's authentication requirements.
## Configuration
### Prerequisites
Before you begin, you will need:
1. An **[Auth0 Account](https://auth0.com/)** with access to create Applications
2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)
### Step 1: Create an Auth0 Application
Create an Application in your Auth0 settings to get the credentials needed for authentication:
<Steps>
<Step title="Navigate to Applications">
Go to **Applications → Applications** in your Auth0 account.
Click **"+ Create Application"** to create a new application.
</Step>
<Step title="Create Your Application">
- **Name**: Choose a name users will recognize (e.g., "My FastMCP Server")
- **Choose an application type**: Choose "Single Page Web Applications"
- Click **Create** to create the application
</Step>
<Step title="Configure Your Application">
Select the "Settings" tab for your application, then find the "Application URIs" section.
- **Allowed Callback URLs**: Your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`)
- Click **Save** to save your changes
<Warning>
The callback URL must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter.
</Warning>
<Tip>
If you want to use a custom callback path (e.g., `/auth/auth0/callback`), make sure to set the same path in both your Auth0 Application settings and the `redirect_path` parameter when configuring the Auth0Provider.
</Tip>
</Step>
<Step title="Save Your Credentials">
After creating the app, in the "Basic Information" section you'll see:
- **Client ID**: A public identifier like `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`
- **Client Secret**: A private hidden value that should always be stored securely
<Tip>
Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production.
</Tip>
</Step>
<Step title="Select Your Audience">
Go to **Applications → APIs** in your Auth0 account.
- Find the API that you want to use for your application
- **API Audience**: A URL that uniquely identifies the API
<Tip>
Store this along with of the credentials above. Never commit this to version control. Use environment variables or a secrets manager in production.
</Tip>
</Step>
</Steps>
### Step 2: FastMCP Configuration
Create your FastMCP server using the `Auth0Provider`.
```python server.py
from fastmcp import FastMCP
from fastmcp.server.auth.providers.auth0 import Auth0Provider
# The Auth0Provider utilizes Auth0 OIDC configuration
auth_provider = Auth0Provider(
config_url="https://.../.well-known/openid-configuration", # Your Auth0 configuration URL
client_id="tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB", # Your Auth0 application Client ID
client_secret="vPYqbjemq...", # Your Auth0 application Client Secret
audience="https://...", # Your Auth0 API audience
base_url="http://localhost:8000", # Must match your application configuration
# redirect_path="/auth/callback" # Default value, customize if needed
)
mcp = FastMCP(name="Auth0 Secured App", auth=auth_provider)
# Add a protected tool to test authentication
@mcp.tool
async def get_token_info() -> dict:
"""Returns information about the Auth0 token."""
from fastmcp.server.dependencies import get_access_token
token = get_access_token()
return {
"issuer": token.claims.get("iss"),
"audience": token.claims.get("aud"),
"scope": token.claims.get("scope")
}
```
## Testing
### Running the Server
Start your FastMCP server with HTTP transport to enable OAuth flows:
```bash
fastmcp run server.py --transport http --port 8000
```
Your server is now running and protected by Auth0 authentication.
### Testing with a Client
Create a test client that authenticates with your Auth0-protected server:
```python test_client.py
from fastmcp import Client
import asyncio
async def main():
# The client will automatically handle Auth0 OAuth flows
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
# First-time connection will open Auth0 login in your browser
print("✓ Authenticated with Auth0!")
# Test the protected tool
result = await client.call_tool("get_token_info")
print(f"Auth0 audience: {result['audience']}")
if __name__ == "__main__":
asyncio.run(main())
```
When you run the client for the first time:
1. Your browser will open to Auth0's authorization page
2. After you authorize the app, you'll be redirected back
3. The client receives the token and can make authenticated requests
## Production Configuration
<VersionBadge version="2.13.0" />
For production deployments with persistent token management across server restarts, configure `jwt_signing_key`, and `client_storage`:
```python server.py
import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.auth0 import Auth0Provider
from key_value.aio.stores.redis import RedisStore
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
# Production setup with encrypted persistent token storage
auth_provider = Auth0Provider(
config_url="https://.../.well-known/openid-configuration",
client_id="tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB",
client_secret="vPYqbjemq...",
audience="https://...",
base_url="https://your-production-domain.com",
# Production token management
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
client_storage=FernetEncryptionWrapper(
key_value=RedisStore(
host=os.environ["REDIS_HOST"],
port=int(os.environ["REDIS_PORT"])
),
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
)
)
mcp = FastMCP(name="Production Auth0 App", auth=auth_provider)
```
<Note>
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
</Note>
<Info>
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
</Info>

View file

@ -0,0 +1,106 @@
---
title: AuthKit 🤝 FastMCP
sidebarTitle: AuthKit
description: Secure your FastMCP server with AuthKit by WorkOS
icon: shield-check
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.11.0" />
This guide shows you how to secure your FastMCP server using WorkOS's **AuthKit**, a complete authentication and user management solution. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern with [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html) resource indicators: AuthKit issues tokens whose `aud` claim is bound to your server's resource URL, and FastMCP validates that claim automatically.
## Configuration
### Prerequisites
Before you begin, you will need:
1. A **[WorkOS Account](https://workos.com/)** and a new **Project**.
2. An **[AuthKit](https://www.authkit.com/)** instance configured within your WorkOS project.
3. Your FastMCP server's URL (can be localhost for development, e.g., `http://127.0.0.1:8000`).
### Step 1: WorkOS Dashboard
In the WorkOS Dashboard, go to **Connect → Configuration** and configure:
<Steps>
<Step title="MCP Auth">
Enable **Dynamic Client Registration** (DCR) so MCP clients can register themselves. Alternatively, enable **Client ID Metadata Document** (CIMD) if your clients support it.
</Step>
<Step title="MCP resource indicators">
Add your FastMCP server's resource URL (e.g., `http://127.0.0.1:8000/mcp`) as a valid resource indicator.
This must exactly match what FastMCP advertises in its protected resource metadata. Start your server first and it will log the correct URL on startup — copy that value.
Without this step, AuthKit falls back to a default environment-scoped audience and audience validation will fail with a 401.
</Step>
<Step title="Note Your AuthKit Domain">
Find your **AuthKit Domain** on the configuration page. It will look like `https://your-project-12345.authkit.app`. You'll need this for your FastMCP server configuration.
</Step>
</Steps>
### Step 2: FastMCP Configuration
Create your FastMCP server file and use the `AuthKitProvider` to handle all the OAuth integration automatically:
```python server.py
from fastmcp import FastMCP
from fastmcp.server.auth.providers.workos import AuthKitProvider
# AuthKitProvider automatically discovers WorkOS endpoints, configures JWT
# validation, and binds the token audience to this server's resource URL.
auth_provider = AuthKitProvider(
authkit_domain="https://your-project-12345.authkit.app",
base_url="http://127.0.0.1:8000", # Use your actual server URL
)
mcp = FastMCP(name="AuthKit Secured App", auth=auth_provider)
```
When the server starts, it logs the resource URL it is validating against. Paste that URL into your Dashboard's **MCP resource indicators** list.
## Testing
To test your server, you can use the `fastmcp` CLI to run it locally. Assuming you've saved the above code to `server.py` (after replacing the `authkit_domain` and `base_url` with your actual values!), you can run the following command:
```bash
fastmcp run server.py --transport http --port 8000
```
AuthKit defaults DCR clients to `client_secret_basic` for token exchange, which conflicts with how some MCP clients send credentials. To avoid token exchange errors, register as a public client by setting `token_endpoint_auth_method` to `"none"`:
```python client.py
from fastmcp import Client
from fastmcp.client.auth import OAuth
import asyncio
auth = OAuth(additional_client_metadata={"token_endpoint_auth_method": "none"})
async def main():
async with Client("http://127.0.0.1:8000/mcp", auth=auth) as client:
assert await client.ping()
if __name__ == "__main__":
asyncio.run(main())
```
## Production Configuration
For production deployments, load sensitive configuration from environment variables:
```python server.py
import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.workos import AuthKitProvider
# Load configuration from environment variables
auth = AuthKitProvider(
authkit_domain=os.environ.get("AUTHKIT_DOMAIN"),
base_url=os.environ.get("BASE_URL", "https://your-server.com"),
)
mcp = FastMCP(name="AuthKit Secured App", auth=auth)
```

View file

@ -0,0 +1,278 @@
---
title: AWS Cognito OAuth 🤝 FastMCP
sidebarTitle: AWS Cognito
description: Secure your FastMCP server with AWS Cognito user pools
icon: aws
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.12.4" />
This guide shows you how to secure your FastMCP server using **AWS Cognito user pools**. Since AWS Cognito doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge AWS Cognito's traditional OAuth with MCP's authentication requirements. It also includes robust JWT token validation, ensuring enterprise-grade authentication.
## Configuration
### Prerequisites
Before you begin, you will need:
1. An **[AWS Account](https://aws.amazon.com/)** with access to create AWS Cognito user pools
2. Basic familiarity with AWS Cognito concepts (user pools, app clients)
3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)
### Step 1: Create an AWS Cognito User Pool and App Client
Set up AWS Cognito user pool with an app client to get the credentials needed for authentication:
<Steps>
<Step title="Navigate to AWS Cognito">
Go to the **[AWS Cognito Console](https://console.aws.amazon.com/cognito/)** and ensure you're in your desired AWS region.
Select **"User pools"** from the side navigation (click on the hamburger icon at the top left in case you don't see any), and click **"Create user pool"** to create a new user pool.
</Step>
<Step title="Define Your Application">
AWS Cognito now provides a streamlined setup experience:
1. **Application type**: Select **"Traditional web application"** (this is the correct choice for FastMCP server-side authentication)
2. **Name your application**: Enter a descriptive name (e.g., `FastMCP Server`)
The traditional web application type automatically configures:
- Server-side authentication with client secrets
- Authorization code grant flow
- Appropriate security settings for confidential clients
<Info>
Choose "Traditional web application" rather than SPA, Mobile app, or Machine-to-machine options. This ensures proper OAuth 2.0 configuration for FastMCP.
</Info>
</Step>
<Step title="Configure Options">
AWS will guide you through configuration options:
- **Sign-in identifiers**: Choose how users will sign in (email, username, or phone)
- **Required attributes**: Select any additional user information you need
- **Return URL**: Add your callback URL (e.g., `http://localhost:8000/auth/callback` for development)
<Tip>
The simplified interface handles most OAuth security settings automatically based on your application type selection.
</Tip>
</Step>
<Step title="Review and Create">
Review your configuration and click **"Create user pool"**.
After creation, you'll see your user pool details. Save these important values:
- **User pool ID** (format: `eu-central-1_XXXXXXXXX`)
- **Client ID** (found under → "Applications" → "App clients" in the side navigation → \<Your application name, e.g., `FastMCP Server`\> → "App client information")
- **Client Secret** (found under → "Applications" → "App clients" in the side navigation → \<Your application name, e.g., `FastMCP Server`\> → "App client information")
<Tip>
The user pool ID and app client credentials are all you need for FastMCP configuration.
</Tip>
</Step>
<Step title="Configure OAuth Settings">
Under "Login pages" in your app client's settings, you can double check and adjust the OAuth configuration:
- **Allowed callback URLs**: Add your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`)
- **Allowed sign-out URLs**: Optional, for logout functionality
- **OAuth 2.0 grant types**: Ensure "Authorization code grant" is selected
- **OpenID Connect scopes**: Select scopes your application needs (e.g., `openid`, `email`, `profile`)
<Tip>
For local development, you can use `http://localhost` URLs. For production, you must use HTTPS.
</Tip>
</Step>
<Step title="Configure Resource Server">
AWS Cognito requires a resource server entry to support OAuth with protected resources. Without this, token exchange will fail with an `invalid_grant` error.
Navigate to **"Branding" → "Domain"** in the side navigation, then:
1. Click **"Create resource server"**
2. **Resource server name**: Enter a descriptive name (e.g., `My MCP Server`)
3. **Resource server identifier**: Enter your MCP endpoint URL exactly as it will be accessed (e.g., `http://localhost:8000/mcp` for development, or `https://your-server.com/mcp` for production)
4. Click **"Create resource server"**
<Warning>
The resource server identifier must exactly match your `base_url + mcp_path`. For the default configuration with `base_url="http://localhost:8000"` and `path="/mcp"`, use `http://localhost:8000/mcp`.
</Warning>
</Step>
<Step title="Save Your Credentials">
After setup, you'll have:
- **User Pool ID**: Format like `eu-central-1_XXXXXXXXX`
- **Client ID**: Your application's client identifier
- **Client Secret**: Generated client secret (keep secure)
- **AWS Region**: Where Your AWS Cognito user pool is located
<Tip>
Store these credentials securely. Never commit them to version control. Use environment variables or AWS Secrets Manager in production.
</Tip>
</Step>
</Steps>
### Step 2: FastMCP Configuration
Create your FastMCP server using the `AWSCognitoProvider`, which handles AWS Cognito's JWT tokens and user claims automatically:
```python server.py
from fastmcp import FastMCP
from fastmcp.server.auth.providers.aws import AWSCognitoProvider
from fastmcp.server.dependencies import get_access_token
# The AWSCognitoProvider handles JWT validation and user claims
auth_provider = AWSCognitoProvider(
user_pool_id="eu-central-1_XXXXXXXXX", # Your AWS Cognito user pool ID
aws_region="eu-central-1", # AWS region (defaults to eu-central-1)
client_id="your-app-client-id", # Your app client ID
client_secret="your-app-client-secret", # Your app client Secret
base_url="http://localhost:8000", # Must match your callback URL
# redirect_path="/auth/callback" # Default value, customize if needed
)
mcp = FastMCP(name="AWS Cognito Secured App", auth=auth_provider)
# Add a protected tool to test authentication
@mcp.tool
async def get_access_token_claims() -> dict:
"""Get the authenticated user's access token claims."""
token = get_access_token()
return {
"sub": token.claims.get("sub"),
"username": token.claims.get("username"),
"cognito:groups": token.claims.get("cognito:groups", []),
}
```
## Testing
### Running the Server
Start your FastMCP server with HTTP transport to enable OAuth flows:
```bash
fastmcp run server.py --transport http --port 8000
```
Your server is now running and protected by AWS Cognito OAuth authentication.
### Testing with a Client
Create a test client that authenticates with Your AWS Cognito-protected server:
```python test_client.py
from fastmcp import Client
import asyncio
async def main():
# The client will automatically handle AWS Cognito OAuth
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
# First-time connection will open AWS Cognito login in your browser
print("✓ Authenticated with AWS Cognito!")
# Test the protected tool
print("Calling protected tool: get_access_token_claims")
result = await client.call_tool("get_access_token_claims")
user_data = result.data
print("Available access token claims:")
print(f"- sub: {user_data.get('sub', 'N/A')}")
print(f"- username: {user_data.get('username', 'N/A')}")
print(f"- cognito:groups: {user_data.get('cognito:groups', [])}")
if __name__ == "__main__":
asyncio.run(main())
```
When you run the client for the first time:
1. Your browser will open to AWS Cognito's hosted UI login page
2. After you sign in (or sign up), you'll be redirected back to your MCP server
3. The client receives the JWT token and can make authenticated requests
<Info>
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
</Info>
## Production Configuration
<VersionBadge version="2.13.0" />
For production deployments with persistent token management across server restarts, configure `jwt_signing_key`, and `client_storage`:
```python server.py
import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.aws import AWSCognitoProvider
from key_value.aio.stores.redis import RedisStore
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
# Production setup with encrypted persistent token storage
auth_provider = AWSCognitoProvider(
user_pool_id="eu-central-1_XXXXXXXXX",
aws_region="eu-central-1",
client_id="your-app-client-id",
client_secret="your-app-client-secret",
base_url="https://your-production-domain.com",
# Production token management
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
client_storage=FernetEncryptionWrapper(
key_value=RedisStore(
host=os.environ["REDIS_HOST"],
port=int(os.environ["REDIS_PORT"])
),
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
)
)
mcp = FastMCP(name="Production AWS Cognito App", auth=auth_provider)
```
<Note>
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
</Note>
## Features
### JWT Token Validation
The AWS Cognito provider includes robust JWT token validation:
- **Signature Verification**: Validates tokens against AWS Cognito's public keys (JWKS)
- **Expiration Checking**: Automatically rejects expired tokens
- **Issuer Validation**: Ensures tokens come from your specific AWS Cognito user pool
- **Scope Enforcement**: Verifies required OAuth scopes are present
### User Claims and Groups
Access rich user information from AWS Cognito JWT tokens:
```python
from fastmcp.server.dependencies import get_access_token
@mcp.tool
async def admin_only_tool() -> str:
"""A tool only available to admin users."""
token = get_access_token()
user_groups = token.claims.get("cognito:groups", [])
if "admin" not in user_groups:
raise ValueError("This tool requires admin access")
return "Admin access granted!"
```
### Enterprise Integration
Perfect for enterprise environments with:
- **Single Sign-On (SSO)**: Integrate with corporate identity providers
- **Multi-Factor Authentication (MFA)**: Leverage AWS Cognito's built-in MFA
- **User Groups**: Role-based access control through AWS Cognito groups
- **Custom Attributes**: Access custom user attributes defined in your AWS Cognito user pool
- **Compliance**: Meet enterprise security and compliance requirements

View file

@ -0,0 +1,542 @@
---
title: Azure (Microsoft Entra ID) OAuth 🤝 FastMCP
sidebarTitle: Azure (Entra ID)
description: Secure your FastMCP server with Azure/Microsoft Entra OAuth
icon: microsoft
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.13.0" />
This guide shows you how to secure your FastMCP server using **Azure OAuth** (Microsoft Entra ID). Since Azure doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge Azure's traditional OAuth with MCP's authentication requirements. FastMCP validates Azure JWTs against your application's client_id.
## Configuration
### Prerequisites
Before you begin, you will need:
1. An **[Azure Account](https://portal.azure.com/)** with access to create App registrations
2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)
3. Your Azure tenant ID (found in Azure Portal under Microsoft Entra ID)
### Step 1: Create an Azure App Registration
Create an App registration in Azure Portal to get the credentials needed for authentication:
<Steps>
<Step title="Navigate to App registrations">
Go to the [Azure Portal](https://portal.azure.com) and navigate to **Microsoft Entra ID → App registrations**.
Click **"New registration"** to create a new application.
</Step>
<Step title="Configure Your Application">
Fill in the application details:
- **Name**: Choose a name users will recognize (e.g., "My FastMCP Server")
- **Supported account types**: Choose based on your needs:
- **Single tenant**: Only users in your organization
- **Multitenant**: Users in any Microsoft Entra directory
- **Multitenant + personal accounts**: Any Microsoft account
- **Redirect URI**: Select "Web" and enter your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`)
<Warning>
The redirect URI must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter. For local development, Azure allows `http://localhost` URLs. For production, you must use HTTPS.
</Warning>
<Tip>
If you want to use a custom callback path (e.g., `/auth/azure/callback`), make sure to set the same path in both your Azure App registration and the `redirect_path` parameter when configuring the AzureProvider.
</Tip>
- **Expose an API**: Configure your Application ID URI and define scopes
- Go to **Expose an API** in the App registration sidebar.
- Click **Set** next to "Application ID URI" and choose one of:
- Keep the default `api://{client_id}`
- Set a custom value, following the supported formats (see [Identifier URI restrictions](https://learn.microsoft.com/en-us/entra/identity-platform/identifier-uri-restrictions))
- Click **Add a scope** and create a scope your app will require, for example:
- Scope name: `read` (or `write`, etc.)
- Admin consent display name/description: as appropriate for your org
- Who can consent: as needed (Admins only or Admins and users)
- **Configure Access Token Version**: Ensure your app uses access token v2
- Go to **Manifest** in the App registration sidebar.
- Find the `requestedAccessTokenVersion` property and set it to `2`:
```json
"api": {
"requestedAccessTokenVersion": 2
}
```
- Click **Save** at the top of the manifest editor.
<Warning>
Access token v2 is required for FastMCP's Azure integration to work correctly. If this is not set, you may encounter authentication errors.
</Warning>
<Note>
In FastMCP's `AzureProvider`, set `identifier_uri` to your Application ID URI (optional; defaults to `api://{client_id}`) and set `required_scopes` to the unprefixed scope names (e.g., `read`, `write`). During authorization, FastMCP automatically prefixes scopes with your `identifier_uri`.
</Note>
</Step>
<Step title="Create Client Secret">
After registration, navigate to **Certificates & secrets** in your app's settings.
- Click **"New client secret"**
- Add a description (e.g., "FastMCP Server")
- Choose an expiration period
- Click **"Add"**
<Warning>
Copy the secret value immediately - it won't be shown again! You'll need to create a new secret if you lose it.
</Warning>
</Step>
<Step title="Note Your Credentials">
From the **Overview** page of your app registration, note:
- **Application (client) ID**: A UUID like `835f09b6-0f0f-40cc-85cb-f32c5829a149`
- **Directory (tenant) ID**: A UUID like `08541b6e-646d-43de-a0eb-834e6713d6d5`
- **Client Secret**: The value you copied in the previous step
<Tip>
Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production.
</Tip>
</Step>
</Steps>
### Step 2: FastMCP Configuration
Create your FastMCP server using the `AzureProvider`, which handles Azure's OAuth flow automatically:
```python server.py
from fastmcp import FastMCP
from fastmcp.server.auth.providers.azure import AzureProvider
# The AzureProvider handles Azure's token format and validation
auth_provider = AzureProvider(
client_id="835f09b6-0f0f-40cc-85cb-f32c5829a149", # Your Azure App Client ID
client_secret="your-client-secret", # Your Azure App Client Secret
tenant_id="08541b6e-646d-43de-a0eb-834e6713d6d5", # Your Azure Tenant ID (REQUIRED)
base_url="http://localhost:8000", # Must match your App registration
required_scopes=["your-scope"], # At least one scope REQUIRED - name of scope from your App
# identifier_uri defaults to api://{client_id}
# identifier_uri="api://your-api-id",
# Optional: request additional upstream scopes in the authorize request
# additional_authorize_scopes=["User.Read", "openid", "email"],
# redirect_path="/auth/callback" # Default value, customize if needed
# base_authority="login.microsoftonline.us" # For Azure Government (default: login.microsoftonline.com)
)
mcp = FastMCP(name="Azure Secured App", auth=auth_provider)
# Add a protected tool to test authentication
@mcp.tool
async def get_user_info() -> dict:
"""Returns information about the authenticated Azure user."""
from fastmcp.server.dependencies import get_access_token
token = get_access_token()
# The AzureProvider stores user data in token claims
return {
"azure_id": token.claims.get("sub"),
"email": token.claims.get("email"),
"name": token.claims.get("name"),
"job_title": token.claims.get("job_title"),
"office_location": token.claims.get("office_location")
}
```
<Note>
**Important**: The `tenant_id` parameter is **REQUIRED**. Azure no longer supports using "common" for new applications due to security requirements. You must use one of:
- **Your specific tenant ID**: Found in Azure Portal (e.g., `08541b6e-646d-43de-a0eb-834e6713d6d5`)
- **"organizations"**: For work and school accounts only
- **"consumers"**: For personal Microsoft accounts only
Using your specific tenant ID is recommended for better security and control.
</Note>
<Note>
**Important**: The `required_scopes` parameter is **REQUIRED** and must include at least one scope. Azure's OAuth API requires the `scope` parameter in all authorization requests - you cannot authenticate without specifying at least one scope. Use the unprefixed scope names from your Azure App registration (e.g., `["read", "write"]`). These scopes must be created under **Expose an API** in your App registration.
</Note>
### Scope Handling
FastMCP automatically prefixes `required_scopes` with your `identifier_uri` (e.g., `api://your-client-id`) since these are your custom API scopes. Scopes in `additional_authorize_scopes` are sent as-is since they target external resources like Microsoft Graph.
**`required_scopes`** — Your custom API scopes, defined in Azure "Expose an API":
| You write | Sent to Azure | Validated on tokens |
|-----------|---------------|---------------------|
| `mcp-read` | `api://xxx/mcp-read` | ✓ |
| `my.scope` | `api://xxx/my.scope` | ✓ |
| `openid` | `openid` | ✗ (OIDC scope) |
| `api://xxx/read` | `api://xxx/read` | ✓ |
**`additional_authorize_scopes`** — External scopes (e.g., Microsoft Graph) for server-side use:
| You write | Sent to Azure | Validated on tokens |
|-----------|---------------|---------------------|
| `User.Read` | `User.Read` | ✗ |
| `Mail.Send` | `Mail.Send` | ✗ |
<Note>
`offline_access` is automatically included to obtain refresh tokens. FastMCP manages token refreshing automatically.
</Note>
<Info>
**Why aren't `additional_authorize_scopes` validated?** Azure issues separate tokens per resource. The access token FastMCP receives is for *your API*—Graph scopes aren't in its `scp` claim. To call Graph APIs, your server uses the upstream Azure token in an on-behalf-of (OBO) flow.
</Info>
<Note>
OIDC scopes (`openid`, `profile`, `email`, `offline_access`) are never prefixed and excluded from validation because Azure doesn't include them in access token `scp` claims.
</Note>
## Testing
### Running the Server
Start your FastMCP server with HTTP transport to enable OAuth flows:
```bash
fastmcp run server.py --transport http --port 8000
```
Your server is now running and protected by Azure OAuth authentication.
### Testing with a Client
Create a test client that authenticates with your Azure-protected server:
```python test_client.py
from fastmcp import Client
import asyncio
async def main():
# The client will automatically handle Azure OAuth
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
# First-time connection will open Azure login in your browser
print("✓ Authenticated with Azure!")
# Test the protected tool
result = await client.call_tool("get_user_info")
print(f"Azure user: {result['email']}")
print(f"Name: {result['name']}")
if __name__ == "__main__":
asyncio.run(main())
```
When you run the client for the first time:
1. Your browser will open to Microsoft's authorization page
2. Sign in with your Microsoft account (work, school, or personal based on your tenant configuration)
3. Grant the requested permissions
4. After authorization, you'll be redirected back
5. The client receives the token and can make authenticated requests
<Info>
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
</Info>
## Production Configuration
<VersionBadge version="2.13.0" />
For production deployments with persistent token management across server restarts, configure `jwt_signing_key` and `client_storage`:
```python server.py
import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.azure import AzureProvider
from key_value.aio.stores.redis import RedisStore
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
# Production setup with encrypted persistent token storage
auth_provider = AzureProvider(
client_id="835f09b6-0f0f-40cc-85cb-f32c5829a149",
client_secret="your-client-secret",
tenant_id="08541b6e-646d-43de-a0eb-834e6713d6d5",
base_url="https://your-production-domain.com",
required_scopes=["your-scope"],
# Production token management
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
client_storage=FernetEncryptionWrapper(
key_value=RedisStore(
host=os.environ["REDIS_HOST"],
port=int(os.environ["REDIS_PORT"])
),
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
)
)
mcp = FastMCP(name="Production Azure App", auth=auth_provider)
```
<Note>
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
</Note>
## Token Verification Only (Managed Identity)
<VersionBadge version="2.15.0" />
For deployments where your server only needs to **validate incoming tokens** — such as Azure Container Apps with Managed Identity — use `AzureJWTVerifier` with `RemoteAuthProvider` instead of the full `AzureProvider`.
This pattern is ideal when:
- Your infrastructure handles authentication (e.g., Managed Identity)
- You don't need the OAuth proxy flow (no `client_secret` required)
- You just need to verify that incoming Azure AD tokens are valid
```python server.py
from fastmcp import FastMCP
from fastmcp.server.auth import RemoteAuthProvider
from fastmcp.server.auth.providers.azure import AzureJWTVerifier
from pydantic import AnyHttpUrl
tenant_id = "your-tenant-id"
client_id = "your-client-id"
# AzureJWTVerifier auto-configures JWKS, issuer, and audience
verifier = AzureJWTVerifier(
client_id=client_id,
tenant_id=tenant_id,
required_scopes=["access_as_user"], # Scope names from Azure Portal
)
auth = RemoteAuthProvider(
token_verifier=verifier,
authorization_servers=[
AnyHttpUrl(f"https://login.microsoftonline.com/{tenant_id}/v2.0")
],
base_url="https://your-container-app.azurecontainerapps.io",
)
mcp = FastMCP(name="Azure MI App", auth=auth)
```
`AzureJWTVerifier` handles Azure's scope format automatically. You write scope names exactly as they appear in Azure Portal under **Expose an API** (e.g., `access_as_user`). The verifier validates tokens using the short-form scopes that Azure puts in the `scp` claim, while advertising the full URI scopes (e.g., `api://your-client-id/access_as_user`) in OAuth metadata so MCP clients know what to request.
<Note>
For Azure Government, pass `base_authority="login.microsoftonline.us"` to `AzureJWTVerifier`.
</Note>
## On-Behalf-Of (OBO)
<VersionBadge version="3.0.0" />
The On-Behalf-Of (OBO) flow allows your FastMCP server to call downstream Microsoft APIs—like Microsoft Graph—using the authenticated user's identity. When a user authenticates to your MCP server, you receive a token for your API. OBO exchanges that token for a new token that can call other services, maintaining the user's identity and permissions throughout the chain.
This pattern is useful when your tools need to access user-specific data from Microsoft services: reading emails, accessing calendar events, querying SharePoint, or any other Graph API operation that requires user context.
<Note>
OBO features require the `azure` extra:
```bash
pip install 'fastmcp[azure]'
```
</Note>
### Azure Portal Setup
OBO requires additional configuration in your Azure App registration beyond basic authentication.
<Steps>
<Step title="Add API Permissions">
In your App registration, navigate to **API permissions** and add the Microsoft Graph permissions your tools will need.
- Click **Add a permission** → **Microsoft Graph** → **Delegated permissions**
- Select the permissions required for your use case (e.g., `Mail.Read`, `Calendars.Read`, `User.Read`)
- Repeat for any other APIs you need to call
<Warning>
Only add delegated permissions for OBO. Application permissions bypass user context entirely and are inappropriate for the OBO flow.
</Warning>
</Step>
<Step title="Grant Admin Consent">
OBO requires admin consent for the permissions you've added. In the **API permissions** page, click **Grant admin consent for [Your Organization]**.
Without admin consent, OBO token exchanges will fail with an `AADSTS65001` error indicating the user or administrator hasn't consented to use the application.
<Tip>
For development, you can grant consent for just your own account. For production, an Azure AD administrator must grant tenant-wide consent.
</Tip>
</Step>
</Steps>
### Configure AzureProvider for OBO
The `additional_authorize_scopes` parameter tells Azure which downstream API permissions to include during the initial authorization. These scopes establish what your server can request through OBO later.
```python server.py
from fastmcp import FastMCP
from fastmcp.server.auth.providers.azure import AzureProvider
auth_provider = AzureProvider(
client_id="your-client-id",
client_secret="your-client-secret",
tenant_id="your-tenant-id",
base_url="http://localhost:8000",
required_scopes=["mcp-access"], # Your API scope
# Include Graph scopes for OBO
additional_authorize_scopes=[
"https://graph.microsoft.com/Mail.Read",
"https://graph.microsoft.com/User.Read",
"offline_access", # Enables refresh tokens
],
)
mcp = FastMCP(name="Graph-Enabled Server", auth=auth_provider)
```
Scopes listed in `additional_authorize_scopes` are requested during the initial OAuth flow but aren't validated on incoming tokens. They establish permission for your server to later exchange the user's token for downstream API access.
<Info>
Use fully-qualified scope URIs for downstream APIs (e.g., `https://graph.microsoft.com/Mail.Read`). Short forms like `Mail.Read` work for authorization requests, but fully-qualified URIs are clearer and avoid ambiguity.
</Info>
### EntraOBOToken Dependency
The `EntraOBOToken` dependency handles the complete OBO flow automatically. Declare it as a parameter default with the scopes you need, and FastMCP exchanges the user's token for a downstream API token before your function runs.
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.azure import AzureProvider, EntraOBOToken
import httpx
auth_provider = AzureProvider(
client_id="your-client-id",
client_secret="your-client-secret",
tenant_id="your-tenant-id",
base_url="http://localhost:8000",
required_scopes=["mcp-access"],
additional_authorize_scopes=[
"https://graph.microsoft.com/Mail.Read",
"https://graph.microsoft.com/User.Read",
],
)
mcp = FastMCP(name="Email Reader", auth=auth_provider)
@mcp.tool
async def get_recent_emails(
count: int = 10,
graph_token: str = EntraOBOToken(["https://graph.microsoft.com/Mail.Read"]),
) -> list[dict]:
"""Get the user's recent emails from Microsoft Graph."""
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://graph.microsoft.com/v1.0/me/messages?$top={count}",
headers={"Authorization": f"Bearer {graph_token}"},
)
response.raise_for_status()
data = response.json()
return [
{"subject": msg["subject"], "from": msg["from"]["emailAddress"]["address"]}
for msg in data.get("value", [])
]
```
The `graph_token` parameter receives a ready-to-use access token for Microsoft Graph. FastMCP handles the OBO exchange transparently—your function just uses the token to call the API.
<Warning>
**Scope alignment is critical.** The scopes passed to `EntraOBOToken` must be a subset of the scopes in `additional_authorize_scopes`. If you request a scope during OBO that wasn't included in the initial authorization, the exchange will fail.
</Warning>
<Tip>
For advanced OBO scenarios, use `CurrentAccessToken()` to get the user's token, then construct an `azure.identity.aio.OnBehalfOfCredential` directly with your Azure credentials.
</Tip>
<Tip>
For a complete working example of Azure OBO with FastMCP, see [Pamela Fox's blog post on OBO flow for Entra-based MCP servers](https://blog.pamelafox.org/2026/01/using-on-behalf-of-flow-for-entra-based.html).
</Tip>
## Azure AD B2C
<VersionBadge version="3.3.0" />
Azure AD B2C (Business-to-Consumer) uses different endpoints, scope URIs, and
token issuers than standard Microsoft Entra ID. The `AzureProvider.from_b2c()`
factory handles all of these differences automatically.
<Warning>
Azure AD B2C does **not** support the On-Behalf-Of (OBO) flow. If you need
OBO for downstream API calls, use `AzureProvider` with standard Entra ID
instead.
</Warning>
### Quick Start
```python server.py
from fastmcp import FastMCP
from fastmcp.server.auth.providers.azure import AzureProvider
auth = AzureProvider.from_b2c(
tenant_name="mytenant",
policy_name="B2C_1_susi",
client_id="00000000-0000-0000-0000-000000000000",
client_secret="my-secret",
required_scopes=["mcp-access"],
base_url="https://myserver.com",
)
mcp = FastMCP("My App", auth=auth)
```
`from_b2c()` derives the following values automatically:
| Derived value | Formula |
|---|---|
| Authority host | `{tenant_name}.b2clogin.com` |
| Authorization endpoint | `https://{tenant_name}.b2clogin.com/{tenant_name}.onmicrosoft.com/{policy_name}/oauth2/v2.0/authorize` |
| Token endpoint | `https://{tenant_name}.b2clogin.com/{tenant_name}.onmicrosoft.com/{policy_name}/oauth2/v2.0/token` |
| Scope identifier URI | `https://{tenant_name}.onmicrosoft.com/{client_id}` |
### Token Issuer Validation
B2C access tokens carry the **tenant GUID** (not the `.onmicrosoft.com` name)
in the `iss` claim, and the exact format varies by policy and custom-domain
configuration. `from_b2c()` therefore **disables issuer validation by
default**; **audience validation still enforces that tokens target the correct
application**.
Once you have confirmed a successful end-to-end login, read the actual `iss`
value from the decoded claims and enable strict validation:
```python
auth = AzureProvider.from_b2c(
tenant_name="mytenant",
policy_name="B2C_1_susi",
client_id="00000000-0000-0000-0000-000000000000",
client_secret="my-secret",
required_scopes=["mcp-access"],
base_url="https://myserver.com",
token_issuer="https://mytenant.b2clogin.com/11111111-2222-3333-4444-555555555555/v2.0/",
)
```
### Custom Domains
If your B2C tenant uses a [custom domain](https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-domain)
(e.g. `auth.mycompany.com` instead of `mytenant.b2clogin.com`), pass it via
`custom_domain`:
```python
auth = AzureProvider.from_b2c(
tenant_name="mytenant",
policy_name="B2C_1_susi",
client_id="00000000-0000-0000-0000-000000000000",
client_secret="my-secret",
required_scopes=["mcp-access"],
base_url="https://myserver.com",
custom_domain="auth.mycompany.com",
)
```

View file

@ -0,0 +1,157 @@
---
title: ChatGPT 🤝 FastMCP
sidebarTitle: ChatGPT
description: Connect FastMCP servers to ChatGPT in Chat and Deep Research modes
icon: message-smile
---
[ChatGPT](https://chatgpt.com/) supports MCP servers through remote HTTP connections in two modes: **Chat mode** for interactive conversations and **Deep Research mode** for comprehensive information retrieval.
<Tip>
**Developer Mode Required for Chat Mode**: To use MCP servers in regular ChatGPT conversations, you must first enable Developer Mode in your ChatGPT settings. This feature is available for ChatGPT Pro, Team, Enterprise, and Edu users.
</Tip>
<Note>
OpenAI's official MCP documentation and examples are built with **FastMCP v2**! Learn more from their [MCP documentation](https://platform.openai.com/docs/mcp) and [Developer Mode guide](https://platform.openai.com/docs/guides/developer-mode).
</Note>
## Build a Server
First, let's create a simple FastMCP server:
```python server.py
from fastmcp import FastMCP
import random
mcp = FastMCP("Demo Server")
@mcp.tool
def roll_dice(sides: int = 6) -> int:
"""Roll a dice with the specified number of sides."""
return random.randint(1, sides)
if __name__ == "__main__":
mcp.run(transport="http", port=8000)
```
### Deploy Your Server
Your server must be accessible from the internet. For development, use `ngrok`:
<CodeGroup>
```bash Terminal 1
python server.py
```
```bash Terminal 2
ngrok http 8000
```
</CodeGroup>
Note your public URL (e.g., `https://abc123.ngrok.io`) for the next steps.
## Chat Mode
Chat mode lets you use MCP tools directly in ChatGPT conversations. See [OpenAI's Developer Mode guide](https://platform.openai.com/docs/guides/developer-mode) for the latest requirements.
### Add to ChatGPT
#### 1. Enable Developer Mode
1. Open ChatGPT and go to **Settings** → **Connectors**
2. Under **Advanced**, toggle **Developer Mode** to enabled
#### 2. Create Connector
1. In **Settings** → **Connectors**, click **Create**
2. Enter:
- **Name**: Your server name
- **Server URL**: `https://your-server.ngrok.io/mcp/`
3. Check **I trust this provider**
4. Add authentication if needed
5. Click **Create**
<Note>
**Without Developer Mode**: If you don't have search/fetch tools, ChatGPT will reject the server. With Developer Mode enabled, you don't need search/fetch tools for Chat mode.
</Note>
#### 3. Use in Chat
1. Start a new chat
2. Click the **+** button → **More** → **Developer Mode**
3. **Enable your MCP server connector** (required - the connector must be explicitly added to each chat)
4. Now you can use your tools:
Example usage:
- "Roll a 20-sided dice"
- "Roll dice" (uses default 6 sides)
<Tip>
The connector must be explicitly enabled in each chat session through Developer Mode. Once added, it remains active for the entire conversation.
</Tip>
### Skip Confirmations
Use `annotations=ToolAnnotations(readOnlyHint=True)` to skip confirmation prompts for read-only tools:
```python
from mcp.types import ToolAnnotations
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_status() -> str:
"""Check system status."""
return "All systems operational"
@mcp.tool() # No annotation - ChatGPT may ask for confirmation
def delete_item(id: str) -> str:
"""Delete an item."""
return f"Deleted {id}"
```
## Deep Research Mode
Deep Research mode provides systematic information retrieval with citations. See [OpenAI's MCP documentation](https://platform.openai.com/docs/mcp) for the latest Deep Research specifications.
<Warning>
**Search and Fetch Required**: Without Developer Mode, ChatGPT will reject any server that doesn't have both `search` and `fetch` tools. Even in Developer Mode, Deep Research only uses these two tools.
</Warning>
### Tool Implementation
Deep Research tools must follow this pattern:
```python
@mcp.tool()
def search(query: str) -> dict:
"""
Search for records matching the query.
Must return {"ids": [list of string IDs]}
"""
# Your search logic
matching_ids = ["id1", "id2", "id3"]
return {"ids": matching_ids}
@mcp.tool()
def fetch(id: str) -> dict:
"""
Fetch a complete record by ID.
Return the full record data for ChatGPT to analyze.
"""
# Your fetch logic
return {
"id": id,
"title": "Record Title",
"content": "Full record content...",
"metadata": {"author": "Jane Doe", "date": "2024"}
}
```
### Using Deep Research
1. Ensure your server is added to ChatGPT's connectors (same as Chat mode)
2. Start a new chat
3. Click **+** → **Deep Research**
4. Select your MCP server as a source
5. Ask research questions
ChatGPT will use your `search` and `fetch` tools to find and cite relevant information.

View file

@ -0,0 +1,177 @@
---
title: Claude Code 🤝 FastMCP
sidebarTitle: Claude Code
description: Install and use FastMCP servers in Claude Code
icon: message-smile
---
import { VersionBadge } from "/snippets/version-badge.mdx"
import { LocalFocusTip } from "/snippets/local-focus.mdx"
<LocalFocusTip />
[Claude Code](https://docs.anthropic.com/en/docs/claude-code) supports MCP servers through multiple transport methods including STDIO, SSE, and HTTP, allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
## Requirements
This integration uses STDIO transport to run your FastMCP server locally. For remote deployments, you can run your FastMCP server with HTTP or SSE transport and configure it directly using Claude Code's built-in MCP management commands.
## Create a Server
The examples in this guide will use the following simple dice-rolling server, saved as `server.py`.
```python server.py
import random
from fastmcp import FastMCP
mcp = FastMCP(name="Dice Roller")
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]
if __name__ == "__main__":
mcp.run()
```
## Install the Server
### FastMCP CLI
<VersionBadge version="2.10.3" />
The easiest way to install a FastMCP server in Claude Code is using the `fastmcp install claude-code` command. This automatically handles the configuration, dependency management, and calls Claude Code's built-in MCP management system.
```bash
fastmcp install claude-code server.py
```
The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file:
```bash
# These are equivalent if your server object is named 'mcp'
fastmcp install claude-code server.py
fastmcp install claude-code server.py:mcp
# Use explicit object name if your server has a different name
fastmcp install claude-code server.py:my_custom_server
```
The command will automatically configure the server with Claude Code's `claude mcp add` command.
#### Dependencies
FastMCP provides flexible dependency management options for your Claude Code servers:
**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times:
```bash
fastmcp install claude-code server.py --with pandas --with requests
```
**Requirements file**: If you maintain a `requirements.txt` file with all your dependencies, use `--with-requirements` to install them:
```bash
fastmcp install claude-code server.py --with-requirements requirements.txt
```
**Editable packages**: For local packages under development, use `--with-editable` to install them in editable mode:
```bash
fastmcp install claude-code server.py --with-editable ./my-local-package
```
Alternatively, you can use a `fastmcp.json` configuration file (recommended):
```json fastmcp.json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "server.py",
"entrypoint": "mcp"
},
"environment": {
"dependencies": ["pandas", "requests"]
}
}
```
#### Python Version and Project Configuration
Control the Python environment for your server with these options:
**Python version**: Use `--python` to specify which Python version your server requires. This ensures compatibility when your server needs specific Python features:
```bash
fastmcp install claude-code server.py --python 3.11
```
**Project directory**: Use `--project` to run your server within a specific project context. This tells `uv` to use the project's configuration files and virtual environment:
```bash
fastmcp install claude-code server.py --project /path/to/my-project
```
#### Environment Variables
If your server needs environment variables (like API keys), you must include them:
```bash
fastmcp install claude-code server.py --server-name "Weather Server" \
--env API_KEY=your-api-key \
--env DEBUG=true
```
Or load them from a `.env` file:
```bash
fastmcp install claude-code server.py --server-name "Weather Server" --env-file .env
```
<Warning>
**Claude Code must be installed**. The integration looks for the Claude Code CLI at the default installation location (`~/.claude/local/claude`) and uses the `claude mcp add` command to register servers.
</Warning>
### Manual Configuration
For more control over the configuration, you can manually use Claude Code's built-in MCP management commands. This gives you direct control over how your server is launched:
```bash
# Add a server with custom configuration
claude mcp add dice-roller -- uv run --with fastmcp fastmcp run server.py
# Add with environment variables
claude mcp add weather-server -e API_KEY=secret -e DEBUG=true -- uv run --with fastmcp fastmcp run server.py
# Add with specific scope (local, user, or project)
claude mcp add my-server --scope user -- uv run --with fastmcp fastmcp run server.py
```
You can also manually specify Python versions and project directories in your Claude Code commands:
```bash
# With specific Python version
claude mcp add ml-server -- uv run --python 3.11 --with fastmcp fastmcp run server.py
# Within a project directory
claude mcp add project-server -- uv run --project /path/to/project --with fastmcp fastmcp run server.py
```
## Using the Server
Once your server is installed, you can start using your FastMCP server with Claude Code.
Try asking Claude something like:
> "Roll some dice for me"
Claude will automatically detect your `roll_dice` tool and use it to fulfill your request, returning something like:
> I'll roll some dice for you! Here are your results: [4, 2, 6]
>
> You rolled three dice and got a 4, a 2, and a 6!
Claude Code can now access all the tools, resources, and prompts you've defined in your FastMCP server.
If your server provides resources, you can reference them with `@` mentions using the format `@server:protocol://resource/path`. If your server provides prompts, you can use them as slash commands with `/mcp__servername__promptname`.

View file

@ -0,0 +1,299 @@
---
title: Claude Desktop 🤝 FastMCP
sidebarTitle: Claude Desktop
description: Connect FastMCP servers to Claude Desktop
icon: message-smile
---
import { VersionBadge } from "/snippets/version-badge.mdx"
import { LocalFocusTip } from "/snippets/local-focus.mdx"
<LocalFocusTip />
[Claude Desktop](https://www.claude.com/download) supports MCP servers through local STDIO connections and remote servers (beta), allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
<Note>
Remote MCP server support is currently in beta and available for users on Claude Pro, Max, Team, and Enterprise plans (as of June 2025). Most users will still need to use local STDIO connections.
</Note>
<Note>
This guide focuses specifically on using FastMCP servers with Claude Desktop. For general Claude Desktop MCP setup and official examples, see the [official Claude Desktop quickstart guide](https://modelcontextprotocol.io/quickstart/user).
</Note>
## Requirements
Claude Desktop traditionally requires MCP servers to run locally using STDIO transport, where your server communicates with Claude through standard input/output rather than HTTP. However, users on certain plans now have access to remote server support as well.
<Tip>
If you don't have access to remote server support or need to connect to remote servers, you can create a **proxy server** that runs locally via STDIO and forwards requests to remote HTTP servers. See the [Proxy Servers](#proxy-servers) section below.
</Tip>
## Create a Server
The examples in this guide will use the following simple dice-rolling server, saved as `server.py`.
```python server.py
import random
from fastmcp import FastMCP
mcp = FastMCP(name="Dice Roller")
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]
if __name__ == "__main__":
mcp.run()
```
## Install the Server
### FastMCP CLI
<VersionBadge version="2.10.3" />
The easiest way to install a FastMCP server in Claude Desktop is using the `fastmcp install claude-desktop` command. This automatically handles the configuration and dependency management.
<Tip>
Prior to version 2.10.3, Claude Desktop could be managed by running `fastmcp install <path>` without specifying the client.
</Tip>
```bash
fastmcp install claude-desktop server.py
```
The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file:
```bash
# These are equivalent if your server object is named 'mcp'
fastmcp install claude-desktop server.py
fastmcp install claude-desktop server.py:mcp
# Use explicit object name if your server has a different name
fastmcp install claude-desktop server.py:my_custom_server
```
After installation, restart Claude Desktop completely. You should see a hammer icon (🔨) in the bottom left of the input box, indicating that MCP tools are available.
#### Dependencies
FastMCP provides several ways to manage your server's dependencies when installing in Claude Desktop:
**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times:
```bash
fastmcp install claude-desktop server.py --with pandas --with requests
```
**Requirements file**: If you have a `requirements.txt` file listing all your dependencies, use `--with-requirements` to install them all at once:
```bash
fastmcp install claude-desktop server.py --with-requirements requirements.txt
```
**Editable packages**: For local packages in development, use `--with-editable` to install them in editable mode:
```bash
fastmcp install claude-desktop server.py --with-editable ./my-local-package
```
Alternatively, you can use a `fastmcp.json` configuration file (recommended):
```json fastmcp.json
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "server.py",
"entrypoint": "mcp"
},
"environment": {
"dependencies": ["pandas", "requests"]
}
}
```
#### Python Version and Project Directory
FastMCP allows you to control the Python environment for your server:
**Python version**: Use `--python` to specify which Python version your server should run with. This is particularly useful when your server requires a specific Python version:
```bash
fastmcp install claude-desktop server.py --python 3.11
```
**Project directory**: Use `--project` to run your server within a specific project directory. This ensures that `uv` will discover all `pyproject.toml`, `uv.toml`, and `.python-version` files from that project:
```bash
fastmcp install claude-desktop server.py --project /path/to/my-project
```
When you specify a project directory, all relative paths in your server will be resolved from that directory, and the project's virtual environment will be used.
#### Environment Variables
<Warning>
Claude Desktop runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs.
</Warning>
If your server needs environment variables (like API keys), you must include them:
```bash
fastmcp install claude-desktop server.py --server-name "Weather Server" \
--env API_KEY=your-api-key \
--env DEBUG=true
```
Or load them from a `.env` file:
```bash
fastmcp install claude-desktop server.py --server-name "Weather Server" --env-file .env
```
<Warning>
- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies.
- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop.
</Warning>
### Manual Configuration
For more control over the configuration, you can manually edit Claude Desktop's configuration file. You can open the configuration file from Claude's developer settings, or find it in the following locations:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
The configuration file is a JSON object with a `mcpServers` key, which contains the configuration for each MCP server.
```json
{
"mcpServers": {
"dice-roller": {
"command": "python",
"args": ["path/to/your/server.py"]
}
}
}
```
After updating the configuration file, restart Claude Desktop completely. Look for the hammer icon (🔨) to confirm your server is loaded.
#### Dependencies
If your server has dependencies, you can use `uv` or another package manager to set up the environment.
When manually configuring dependencies, the recommended approach is to use `uv` with FastMCP. The configuration uses `uv run` to create an isolated environment with your specified packages:
```json
{
"mcpServers": {
"dice-roller": {
"command": "uv",
"args": [
"run",
"--with", "fastmcp",
"--with", "pandas",
"--with", "requests",
"fastmcp",
"run",
"path/to/your/server.py"
]
}
}
}
```
You can also manually specify Python versions and project directories in your configuration. Add `--python` to use a specific Python version, or `--project` to run within a project directory:
```json
{
"mcpServers": {
"dice-roller": {
"command": "uv",
"args": [
"run",
"--python", "3.11",
"--project", "/path/to/project",
"--with", "fastmcp",
"fastmcp",
"run",
"path/to/your/server.py"
]
}
}
}
```
The order of arguments matters: Python version and project settings come before package specifications, which come before the actual command to run.
<Warning>
- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies.
- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop.
</Warning>
#### Environment Variables
You can also specify environment variables in the configuration:
```json
{
"mcpServers": {
"weather-server": {
"command": "python",
"args": ["path/to/weather_server.py"],
"env": {
"API_KEY": "your-api-key",
"DEBUG": "true"
}
}
}
}
```
<Warning>
Claude Desktop runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs.
</Warning>
## Remote Servers
Users on Claude Pro, Max, Team, and Enterprise plans have first-class remote server support via integrations. For other users, or as an alternative approach, FastMCP can create a proxy server that forwards requests to a remote HTTP server. You can install the proxy server in Claude Desktop.
Create a proxy server that connects to a remote HTTP server:
```python proxy_server.py
from fastmcp.server import create_proxy
# Create a proxy to a remote server
proxy = create_proxy(
"https://example.com/mcp/sse",
name="Remote Server Proxy"
)
if __name__ == "__main__":
proxy.run() # Runs via STDIO for Claude Desktop
```
### Authentication
For authenticated remote servers, create an authenticated client following the guidance in the [client auth documentation](/clients/auth/bearer) and pass it to the proxy:
```python auth_proxy_server.py {7}
from fastmcp import Client
from fastmcp.client.auth import BearerAuth
from fastmcp.server import create_proxy
# Create authenticated client
client = Client(
"https://api.example.com/mcp/sse",
auth=BearerAuth(token="your-access-token")
)
# Create proxy using the authenticated client
proxy = create_proxy(client, name="Authenticated Proxy")
if __name__ == "__main__":
proxy.run()
```

Some files were not shown because too many files have changed in this diff Show more