Skip to content

Commit 13c7ee2

Browse files
committed
use ASGI-transport httpx client by default, to enable easier connection between MCP server and FastAPI app, without requiring base_url and without requiring the FastAPI app to actually run.
Simplify usage examples as well. Update README.
1 parent 918d9ed commit 13c7ee2

15 files changed

+128
-238
lines changed

README.md

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,14 @@ from fastapi_mcp import FastApiMCP
4848

4949
app = FastAPI()
5050

51-
mcp = FastApiMCP(
52-
app,
53-
54-
# Optional parameters
55-
name="My API MCP",
56-
description="My API description",
57-
base_url="http://localhost:8000",
58-
)
51+
mcp = FastApiMCP(app)
5952

6053
# Mount the MCP server directly to your FastAPI app
6154
mcp.mount()
6255
```
6356

6457
That's it! Your auto-generated MCP server is now available at `https://app.base.url/mcp`.
6558

66-
> **Note on `base_url`**: While `base_url` is optional, it is highly recommended to provide it explicitly. The `base_url` tells the MCP server where to send API requests when tools are called. Without it, the library will attempt to determine the URL automatically, which may not work correctly in deployed environments where the internal and external URLs differ.
67-
6859
## Tool Naming
6960

7061
FastAPI-MCP uses the `operation_id` from your FastAPI routes as the MCP tool names. When you don't specify an `operation_id`, FastAPI auto-generates one, but these can be cryptic.
@@ -102,7 +93,6 @@ app = FastAPI()
10293
mcp = FastApiMCP(
10394
app,
10495
name="My API MCP",
105-
base_url="http://localhost:8000",
10696
describe_all_responses=True, # Include all possible response schemas in tool descriptions
10797
describe_full_response_schema=True # Include full JSON schema in tool descriptions
10898
)
@@ -178,10 +168,7 @@ api_app = FastAPI()
178168
mcp_app = FastAPI()
179169

180170
# Create MCP server from the API app
181-
mcp = FastApiMCP(
182-
api_app,
183-
base_url="http://api-host:8001", # The URL where the API app will be running
184-
)
171+
mcp = FastApiMCP(api_app)
185172

186173
# Mount the MCP server to the separate app
187174
mcp.mount(mcp_app)

examples/filtered_tools_example.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,53 @@
11
from examples.shared.apps import items
22
from examples.shared.setup import setup_logging
33

4+
from fastapi import FastAPI
45
from fastapi_mcp import FastApiMCP
56

67
setup_logging()
78

9+
app = FastAPI()
10+
app.include_router(items.router)
11+
812
# Example demonstrating how to filter MCP tools by operation IDs and tags
913

1014
# Filter by including specific operation IDs
1115
include_operations_mcp = FastApiMCP(
12-
items.app,
16+
app,
1317
name="Item API MCP - Included Operations",
1418
description="MCP server showing only specific operations",
15-
base_url="http://localhost:8000",
1619
include_operations=["get_item", "list_items"],
1720
)
1821

1922
# Filter by excluding specific operation IDs
2023
exclude_operations_mcp = FastApiMCP(
21-
items.app,
24+
app,
2225
name="Item API MCP - Excluded Operations",
2326
description="MCP server showing all operations except the excluded ones",
24-
base_url="http://localhost:8000",
2527
exclude_operations=["create_item", "update_item", "delete_item"],
2628
)
2729

2830
# Filter by including specific tags
2931
include_tags_mcp = FastApiMCP(
30-
items.app,
32+
app,
3133
name="Item API MCP - Included Tags",
3234
description="MCP server showing operations with specific tags",
33-
base_url="http://localhost:8000",
3435
include_tags=["items"],
3536
)
3637

3738
# Filter by excluding specific tags
3839
exclude_tags_mcp = FastApiMCP(
39-
items.app,
40+
app,
4041
name="Item API MCP - Excluded Tags",
4142
description="MCP server showing operations except those with specific tags",
42-
base_url="http://localhost:8000",
4343
exclude_tags=["search"],
4444
)
4545

4646
# Combine operation IDs and tags (include mode)
4747
combined_include_mcp = FastApiMCP(
48-
items.app,
48+
app,
4949
name="Item API MCP - Combined Include",
5050
description="MCP server showing operations by combining include filters",
51-
base_url="http://localhost:8000",
5251
include_operations=["delete_item"],
5352
include_tags=["search"],
5453
)
@@ -69,4 +68,4 @@
6968
print(" - /include-tags-mcp: Only operations with the 'items' tag")
7069
print(" - /exclude-tags-mcp: All operations except those with the 'search' tag")
7170
print(" - /combined-include-mcp: Operations with 'search' tag or delete_item operation")
72-
uvicorn.run(items.app, host="0.0.0.0", port=8000)
71+
uvicorn.run(items.router, host="0.0.0.0", port=8000)
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
from examples.shared.apps import items
22
from examples.shared.setup import setup_logging
33

4+
from fastapi import FastAPI
45
from fastapi_mcp import FastApiMCP
56

67
setup_logging()
78

89

10+
app = FastAPI()
11+
app.include_router(items.router)
12+
13+
914
# Add MCP server to the FastAPI app
1015
mcp = FastApiMCP(
11-
items.app,
16+
app,
1217
name="Item API MCP",
1318
description="MCP server for the Item API",
14-
base_url="http://localhost:8000",
1519
describe_full_response_schema=True, # Describe the full response JSON-schema instead of just a response example
1620
describe_all_responses=True, # Describe all the possible responses instead of just the success (2XX) response
1721
)
@@ -22,4 +26,4 @@
2226
if __name__ == "__main__":
2327
import uvicorn
2428

25-
uvicorn.run(items.app, host="0.0.0.0", port=8000)
29+
uvicorn.run(items.router, host="0.0.0.0", port=8000)
Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
11
from examples.shared.apps import items
22
from examples.shared.setup import setup_logging
33

4-
from fastapi import APIRouter
4+
from fastapi import FastAPI, APIRouter
55
from fastapi_mcp import FastApiMCP
66

77
setup_logging()
88

99

10-
router = APIRouter(prefix="/other/route")
11-
items.app.include_router(router)
10+
other_router = APIRouter(prefix="/other/route")
11+
12+
app = FastAPI()
13+
app.include_router(items.router)
14+
app.include_router(other_router)
1215

1316
mcp = FastApiMCP(
14-
items.app,
17+
app,
1518
name="Item API MCP",
1619
description="MCP server for the Item API",
1720
base_url="http://localhost:8000",
1821
)
1922

2023
# Mount the MCP server to a specific router.
2124
# It will now only be available at `/other/route/mcp`
22-
mcp.mount(router)
25+
mcp.mount(other_router)
2326

2427

2528
if __name__ == "__main__":
2629
import uvicorn
2730

28-
uvicorn.run(items.app, host="0.0.0.0", port=8000)
31+
uvicorn.run(items.router, host="0.0.0.0", port=8000)

examples/reregister_tools_example.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
from examples.shared.apps import items
22
from examples.shared.setup import setup_logging
33

4+
from fastapi import FastAPI
45
from fastapi_mcp import FastApiMCP
56

67
setup_logging()
78

89

10+
app = FastAPI()
11+
app.include_router(items.router)
12+
13+
914
# Add MCP server to the FastAPI app
10-
mcp = FastApiMCP(
11-
items.app,
12-
name="Item API MCP",
13-
description="MCP server for the Item API",
14-
base_url="http://localhost:8000",
15-
)
15+
mcp = FastApiMCP(app)
1616

1717

1818
# MCP server
1919
mcp.mount()
2020

2121

2222
# This endpoint will not be registered as a tool, since it was added after the MCP instance was created
23-
@items.app.get("/new/endpoint/", operation_id="new_endpoint", response_model=dict[str, str])
23+
@items.router.get("/new/endpoint/", operation_id="new_endpoint", response_model=dict[str, str])
2424
async def new_endpoint():
2525
return {"message": "Hello, world!"}
2626

@@ -32,4 +32,4 @@ async def new_endpoint():
3232
if __name__ == "__main__":
3333
import uvicorn
3434

35-
uvicorn.run(items.app, host="0.0.0.0", port=8000)
35+
uvicorn.run(items.router, host="0.0.0.0", port=8000)

examples/separate_server_example.py

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from fastapi import FastAPI
2-
import asyncio
32
import uvicorn
43

54
from examples.shared.apps import items
@@ -10,35 +9,23 @@
109
setup_logging()
1110

1211

13-
MCP_SERVER_HOST = "localhost"
14-
MCP_SERVER_PORT = 8000
15-
ITEMS_API_HOST = "localhost"
16-
ITEMS_API_PORT = 8001
12+
app = FastAPI()
13+
app.include_router(items.router)
1714

1815

1916
# Take the FastAPI app only as a source for MCP server generation
20-
mcp = FastApiMCP(
21-
items.app,
22-
base_url=f"http://{ITEMS_API_HOST}:{ITEMS_API_PORT}", # Note how the base URL is the **Items API** URL, not the MCP server URL
23-
)
17+
mcp = FastApiMCP(app)
18+
2419

2520
# And then mount the MCP server to a separate FastAPI app
2621
mcp_app = FastAPI()
2722
mcp.mount(mcp_app)
2823

2924

30-
def run_items_app():
31-
uvicorn.run(items.app, port=ITEMS_API_PORT)
32-
33-
34-
def run_mcp_app():
35-
uvicorn.run(mcp_app, port=MCP_SERVER_PORT)
36-
37-
38-
# The MCP server depends on the Items API to be available, so we need to run both.
39-
async def main():
40-
await asyncio.gather(asyncio.to_thread(run_items_app), asyncio.to_thread(run_mcp_app))
41-
42-
25+
# Run the MCP server separately from the original FastAPI app.
26+
# It still works 🚀
27+
# Your original API is **not exposed**, only via the MCP server.
4328
if __name__ == "__main__":
44-
asyncio.run(main())
29+
import uvicorn
30+
31+
uvicorn.run(mcp_app, host="0.0.0.0", port=8000)

examples/shared/apps/items.py

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,14 @@
22
Simple example of using FastAPI-MCP to add an MCP server to a FastAPI app.
33
"""
44

5-
from fastapi import FastAPI, HTTPException, Query
5+
from fastapi import APIRouter, HTTPException, Query
66
from pydantic import BaseModel
77
from typing import List, Optional
88

99

10-
# Create a simple FastAPI app
11-
app = FastAPI(
12-
title="Example API",
13-
description="A simple example API with integrated MCP server",
14-
version="0.1.0",
15-
)
10+
router = APIRouter()
1611

1712

18-
# Define some models
1913
class Item(BaseModel):
2014
id: int
2115
name: str
@@ -24,12 +18,10 @@ class Item(BaseModel):
2418
tags: List[str] = []
2519

2620

27-
# In-memory database
2821
items_db: dict[int, Item] = {}
2922

3023

31-
# Define some endpoints
32-
@app.get("/items/", response_model=List[Item], tags=["items"], operation_id="list_items")
24+
@router.get("/items/", response_model=List[Item], tags=["items"], operation_id="list_items")
3325
async def list_items(skip: int = 0, limit: int = 10):
3426
"""
3527
List all items in the database.
@@ -39,7 +31,7 @@ async def list_items(skip: int = 0, limit: int = 10):
3931
return list(items_db.values())[skip : skip + limit]
4032

4133

42-
@app.get("/items/{item_id}", response_model=Item, tags=["items"], operation_id="get_item")
34+
@router.get("/items/{item_id}", response_model=Item, tags=["items"], operation_id="get_item")
4335
async def read_item(item_id: int):
4436
"""
4537
Get a specific item by its ID.
@@ -51,7 +43,7 @@ async def read_item(item_id: int):
5143
return items_db[item_id]
5244

5345

54-
@app.post("/items/", response_model=Item, tags=["items"], operation_id="create_item")
46+
@router.post("/items/", response_model=Item, tags=["items"], operation_id="create_item")
5547
async def create_item(item: Item):
5648
"""
5749
Create a new item in the database.
@@ -62,7 +54,7 @@ async def create_item(item: Item):
6254
return item
6355

6456

65-
@app.put("/items/{item_id}", response_model=Item, tags=["items"], operation_id="update_item")
57+
@router.put("/items/{item_id}", response_model=Item, tags=["items"], operation_id="update_item")
6658
async def update_item(item_id: int, item: Item):
6759
"""
6860
Update an existing item.
@@ -77,7 +69,7 @@ async def update_item(item_id: int, item: Item):
7769
return item
7870

7971

80-
@app.delete("/items/{item_id}", tags=["items"], operation_id="delete_item")
72+
@router.delete("/items/{item_id}", tags=["items"], operation_id="delete_item")
8173
async def delete_item(item_id: int):
8274
"""
8375
Delete an item from the database.
@@ -91,7 +83,7 @@ async def delete_item(item_id: int):
9183
return {"message": "Item deleted successfully"}
9284

9385

94-
@app.get("/items/search/", response_model=List[Item], tags=["search"], operation_id="search_items")
86+
@router.get("/items/search/", response_model=List[Item], tags=["search"], operation_id="search_items")
9587
async def search_items(
9688
q: Optional[str] = Query(None, description="Search query string"),
9789
min_price: Optional[float] = Query(None, description="Minimum price"),
@@ -105,27 +97,23 @@ async def search_items(
10597
"""
10698
results = list(items_db.values())
10799

108-
# Filter by search query
109100
if q:
110101
q = q.lower()
111102
results = [
112103
item for item in results if q in item.name.lower() or (item.description and q in item.description.lower())
113104
]
114105

115-
# Filter by price range
116106
if min_price is not None:
117107
results = [item for item in results if item.price >= min_price]
118108
if max_price is not None:
119109
results = [item for item in results if item.price <= max_price]
120110

121-
# Filter by tags
122111
if tags:
123112
results = [item for item in results if all(tag in item.tags for tag in tags)]
124113

125114
return results
126115

127116

128-
# Add sample data
129117
sample_items = [
130118
Item(id=1, name="Hammer", description="A tool for hammering nails", price=9.99, tags=["tool", "hardware"]),
131119
Item(id=2, name="Screwdriver", description="A tool for driving screws", price=7.99, tags=["tool", "hardware"]),

0 commit comments

Comments
 (0)