Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
0332908
1
Parent(s): 9b4ba17
Add pattern docs
Browse files- docs/docs.json +10 -1
- docs/patterns/composition.mdx +186 -0
- docs/patterns/fastapi.mdx +114 -0
- docs/patterns/openapi.mdx +172 -0
- docs/patterns/proxying.mdx +109 -0
docs/docs.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
| 10 |
"colors": {
|
| 11 |
"dark": "#f72585",
|
| 12 |
"light": "#4cc9f0",
|
| 13 |
-
"primary": "#
|
| 14 |
},
|
| 15 |
"description": "The fast, Pythonic way to build MCP servers.",
|
| 16 |
"footer": {
|
|
@@ -54,6 +54,15 @@
|
|
| 54 |
"clients/transports"
|
| 55 |
]
|
| 56 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
{
|
| 58 |
"group": "Deployment",
|
| 59 |
"pages": []
|
|
|
|
| 10 |
"colors": {
|
| 11 |
"dark": "#f72585",
|
| 12 |
"light": "#4cc9f0",
|
| 13 |
+
"primary": "#2d00f7"
|
| 14 |
},
|
| 15 |
"description": "The fast, Pythonic way to build MCP servers.",
|
| 16 |
"footer": {
|
|
|
|
| 54 |
"clients/transports"
|
| 55 |
]
|
| 56 |
},
|
| 57 |
+
{
|
| 58 |
+
"group": "Advanced Patterns",
|
| 59 |
+
"pages": [
|
| 60 |
+
"patterns/proxying",
|
| 61 |
+
"patterns/composition",
|
| 62 |
+
"patterns/openapi",
|
| 63 |
+
"patterns/fastapi"
|
| 64 |
+
]
|
| 65 |
+
},
|
| 66 |
{
|
| 67 |
"group": "Deployment",
|
| 68 |
"pages": []
|
docs/patterns/composition.mdx
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Server Composition
|
| 3 |
+
sidebarTitle: Composition
|
| 4 |
+
description: Combine multiple FastMCP servers into a single, larger application using mounting.
|
| 5 |
+
icon: puzzle-piece
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
As your MCP applications grow, you might want to organize your tools, resources, and prompts into logical modules or reuse existing server components. FastMCP supports composition through the `server.mount()` method, allowing you to combine multiple `FastMCP` instances into a single, unified server.
|
| 9 |
+
|
| 10 |
+
## Why Compose Servers?
|
| 11 |
+
|
| 12 |
+
- **Modularity**: Break down large applications into smaller, focused servers (e.g., a `WeatherServer`, a `DatabaseServer`, a `CalendarServer`).
|
| 13 |
+
- **Reusability**: Create common utility servers (e.g., a `TextProcessingServer`) and mount them wherever needed.
|
| 14 |
+
- **Teamwork**: Different teams can work on separate FastMCP servers that are later combined.
|
| 15 |
+
- **Organization**: Keep related functionality grouped together logically.
|
| 16 |
+
|
| 17 |
+
## Mounting Subservers
|
| 18 |
+
|
| 19 |
+
The `mount()` method attaches all components (tools, resources, templates, prompts) from one `FastMCP` instance (the *subserver*) onto another (the *main server*). A `prefix` is added to avoid naming conflicts.
|
| 20 |
+
|
| 21 |
+
```python
|
| 22 |
+
from fastmcp import FastMCP
|
| 23 |
+
from typing import dict, list
|
| 24 |
+
|
| 25 |
+
# --- Define Subservers ---
|
| 26 |
+
|
| 27 |
+
# Weather Service
|
| 28 |
+
weather_mcp = FastMCP(name="WeatherService")
|
| 29 |
+
|
| 30 |
+
@weather_mcp.tool()
|
| 31 |
+
def get_forecast(city: str) -> dict:
|
| 32 |
+
"""Get weather forecast."""
|
| 33 |
+
return {"city": city, "forecast": "Sunny"}
|
| 34 |
+
|
| 35 |
+
@weather_mcp.resource("data://cities/supported")
|
| 36 |
+
def list_supported_cities() -> list[str]:
|
| 37 |
+
"""List cities with weather support."""
|
| 38 |
+
return ["London", "Paris", "Tokyo"]
|
| 39 |
+
|
| 40 |
+
# Calculator Service
|
| 41 |
+
calc_mcp = FastMCP(name="CalculatorService")
|
| 42 |
+
|
| 43 |
+
@calc_mcp.tool()
|
| 44 |
+
def add(a: int, b: int) -> int:
|
| 45 |
+
"""Add two numbers."""
|
| 46 |
+
return a + b
|
| 47 |
+
|
| 48 |
+
@calc_mcp.prompt()
|
| 49 |
+
def explain_addition() -> str:
|
| 50 |
+
"""Explain the concept of addition."""
|
| 51 |
+
return "Addition is the process of combining two or more numbers."
|
| 52 |
+
|
| 53 |
+
# --- Define Main Server ---
|
| 54 |
+
main_mcp = FastMCP(name="MainApp")
|
| 55 |
+
|
| 56 |
+
# --- Mount Subservers ---
|
| 57 |
+
# Mount weather service with prefix "weather"
|
| 58 |
+
main_mcp.mount("weather", weather_mcp)
|
| 59 |
+
|
| 60 |
+
# Mount calculator service with prefix "calc"
|
| 61 |
+
main_mcp.mount("calc", calc_mcp)
|
| 62 |
+
|
| 63 |
+
# --- Now, main_mcp contains combined components ---
|
| 64 |
+
# Tools:
|
| 65 |
+
# - "weather_get_forecast"
|
| 66 |
+
# - "calc_add"
|
| 67 |
+
# Resources:
|
| 68 |
+
# - "weather+data://cities/supported" (prefixed URI)
|
| 69 |
+
# Prompts:
|
| 70 |
+
# - "calc_explain_addition"
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
# Run the main server, which now includes components from both subservers
|
| 74 |
+
main_mcp.run()
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
### How Mounting Works
|
| 78 |
+
|
| 79 |
+
When you call `main_mcp.mount(prefix, subserver)`:
|
| 80 |
+
|
| 81 |
+
1. **Tools**: All tools from `subserver` are added to `main_mcp`. Their names are automatically prefixed using the `prefix` and a default separator (`_`).
|
| 82 |
+
- `subserver.tool(name="my_tool")` becomes `main_mcp.tool(name="{prefix}_my_tool")`.
|
| 83 |
+
2. **Resources**: All resources from `subserver` are added. Their URIs are prefixed using the `prefix` and a default separator (`+`).
|
| 84 |
+
- `subserver.resource(uri="data://info")` becomes `main_mcp.resource(uri="{prefix}+data://info")`.
|
| 85 |
+
3. **Resource Templates**: All templates from `subserver` are added. Their URI *templates* are prefixed similarly to resources.
|
| 86 |
+
- `subserver.resource(uri="data://{id}")` becomes `main_mcp.resource(uri="{prefix}+data://{id}")`.
|
| 87 |
+
4. **Prompts**: All prompts from `subserver` are added, with names prefixed like tools.
|
| 88 |
+
- `subserver.prompt(name="my_prompt")` becomes `main_mcp.prompt(name="{prefix}_my_prompt")`.
|
| 89 |
+
5. **Lifespan Management**: If the `subserver` has a `lifespan` function defined, it will be automatically executed within the `main_mcp`'s lifespan context. This ensures that setup and teardown logic for the subserver runs correctly.
|
| 90 |
+
|
| 91 |
+
### Customizing Separators
|
| 92 |
+
|
| 93 |
+
You might prefer different separators for the prefixed names and URIs. You can customize these when calling `mount()`:
|
| 94 |
+
|
| 95 |
+
```python
|
| 96 |
+
main_mcp.mount(
|
| 97 |
+
prefix="api",
|
| 98 |
+
app=some_subserver,
|
| 99 |
+
tool_separator="/", # Tool name becomes: "api/sub_tool_name"
|
| 100 |
+
resource_separator=":", # Resource URI becomes: "api:data://sub_resource"
|
| 101 |
+
prompt_separator="." # Prompt name becomes: "api.sub_prompt_name"
|
| 102 |
+
)
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
<Warning>
|
| 106 |
+
Be cautious when choosing separators. Some MCP clients (like Claude Desktop) might have restrictions on characters allowed in tool names (e.g., `/` might not be supported). The defaults (`_` for names, `+` for URIs) are generally safe.
|
| 107 |
+
</Warning>
|
| 108 |
+
|
| 109 |
+
## Example: Modular Application
|
| 110 |
+
|
| 111 |
+
```python
|
| 112 |
+
# modules/text_utils.py
|
| 113 |
+
from fastmcp import FastMCP
|
| 114 |
+
from typing import list
|
| 115 |
+
|
| 116 |
+
text_mcp = FastMCP(name="TextUtilities")
|
| 117 |
+
|
| 118 |
+
@text_mcp.tool()
|
| 119 |
+
def count_words(text: str) -> int:
|
| 120 |
+
"""Counts words in a text."""
|
| 121 |
+
return len(text.split())
|
| 122 |
+
|
| 123 |
+
@text_mcp.resource("resource://stopwords")
|
| 124 |
+
def get_stopwords() -> list[str]:
|
| 125 |
+
"""Return a list of common stopwords."""
|
| 126 |
+
return ["the", "a", "is", "in"]
|
| 127 |
+
|
| 128 |
+
# ------------------------------
|
| 129 |
+
# modules/data_api.py
|
| 130 |
+
from fastmcp import FastMCP
|
| 131 |
+
import random
|
| 132 |
+
from typing import dict
|
| 133 |
+
|
| 134 |
+
data_mcp = FastMCP(name="DataAPI")
|
| 135 |
+
|
| 136 |
+
@data_mcp.tool()
|
| 137 |
+
def fetch_record(record_id: int) -> dict:
|
| 138 |
+
"""Fetches a dummy data record."""
|
| 139 |
+
return {"id": record_id, "value": random.random()}
|
| 140 |
+
|
| 141 |
+
@data_mcp.resource("data://schema/{table}")
|
| 142 |
+
def get_table_schema(table: str) -> dict:
|
| 143 |
+
"""Provides a dummy schema for a table."""
|
| 144 |
+
return {"table": table, "columns": ["id", "value"]}
|
| 145 |
+
|
| 146 |
+
# ------------------------------
|
| 147 |
+
# main_app.py
|
| 148 |
+
from fastmcp import FastMCP
|
| 149 |
+
from modules.text_utils import text_mcp # Import server instances
|
| 150 |
+
from modules.data_api import data_mcp
|
| 151 |
+
|
| 152 |
+
app = FastMCP(name="MainApplication")
|
| 153 |
+
|
| 154 |
+
# Mount the utility servers
|
| 155 |
+
app.mount("text", text_mcp)
|
| 156 |
+
app.mount("data", data_mcp)
|
| 157 |
+
|
| 158 |
+
@app.tool()
|
| 159 |
+
def process_and_analyze(record_id: int) -> str:
|
| 160 |
+
"""Fetches a record and analyzes its string representation."""
|
| 161 |
+
# In a real application, you'd use proper methods to interact between
|
| 162 |
+
# mounted tools rather than accessing internal managers
|
| 163 |
+
|
| 164 |
+
# Get record data
|
| 165 |
+
record = {"id": record_id, "value": random.random()}
|
| 166 |
+
|
| 167 |
+
# Count words in the record string representation
|
| 168 |
+
word_count = len(str(record).split())
|
| 169 |
+
|
| 170 |
+
return (
|
| 171 |
+
f"Record {record_id} has {word_count} words in its string "
|
| 172 |
+
f"representation."
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
if __name__ == "__main__":
|
| 176 |
+
app.run()
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
Now, running `main_app.py` starts a server that exposes:
|
| 180 |
+
- `text_count_words`
|
| 181 |
+
- `data_fetch_record`
|
| 182 |
+
- `process_and_analyze`
|
| 183 |
+
- `text+resource://stopwords`
|
| 184 |
+
- `data+data://schema/{table}` (template)
|
| 185 |
+
|
| 186 |
+
This pattern promotes code organization and reuse within your FastMCP projects.
|
docs/patterns/fastapi.mdx
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: FastAPI Integration
|
| 3 |
+
sidebarTitle: FastAPI
|
| 4 |
+
description: Automatically create FastMCP servers directly from FastAPI applications.
|
| 5 |
+
icon: square-bolt
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
If you build your APIs using the popular [FastAPI](https://fastapi.tiangolo.com/) framework, FastMCP offers a seamless way to expose your FastAPI application as an MCP server. This leverages the OpenAPI integration internally but simplifies the setup significantly.
|
| 9 |
+
|
| 10 |
+
## The Goal: FastAPI App -> MCP Server
|
| 11 |
+
|
| 12 |
+
FastAPI automatically generates an OpenAPI specification for your application. FastMCP uses this built-in capability to create an MCP server that mirrors your API routes.
|
| 13 |
+
|
| 14 |
+
- FastAPI path operations (`@app.get`, `@app.post`, etc.) become MCP tools, resources, or templates.
|
| 15 |
+
- Pydantic models used in FastAPI for request/response validation are used to generate MCP schemas.
|
| 16 |
+
- Communication happens directly in memory, making it very efficient.
|
| 17 |
+
|
| 18 |
+
## Creating from FastAPI App
|
| 19 |
+
|
| 20 |
+
Use the `FastMCP.from_fastapi()` class method. You only need your FastAPI `app` instance.
|
| 21 |
+
|
| 22 |
+
```python
|
| 23 |
+
import asyncio
|
| 24 |
+
from fastapi import FastAPI
|
| 25 |
+
from pydantic import BaseModel
|
| 26 |
+
from fastmcp import FastMCP, Client # Import FastMCP and Client
|
| 27 |
+
|
| 28 |
+
# 1. Define your FastAPI application
|
| 29 |
+
api_app = FastAPI(title="MyFastAPIApp")
|
| 30 |
+
|
| 31 |
+
class Item(BaseModel):
|
| 32 |
+
name: str
|
| 33 |
+
price: float
|
| 34 |
+
is_offer: bool | None = None
|
| 35 |
+
|
| 36 |
+
@api_app.get("/")
|
| 37 |
+
def read_root():
|
| 38 |
+
return {"Hello": "World"}
|
| 39 |
+
|
| 40 |
+
@api_app.get("/items/{item_id}") # -> Resource Template
|
| 41 |
+
def read_item(item_id: int, q: str | None = None):
|
| 42 |
+
# This will become resource://openapi/read_item_items__item_id__get/{item_id}
|
| 43 |
+
return {"item_id": item_id, "q": q, "description": f"Details for item {item_id}"}
|
| 44 |
+
|
| 45 |
+
@api_app.post("/items/") # -> Tool
|
| 46 |
+
def create_item(item: Item):
|
| 47 |
+
# This will become the 'create_item_items__post' tool
|
| 48 |
+
print(f"Creating item: {item.name}")
|
| 49 |
+
return {"item_name": item.name, "status": "created"}
|
| 50 |
+
|
| 51 |
+
# 2. Create the FastMCP server directly from the FastAPI app
|
| 52 |
+
# This is an async class method
|
| 53 |
+
async def create_mcp_server_from_fastapi():
|
| 54 |
+
mcp_server = await FastMCP.from_fastapi(
|
| 55 |
+
app=api_app,
|
| 56 |
+
name="FastAPI_MCP_Bridge" # Optional name for the MCP server
|
| 57 |
+
)
|
| 58 |
+
return mcp_server
|
| 59 |
+
|
| 60 |
+
# 3. (Example) Run the MCP server and test with an in-memory client
|
| 61 |
+
async def run_and_test():
|
| 62 |
+
server = await create_mcp_server_from_fastapi()
|
| 63 |
+
print(f"Created MCP server '{server.name}' from FastAPI app '{api_app.title}'")
|
| 64 |
+
|
| 65 |
+
# List discovered components
|
| 66 |
+
tools = await server.list_tools()
|
| 67 |
+
templates = await server.list_resource_templates()
|
| 68 |
+
print("Discovered Tools:", [t.name for t in tools])
|
| 69 |
+
print("Discovered Templates:", [t.uriTemplate for t in templates])
|
| 70 |
+
|
| 71 |
+
# Test using an in-memory client
|
| 72 |
+
client = Client(server) # Uses FastMCPTransport
|
| 73 |
+
async with client:
|
| 74 |
+
# Call the tool derived from POST /items/
|
| 75 |
+
create_result = await client.call_tool(
|
| 76 |
+
"create_item_items__post",
|
| 77 |
+
{"name": "MCP Special", "price": 99.99} # Pydantic model fields become args
|
| 78 |
+
)
|
| 79 |
+
print("Create Item Tool Result:", create_result[0].text) # JSON string
|
| 80 |
+
|
| 81 |
+
# Read the resource derived from GET /items/{item_id}
|
| 82 |
+
read_result = await client.read_resource(
|
| 83 |
+
"resource://openapi/read_item_items__item_id__get/42" # Match template URI
|
| 84 |
+
)
|
| 85 |
+
print("Read Item Resource Result:", read_result[0].text) # JSON string
|
| 86 |
+
|
| 87 |
+
# In a real scenario, you might run the MCP server via stdio or sse
|
| 88 |
+
# print("Running MCP server via stdio...")
|
| 89 |
+
# server.run()
|
| 90 |
+
|
| 91 |
+
if __name__ == "__main__":
|
| 92 |
+
# Requires fastapi, uvicorn, httpx:
|
| 93 |
+
# uv pip install "fastapi[all]" httpx
|
| 94 |
+
try:
|
| 95 |
+
asyncio.run(run_and_test())
|
| 96 |
+
except ImportError as e:
|
| 97 |
+
print(f"Error: {e}. Please install required packages: uv pip install \"fastapi[all]\" httpx")
|
| 98 |
+
|
| 99 |
+
# Example Output might include:
|
| 100 |
+
# Created MCP server 'FastAPI_MCP_Bridge' from FastAPI app 'MyFastAPIApp'
|
| 101 |
+
# Discovered Tools: ['read_root___get', 'create_item_items__post']
|
| 102 |
+
# Discovered Templates: ['resource://openapi/read_item_items__item_id__get/{item_id}']
|
| 103 |
+
# Create Item Tool Result: {"item_name": "MCP Special", "status": "created"}
|
| 104 |
+
# Read Item Resource Result: {"item_id": 42, "q": null, "description": "Details for item 42"}
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
### How it Works Internally
|
| 108 |
+
|
| 109 |
+
1. **OpenAPI Generation**: `from_fastapi` asks the FastAPI `app` for its OpenAPI schema dictionary (`app.openapi()`).
|
| 110 |
+
2. **In-Memory Client**: It creates an `httpx.AsyncClient` configured with an `ASGITransport`. This special transport allows `httpx` to call the FastAPI application directly in memory without needing a running web server process.
|
| 111 |
+
3. **OpenAPI Integration**: It calls `FastMCP.from_openapi()`, passing the generated schema and the in-memory `httpx` client.
|
| 112 |
+
4. **MCP Server Creation**: The standard OpenAPI integration logic then proceeds to parse the schema and create the `Tool`, `Resource`, and `ResourceTemplate` components that wrap calls to the in-memory FastAPI app.
|
| 113 |
+
|
| 114 |
+
This provides a highly efficient way to expose your FastAPI logic through the MCP protocol, leveraging FastAPI's routing, dependency injection, and validation features.
|
docs/patterns/openapi.mdx
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: OpenAPI Integration
|
| 3 |
+
sidebarTitle: OpenAPI
|
| 4 |
+
description: Automatically create FastMCP servers from existing OpenAPI specifications.
|
| 5 |
+
icon: code-branch
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
If you have existing REST APIs documented with the OpenAPI Specification (OAS), FastMCP can automatically generate MCP tools, resources, and resource templates directly from that specification. This provides a quick way to make your existing HTTP APIs accessible to MCP clients and LLMs.
|
| 9 |
+
|
| 10 |
+
## The Goal: API -> MCP Server
|
| 11 |
+
|
| 12 |
+
The core idea is to map OpenAPI paths and operations (like `GET /users/{id}` or `POST /orders`) to their corresponding MCP components:
|
| 13 |
+
|
| 14 |
+
- `GET` requests often map to MCP **Resources** (for fetching single items) or **Resource Templates** (if the path has parameters).
|
| 15 |
+
- `POST`, `PUT`, `PATCH`, `DELETE` requests typically map to MCP **Tools** (for actions that create or modify data).
|
| 16 |
+
|
| 17 |
+
FastMCP automates this mapping process.
|
| 18 |
+
|
| 19 |
+
## Creating from OpenAPI Spec
|
| 20 |
+
|
| 21 |
+
Use the `FastMCP.from_openapi()` class method. You need:
|
| 22 |
+
|
| 23 |
+
1. The OpenAPI specification as a Python dictionary.
|
| 24 |
+
2. An `httpx.AsyncClient` configured to make requests to the actual API backend.
|
| 25 |
+
|
| 26 |
+
<CodeGroup>
|
| 27 |
+
|
| 28 |
+
```python server.py
|
| 29 |
+
import asyncio
|
| 30 |
+
import httpx
|
| 31 |
+
from fastmcp import FastMCP
|
| 32 |
+
|
| 33 |
+
# load the OpenAPI specification from the openapi_spec.py file
|
| 34 |
+
petstore_spec = PETSTORE_SPEC
|
| 35 |
+
|
| 36 |
+
# Client to communicate with the actual Pet Store API backend
|
| 37 |
+
# The base_url should match the server URL in the OpenAPI spec
|
| 38 |
+
http_client = httpx.AsyncClient(base_url="http://petstore.example.com/api")
|
| 39 |
+
|
| 40 |
+
# Create the FastMCP server from the spec
|
| 41 |
+
# This is an async class method
|
| 42 |
+
async def create_openapi_server():
|
| 43 |
+
mcp_server = await FastMCP.from_openapi(
|
| 44 |
+
openapi_spec=petstore_spec,
|
| 45 |
+
client=http_client,
|
| 46 |
+
name="PetStoreMCP" # Optional name for the MCP server
|
| 47 |
+
)
|
| 48 |
+
return mcp_server
|
| 49 |
+
|
| 50 |
+
async def run_server():
|
| 51 |
+
server = await create_openapi_server()
|
| 52 |
+
print(f"Starting OpenAPI-based server '{server.name}'...")
|
| 53 |
+
|
| 54 |
+
# List discovered components
|
| 55 |
+
tools = await server.list_tools()
|
| 56 |
+
resources = await server.list_resources()
|
| 57 |
+
templates = await server.list_resource_templates()
|
| 58 |
+
print("Discovered Tools:", [t.name for t in tools])
|
| 59 |
+
print("Discovered Resources:", [r.uri for r in resources]) # Should be empty if no parameterless GETs
|
| 60 |
+
print("Discovered Templates:", [t.uriTemplate for t in templates])
|
| 61 |
+
|
| 62 |
+
# Run the server (e.g., via stdio)
|
| 63 |
+
# server.run()
|
| 64 |
+
|
| 65 |
+
if __name__ == "__main__":
|
| 66 |
+
# Example: Create the server and print discovered components
|
| 67 |
+
# Requires httpx: uv pip install httpx
|
| 68 |
+
asyncio.run(run_server())
|
| 69 |
+
|
| 70 |
+
# Expected Output might include:
|
| 71 |
+
# Discovered Tools: ['listPets', 'createPet']
|
| 72 |
+
# Discovered Resources: []
|
| 73 |
+
# Discovered Templates: ['resource://openapi/showPetById/{petId}']
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
```python openapi_spec.py
|
| 77 |
+
# Example OpenAPI Specification (simplified Pet Store)
|
| 78 |
+
PETSTORE_SPEC = {
|
| 79 |
+
"openapi": "3.1.0",
|
| 80 |
+
"info": {"title": "Simple Pet Store", "version": "1.0.0"},
|
| 81 |
+
"servers": [{"url": "http://petstore.example.com/api"}], # Base URL for API calls
|
| 82 |
+
"paths": {
|
| 83 |
+
"/pets": {
|
| 84 |
+
"get": {
|
| 85 |
+
"summary": "List all pets",
|
| 86 |
+
"operationId": "listPets",
|
| 87 |
+
"tags": ["pets"],
|
| 88 |
+
"parameters": [{ # Query parameter -> Tool argument
|
| 89 |
+
"name": "limit", "in": "query", "schema": {"type": "integer"}
|
| 90 |
+
}],
|
| 91 |
+
"responses": {"200": {"description": "A list of pets."}},
|
| 92 |
+
},
|
| 93 |
+
"post": { # POST -> Tool
|
| 94 |
+
"summary": "Create a pet",
|
| 95 |
+
"operationId": "createPet",
|
| 96 |
+
"tags": ["pets"],
|
| 97 |
+
"requestBody": { # Request body -> Tool arguments
|
| 98 |
+
"required": True,
|
| 99 |
+
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PetInput"}}}
|
| 100 |
+
},
|
| 101 |
+
"responses": {"201": {"description": "Pet created."}},
|
| 102 |
+
},
|
| 103 |
+
},
|
| 104 |
+
"/pets/{petId}": { # Path parameter -> Resource Template
|
| 105 |
+
"get": { # GET with path param -> Resource Template / FunctionResource
|
| 106 |
+
"summary": "Info for a specific pet",
|
| 107 |
+
"operationId": "showPetById",
|
| 108 |
+
"tags": ["pets"],
|
| 109 |
+
"parameters": [{ # Path parameter -> Template function argument
|
| 110 |
+
"name": "petId", "in": "path", "required": True, "schema": {"type": "string"}
|
| 111 |
+
}],
|
| 112 |
+
"responses": {"200": {"description": "Information about the pet."}},
|
| 113 |
+
},
|
| 114 |
+
},
|
| 115 |
+
},
|
| 116 |
+
"components": {
|
| 117 |
+
"schemas": {
|
| 118 |
+
"PetInput": {"type": "object", "properties": {"name": {"type": "string"}, "tag": {"type": "string"}}},
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
</CodeGroup>
|
| 125 |
+
|
| 126 |
+
### How it Works Internally
|
| 127 |
+
|
| 128 |
+
1. **Parsing**: `from_openapi` parses the spec using utilities that leverage `openapi-pydantic`. It extracts paths, operations, parameters, request bodies, and responses.
|
| 129 |
+
2. **Mapping**: It applies mapping rules (see below) to decide whether each OpenAPI operation (`GET /pets`, `POST /pets`, `GET /pets/{petId}`) becomes an MCP `Tool`, `Resource`, or `ResourceTemplate`.
|
| 130 |
+
3. **Component Creation**: It creates specialized internal components (`OpenAPITool`, `OpenAPIResource`, `OpenAPIResourceTemplate`).
|
| 131 |
+
4. **HTTP Execution**: When an MCP client calls a tool or reads a resource from this server:
|
| 132 |
+
* The corresponding OpenAPI component constructs an HTTP request based on the OpenAPI definition and the arguments provided by the MCP client.
|
| 133 |
+
* It uses the provided `httpx.AsyncClient` to send the request to the backend API.
|
| 134 |
+
* It processes the HTTP response and returns it to the MCP client in the appropriate MCP format.
|
| 135 |
+
5. **Schema Generation**: The schemas for MCP tools are derived by combining OpenAPI parameters (path, query, header) and request body schemas. Resource template function arguments are derived from path parameters.
|
| 136 |
+
6. **Descriptions**: Tool/Resource descriptions are enhanced with information from OpenAPI responses to give the LLM more context about potential outcomes.
|
| 137 |
+
|
| 138 |
+
### Default Mapping Rules
|
| 139 |
+
|
| 140 |
+
FastMCP uses the following default rules to map OpenAPI operations:
|
| 141 |
+
|
| 142 |
+
- `GET` operation with path parameters (e.g., `/users/{id}`) -> **`ResourceTemplate`**
|
| 143 |
+
- `GET` operation without path parameters (e.g., `/users`) -> **`Resource`**
|
| 144 |
+
- `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD` -> **`Tool`**
|
| 145 |
+
|
| 146 |
+
### Customize Route Mapping
|
| 147 |
+
|
| 148 |
+
You can customize the mapping rules by providing a list of `RouteMap` objects directly to `FastMCP.from_openapi()` using the `route_maps` parameter:
|
| 149 |
+
|
| 150 |
+
```python
|
| 151 |
+
from fastmcp.server.openapi import RouteMap, RouteType
|
| 152 |
+
from fastmcp import FastMCP
|
| 153 |
+
|
| 154 |
+
# Custom mapping: Treat GET /admin/stats as a Tool, not a Resource
|
| 155 |
+
custom_maps = [
|
| 156 |
+
RouteMap(methods=["GET"], pattern=r"^/admin/stats$", route_type=RouteType.TOOL)
|
| 157 |
+
]
|
| 158 |
+
|
| 159 |
+
async def create_server_with_custom_mapping():
|
| 160 |
+
mcp_server = await FastMCP.from_openapi(
|
| 161 |
+
openapi_spec=petstore_spec,
|
| 162 |
+
client=http_client,
|
| 163 |
+
name="PetStoreMCP",
|
| 164 |
+
route_maps=custom_maps # Pass custom mapping rules
|
| 165 |
+
)
|
| 166 |
+
return mcp_server
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
Each `RouteMap` maps one or more HTTP methods and a regular expression pattern for the route path to an MCP `RouteType`. Route maps are processed in order, and the first match wins.
|
| 170 |
+
|
| 171 |
+
All parameters passed to `FastMCP.from_openapi()` will be forwarded to the underlying `FastMCPOpenAPI` constructor, so you can customize any aspect of the OpenAPI integration directly through this method call.
|
| 172 |
+
|
docs/patterns/proxying.mdx
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Proxying Servers
|
| 3 |
+
sidebarTitle: Proxying
|
| 4 |
+
description: Use FastMCP to act as an intermediary or change transport for other MCP servers.
|
| 5 |
+
icon: arrows-retweet
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
FastMCP provides a powerful proxying capability that allows one FastMCP server instance to act as a frontend for another MCP server (which could be remote, running on a different transport, or even another FastMCP instance). This is achieved using the `FastMCP.as_proxy()` class method.
|
| 9 |
+
|
| 10 |
+
## What is Proxying?
|
| 11 |
+
|
| 12 |
+
Proxying means setting up a FastMCP server that doesn't implement its own tools or resources directly. Instead, when it receives a request (like `tools/call` or `resources/read`), it forwards that request to a *backend* MCP server, receives the response, and then relays that response back to the original client.
|
| 13 |
+
|
| 14 |
+
```mermaid
|
| 15 |
+
sequenceDiagram
|
| 16 |
+
participant Client
|
| 17 |
+
participant ProxyServer as FastMCP Proxy Server
|
| 18 |
+
participant BackendServer as Backend MCP Server
|
| 19 |
+
|
| 20 |
+
Client->>ProxyServer: Request (e.g., stdio)
|
| 21 |
+
ProxyServer->>BackendServer: Request (e.g., sse)
|
| 22 |
+
BackendServer-->>ProxyServer: Response (e.g., sse)
|
| 23 |
+
ProxyServer-->>Client: Response (e.g., stdio)
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### Use Cases
|
| 27 |
+
|
| 28 |
+
- **Transport Bridging**: Expose a server running on one transport (e.g., a remote SSE server) via a different transport (e.g., local Stdio for Claude Desktop).
|
| 29 |
+
- **Adding Functionality**: Insert a layer in front of an existing server to add caching, logging, authentication, or modify requests/responses (though direct modification requires subclassing `FastMCPProxy`).
|
| 30 |
+
- **Security Boundary**: Use the proxy as a controlled gateway to an internal server.
|
| 31 |
+
- **Simplifying Client Configuration**: Provide a single, stable endpoint (the proxy) even if the backend server's location or transport changes.
|
| 32 |
+
|
| 33 |
+
## Creating a Proxy
|
| 34 |
+
|
| 35 |
+
The easiest way to create a proxy is using the `FastMCP.as_proxy()` class method. This creates a standard FastMCP server that forwards requests to another MCP server.
|
| 36 |
+
|
| 37 |
+
```python
|
| 38 |
+
from fastmcp import FastMCP, Client
|
| 39 |
+
|
| 40 |
+
# Create a client configured to talk to the backend server
|
| 41 |
+
# This could be any MCP server - remote, local, or using any transport
|
| 42 |
+
backend_client = Client("backend_server.py") # Could be "http://remote.server/sse", etc.
|
| 43 |
+
|
| 44 |
+
# Create the proxy server with as_proxy()
|
| 45 |
+
proxy_server = await FastMCP.as_proxy(
|
| 46 |
+
backend_client,
|
| 47 |
+
name="MyProxyServer" # Optional settings for the proxy
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
# That's it! You now have a proxy FastMCP server that can be used
|
| 51 |
+
# with any transport (SSE, stdio, etc.) just like any other FastMCP server
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
**How `as_proxy` Works:**
|
| 55 |
+
|
| 56 |
+
1. It connects to the backend server using the provided client.
|
| 57 |
+
2. It discovers all the tools, resources, resource templates, and prompts available on the backend server.
|
| 58 |
+
3. It creates corresponding "proxy" components that forward requests to the backend.
|
| 59 |
+
4. It returns a standard `FastMCP` server instance that can be used like any other.
|
| 60 |
+
|
| 61 |
+
### Bridging Transports
|
| 62 |
+
|
| 63 |
+
A common use case is to bridge transports. For example, making a remote SSE server available locally via Stdio:
|
| 64 |
+
|
| 65 |
+
```python
|
| 66 |
+
from fastmcp import FastMCP, Client
|
| 67 |
+
|
| 68 |
+
# Client targeting a remote SSE server
|
| 69 |
+
client = Client("http://example.com/mcp/sse")
|
| 70 |
+
|
| 71 |
+
# Create a proxy server - it's just a regular FastMCP server
|
| 72 |
+
proxy = await FastMCP.as_proxy(client, name="SSE to Stdio Proxy")
|
| 73 |
+
|
| 74 |
+
# The proxy can now be used with any transport
|
| 75 |
+
# No special handling needed - it works like any FastMCP server
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### In-Memory Proxies
|
| 79 |
+
|
| 80 |
+
You can also proxy an in-memory `FastMCP` instance, which is useful for adjusting the configuration or behavior of a server you don't completely control.
|
| 81 |
+
|
| 82 |
+
```python
|
| 83 |
+
from fastmcp import FastMCP
|
| 84 |
+
|
| 85 |
+
# Original server
|
| 86 |
+
original_server = FastMCP(name="Original")
|
| 87 |
+
|
| 88 |
+
@original_server.tool()
|
| 89 |
+
def tool_a() -> str:
|
| 90 |
+
return "A"
|
| 91 |
+
|
| 92 |
+
# Create a proxy of the original server
|
| 93 |
+
proxy = await FastMCP.as_proxy(
|
| 94 |
+
original_server,
|
| 95 |
+
name="Proxy Server"
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
# proxy is now a regular FastMCP server that forwards
|
| 99 |
+
# requests to original_server
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
## `FastMCPProxy` Class
|
| 103 |
+
|
| 104 |
+
Internally, `FastMCP.as_proxy()` uses the `FastMCPProxy` class. You generally don't need to interact with this class directly, but it's available if needed. It has two primary async constructors:
|
| 105 |
+
|
| 106 |
+
* `FastMCPProxy.from_client(client: Client, **settings)`: Creates a proxy from a client instance.
|
| 107 |
+
* `FastMCPProxy.from_server(server: FastMCP, **settings)`: Creates a proxy from another FastMCP server instance.
|
| 108 |
+
|
| 109 |
+
Using the class directly might be necessary for advanced scenarios, like subclassing `FastMCPProxy` to add custom logic before or after forwarding requests.
|