Skip to content

Commit ba9897e

Browse files
authored
Merge pull request #46 from tadata-org/refactor/low-level-mcp-server-instead-of-fastmcp
Refactor and API Redesign
2 parents 0da1298 + 5985af8 commit ba9897e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2910
-1688
lines changed

.coveragerc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[run]
2+
omit =
3+
examples/*

.github/codecov.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
base: pr
6+
target: auto
7+
threshold: 0.5%
8+
informational: false
9+
only_pulls: true

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ jobs:
3030
3131
- name: Build and publish
3232
env:
33-
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
34-
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
33+
TWINE_USERNAME: __token__
34+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
3535
run: |
3636
uv run python -m build
3737
uv run twine check dist/*

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,5 @@ ehthumbs.db
151151
Desktop.ini
152152

153153
# Repomix output
154-
repomix-output.txt
154+
repomix-output.txt
155+
repomix-output.xml

README.md

Lines changed: 104 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
- **Automatic discovery** of all FastAPI endpoints and conversion to MCP tools
2323
- **Preserving schemas** of your request models and response models
2424
- **Preserve documentation** of all your endpoints, just as it is in Swagger
25-
- **Extend** - Add custom MCP tools alongside the auto-generated ones
25+
- **Flexible deployment** - Mount your MCP server to the same app, or deploy separately
2626

2727
## Installation
2828

@@ -44,45 +44,125 @@ The simplest way to use FastAPI-MCP is to add an MCP server directly to your Fas
4444

4545
```python
4646
from fastapi import FastAPI
47-
from fastapi_mcp import add_mcp_server
47+
from fastapi_mcp import FastApiMCP
4848

49-
# Your FastAPI app
5049
app = FastAPI()
5150

52-
# Mount the MCP server to your app
53-
add_mcp_server(
54-
app, # Your FastAPI app
55-
mount_path="/mcp", # Where to mount the MCP server
56-
name="My API MCP", # Name for the MCP server
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",
5758
)
59+
60+
# Mount the MCP server directly to your FastAPI app
61+
mcp.mount()
5862
```
5963

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

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+
68+
## Tool Naming
69+
70+
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.
71+
72+
Compare these two endpoint definitions:
73+
74+
```python
75+
# Auto-generated operation_id (something like "read_user_users__user_id__get")
76+
@app.get("/users/{user_id}")
77+
async def read_user(user_id: int):
78+
return {"user_id": user_id}
79+
80+
# Explicit operation_id (tool will be named "get_user_info")
81+
@app.get("/users/{user_id}", operation_id="get_user_info")
82+
async def read_user(user_id: int):
83+
return {"user_id": user_id}
84+
```
85+
86+
For clearer, more intuitive tool names, we recommend adding explicit `operation_id` parameters to your FastAPI route definitions.
87+
88+
To find out more, read FastAPI's official docs about [advanced config of path operations.](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/)
89+
6290
## Advanced Usage
6391

6492
FastAPI-MCP provides several ways to customize and control how your MCP server is created and configured. Here are some advanced usage patterns:
6593

94+
### Customizing Schema Description
95+
6696
```python
6797
from fastapi import FastAPI
68-
from fastapi_mcp import add_mcp_server
98+
from fastapi_mcp import FastApiMCP
6999

70100
app = FastAPI()
71101

72-
mcp_server = add_mcp_server(
73-
app, # Your FastAPI app
74-
mount_path="/mcp", # Where to mount the MCP server
75-
name="My API MCP", # Name for the MCP server
76-
describe_all_responses=True, # False by default. Include all possible response schemas in tool descriptions, instead of just the successful response.
77-
describe_full_response_schema=True # False by default. Include full JSON schema in tool descriptions, instead of just an LLM-friendly response example.
102+
mcp = FastApiMCP(
103+
app,
104+
name="My API MCP",
105+
base_url="http://localhost:8000",
106+
describe_all_responses=True, # Include all possible response schemas in tool descriptions
107+
describe_full_response_schema=True # Include full JSON schema in tool descriptions
78108
)
79109

80-
# Optionally add custom tools in addition to existing APIs.
81-
@mcp_server.tool()
82-
async def get_server_time() -> str:
83-
"""Get the current server time."""
84-
from datetime import datetime
85-
return datetime.now().isoformat()
110+
mcp.mount()
111+
```
112+
113+
### Deploying Separately from Original FastAPI App
114+
115+
You are not limited to serving the MCP on the same FastAPI app from which it was created.
116+
117+
You can create an MCP server from one FastAPI app, and mount it to a different app:
118+
119+
```python
120+
from fastapi import FastAPI
121+
from fastapi_mcp import FastApiMCP
122+
123+
# Your API app
124+
api_app = FastAPI()
125+
# ... define your API endpoints on api_app ...
126+
127+
# A separate app for the MCP server
128+
mcp_app = FastAPI()
129+
130+
# Create MCP server from the API app
131+
mcp = FastApiMCP(
132+
api_app,
133+
base_url="http://api-host:8001", # The URL where the API app will be running
134+
)
135+
136+
# Mount the MCP server to the separate app
137+
mcp.mount(mcp_app)
138+
139+
# Now you can run both apps separately:
140+
# uvicorn main:api_app --host api-host --port 8001
141+
# uvicorn main:mcp_app --host mcp-host --port 8000
142+
```
143+
144+
### Adding Endpoints After MCP Server Creation
145+
146+
If you add endpoints to your FastAPI app after creating the MCP server, you'll need to refresh the server to include them:
147+
148+
```python
149+
from fastapi import FastAPI
150+
from fastapi_mcp import FastApiMCP
151+
152+
app = FastAPI()
153+
# ... define initial endpoints ...
154+
155+
# Create MCP server
156+
mcp = FastApiMCP(app)
157+
mcp.mount()
158+
159+
# Add new endpoints after MCP server creation
160+
@app.get("/new/endpoint/", operation_id="new_endpoint")
161+
async def new_endpoint():
162+
return {"message": "Hello, world!"}
163+
164+
# Refresh the MCP server to include the new endpoint
165+
mcp.setup_server()
86166
```
87167

88168
## Examples
@@ -137,25 +217,19 @@ Find the path to mcp-proxy by running in Terminal: `which mcp-proxy`.
137217

138218
## Development and Contributing
139219

140-
**Notice:** We are currently refactoring our MCP auto-generation system. To avoid potential conflicts, we kindly request that you delay submitting contributions until this notice is removed from the README. Thank you for your understanding and patience.
141-
142-
Thank you for considering contributing to FastAPI-MCP open source projects! It's people like you that make it a reality for users in our community.
220+
Thank you for considering contributing to FastAPI-MCP! We encourage the community to post Issues and Pull Requests.
143221

144-
Before you get started, please see [CONTRIBUTING.md](CONTRIBUTING.md).
222+
Before you get started, please see our [Contribution Guide](CONTRIBUTING.md).
145223

146224
## Community
147225

148226
Join [MCParty Slack community](https://join.slack.com/t/themcparty/shared_invite/zt-30yxr1zdi-2FG~XjBA0xIgYSYuKe7~Xg) to connect with other MCP enthusiasts, ask questions, and share your experiences with FastAPI-MCP.
149227

150228
## Requirements
151229

152-
- Python 3.10+
230+
- Python 3.10+ (Recommended 3.12)
153231
- uv
154232

155233
## License
156234

157235
MIT License. Copyright (c) 2024 Tadata Inc.
158-
159-
## About
160-
161-
Developed and maintained by [Tadata Inc.](https://github.com/tadata-org)

examples/__init__.py

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from examples.shared.apps import items
2+
from examples.shared.setup import setup_logging
3+
4+
from fastapi_mcp import FastApiMCP
5+
6+
setup_logging()
7+
8+
9+
# 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+
describe_full_response_schema=True, # Describe the full response JSON-schema instead of just a response example
16+
describe_all_responses=True, # Describe all the possible responses instead of just the success (2XX) response
17+
)
18+
19+
mcp.mount()
20+
21+
22+
if __name__ == "__main__":
23+
import uvicorn
24+
25+
uvicorn.run(items.app, host="0.0.0.0", port=8000)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from examples.shared.apps import items
2+
from examples.shared.setup import setup_logging
3+
4+
from fastapi import APIRouter
5+
from fastapi_mcp import FastApiMCP
6+
7+
setup_logging()
8+
9+
10+
router = APIRouter(prefix="/other/route")
11+
items.app.include_router(router)
12+
13+
mcp = FastApiMCP(
14+
items.app,
15+
name="Item API MCP",
16+
description="MCP server for the Item API",
17+
base_url="http://localhost:8000",
18+
)
19+
20+
# Mount the MCP server to a specific router.
21+
# It will now only be available at `/other/route/mcp`
22+
mcp.mount(router)
23+
24+
25+
if __name__ == "__main__":
26+
import uvicorn
27+
28+
uvicorn.run(items.app, host="0.0.0.0", port=8000)

examples/reregister_tools_example.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from examples.shared.apps import items
2+
from examples.shared.setup import setup_logging
3+
4+
from fastapi_mcp import FastApiMCP
5+
6+
setup_logging()
7+
8+
9+
# 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+
)
16+
17+
18+
# MCP server
19+
mcp.mount()
20+
21+
22+
# 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])
24+
async def new_endpoint():
25+
return {"message": "Hello, world!"}
26+
27+
28+
# But if you re-run the setup, the new endpoints will now be exposed.
29+
mcp.setup_server()
30+
31+
32+
if __name__ == "__main__":
33+
import uvicorn
34+
35+
uvicorn.run(items.app, host="0.0.0.0", port=8000)

examples/separate_server_example.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from fastapi import FastAPI
2+
import asyncio
3+
import uvicorn
4+
5+
from examples.shared.apps import items
6+
from examples.shared.setup import setup_logging
7+
8+
from fastapi_mcp import FastApiMCP
9+
10+
setup_logging()
11+
12+
13+
MCP_SERVER_HOST = "localhost"
14+
MCP_SERVER_PORT = 8000
15+
ITEMS_API_HOST = "localhost"
16+
ITEMS_API_PORT = 8001
17+
18+
19+
# 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+
)
24+
25+
# And then mount the MCP server to a separate FastAPI app
26+
mcp_app = FastAPI()
27+
mcp.mount(mcp_app)
28+
29+
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+
43+
if __name__ == "__main__":
44+
asyncio.run(main())

0 commit comments

Comments
 (0)