Skip to content

Commit c241955

Browse files
committed
docs: add server example for fastapi application with a mounted MCP endpoint
1 parent 2cd178a commit c241955

File tree

5 files changed

+128
-6
lines changed

5 files changed

+128
-6
lines changed

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ The Context object provides the following capabilities:
676676
- `ctx.session` - Access to the underlying session for advanced communication (see [Session Properties and Methods](#session-properties-and-methods))
677677
- `ctx.request_context` - Access to request-specific data and lifespan resources (see [Request Context Properties](#request-context-properties))
678678
- `await ctx.debug(message)` - Send debug log message
679-
- `await ctx.info(message)` - Send info log message
679+
- `await ctx.info(message)` - Send info log message
680680
- `await ctx.warning(message)` - Send warning log message
681681
- `await ctx.error(message)` - Send error log message
682682
- `await ctx.log(level, message, logger_name=None)` - Send log with custom level
@@ -1106,13 +1106,13 @@ The session object accessible via `ctx.session` provides advanced control over c
11061106
async def notify_data_update(resource_uri: str, ctx: Context) -> str:
11071107
"""Update data and notify clients of the change."""
11081108
# Perform data update logic here
1109-
1109+
11101110
# Notify clients that this specific resource changed
11111111
await ctx.session.send_resource_updated(AnyUrl(resource_uri))
1112-
1112+
11131113
# If this affects the overall resource list, notify about that too
11141114
await ctx.session.send_resource_list_changed()
1115-
1115+
11161116
return f"Updated {resource_uri} and notified clients"
11171117
```
11181118

@@ -1141,11 +1141,11 @@ def query_with_config(query: str, ctx: Context) -> str:
11411141
"""Execute a query using shared database and configuration."""
11421142
# Access typed lifespan context
11431143
app_ctx: AppContext = ctx.request_context.lifespan_context
1144-
1144+
11451145
# Use shared resources
11461146
connection = app_ctx.db
11471147
settings = app_ctx.config
1148-
1148+
11491149
# Execute query with configuration
11501150
result = connection.execute(query, timeout=settings.query_timeout)
11511151
return str(result)
@@ -1548,6 +1548,10 @@ app = Starlette(
15481548
_Full example: [examples/snippets/servers/streamable_http_path_config.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_path_config.py)_
15491549
<!-- /snippet-source -->
15501550

1551+
#### Mounting to a FastAPI server
1552+
1553+
To mount a MCP Streamable HTTP endpoint on a FastAPI application see [`examples/servers/fastapi/`](examples/servers/fastapi/).
1554+
15511555
#### SSE servers
15521556

15531557
> **Note**: SSE transport is being superseded by [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http).

examples/servers/fastapi/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# FastAPI app with MCP endpoint
2+
3+
A FastAPI application with a Streamable HTTP MCP endpoint mounted on `/mcp`.
4+
5+
The key difference when mounting on FastAPI vs Starlette is that you must manually call `mcp.session_manager.run()` in your FastAPI lifespan, as FastAPI doesn't automatically trigger the lifespan of mounted sub-applications.
6+
7+
## Usage
8+
9+
Start the server:
10+
11+
```bash
12+
uv run uvicorn main:app --reload
13+
```

examples/servers/fastapi/main.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""Example showing how to mount FastMCP on a FastAPI application."""
2+
3+
import contextlib
4+
from collections.abc import AsyncIterator
5+
6+
from fastapi import FastAPI
7+
from mcp.server.fastmcp import FastMCP
8+
9+
10+
@contextlib.asynccontextmanager
11+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
12+
"""FastAPI lifespan that initializes the MCP session manager.
13+
14+
This is necessary because FastAPI doesn't automatically trigger
15+
the lifespan of mounted sub-applications. We need to manually
16+
manage the session_manager's lifecycle.
17+
"""
18+
async with mcp.session_manager.run():
19+
yield
20+
21+
22+
# Create FastAPI app with lifespan
23+
app = FastAPI(
24+
title="My API with MCP",
25+
description="Example FastAPI application with mounted MCP endpoint",
26+
version="1.0.0",
27+
lifespan=lifespan,
28+
)
29+
30+
# Create FastMCP instance
31+
mcp = FastMCP(
32+
"MCP Tools",
33+
debug=True,
34+
streamable_http_path="/",
35+
json_response=True,
36+
stateless_http=False, # Required when deploying in production with multiple workers
37+
)
38+
39+
40+
@mcp.tool()
41+
async def process_data(data: str) -> str:
42+
"""Process some data and return the result
43+
44+
Args:
45+
data: The data to process
46+
47+
Returns:
48+
The processed data
49+
"""
50+
return f"Processed: {data}"
51+
52+
53+
# Get the MCP ASGI Starlette app
54+
mcp_app = mcp.streamable_http_app()
55+
56+
# Mount the MCP app on FastAPI at /mcp
57+
app.mount("/mcp", mcp_app)
58+
59+
60+
# Add regular FastAPI endpoints
61+
@app.get("/")
62+
async def root() -> dict[str, str]:
63+
"""Root endpoint with API information"""
64+
return {
65+
"mcp_endpoint": "/mcp",
66+
"docs": "/docs",
67+
"openapi": "/openapi.json",
68+
}
69+
70+
71+
@app.post("/hello")
72+
async def hello(name: str = "World") -> dict[str, str]:
73+
"""Example FastAPI endpoint
74+
75+
Args:
76+
name: Name to greet
77+
78+
Returns:
79+
A greeting message
80+
"""
81+
return {"message": f"Hello, {name}!"}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[project]
2+
name = "fastapi-mcp"
3+
version = "0.1.0"
4+
description = "A simple FastAPI application with a MCP endpoint."
5+
readme = "README.md"
6+
requires-python = ">=3.10"
7+
keywords = ["mcp", "llm", "fastapi"]
8+
license = { text = "MIT" }
9+
dependencies = [
10+
"mcp",
11+
"fastapi>=0.123.0",
12+
]
13+
14+
[tool.ruff.lint]
15+
select = ["E", "F", "I"]
16+
ignore = []
17+
18+
[tool.ruff]
19+
line-length = 120
20+
target-version = "py310"
21+
22+
[dependency-groups]
23+
dev = ["pyright>=1.1.378", "pytest>=8.3.3", "ruff>=0.6.9"]

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ packages = ["src/mcp"]
9494
[tool.pyright]
9595
typeCheckingMode = "strict"
9696
include = ["src/mcp", "tests", "examples/servers", "examples/snippets"]
97+
exclude = ["examples/servers/fastapi"]
9798
venvPath = "."
9899
venv = ".venv"
99100
# The FastAPI style of using decorators in tests gives a `reportUnusedFunction` error.

0 commit comments

Comments
 (0)