Skip to content

Refactor and API Redesign #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
599348d
gitignore repomix
shahar4499 Apr 7, 2025
97bd754
update mcp sdk version
shahar4499 Apr 7, 2025
b79667d
up minimum test coverage to prevent going down further
shahar4499 Apr 7, 2025
45600dc
WIP working after switch to lowlevel server instead of FastMCP
shahar4499 Apr 8, 2025
1bf481f
WIP - move tool call handler registration away from mount function
shahar4499 Apr 8, 2025
2a10c2e
refactor
shahar4499 Apr 8, 2025
b3471e4
remove dead code
shahar4499 Apr 8, 2025
d9c7918
remove redundant leftover code
shahar4499 Apr 8, 2025
e10a4a1
add full response schema example
shahar4499 Apr 8, 2025
222e104
remove unused imports and clean up module docstrings
shahar4499 Apr 8, 2025
97ffd8e
change to class based
shahar4499 Apr 8, 2025
7f543d9
fastapi-native routes
shahar4499 Apr 8, 2025
1f325f2
add logging to examples (override uvicorn's default logger)
shahar4499 Apr 8, 2025
a2ff5ce
Fix ASGI errors
shahar4499 Apr 8, 2025
005d54e
prep tests fixtures
shahar4499 Apr 8, 2025
b2ebc5c
refactor and add more examples
shahar4499 Apr 8, 2025
8a26851
remove redundant util
shahar4499 Apr 9, 2025
bc3738c
ignore examples in test coverage
shahar4499 Apr 9, 2025
9388b69
basic tests and fixes
shahar4499 Apr 9, 2025
9bc99f5
add more tests
shahar4499 Apr 9, 2025
e0d405d
enhance test suite and fix errors
shahar4499 Apr 9, 2025
2789bdd
fix tests
shahar4499 Apr 9, 2025
0c286e2
make setup_server method idempotent to be able to re-call it if needed
shahar4499 Apr 9, 2025
5e6544b
exclude mcp endpoints from openapi schema
shahar4499 Apr 9, 2025
ebbd0e4
improve examples
shahar4499 Apr 9, 2025
8dd0813
write final tests, improve test coverage to 92%, and set coverage lim…
shahar4499 Apr 9, 2025
fbc0398
use PyPi token instead of username+pass
shahar4499 Apr 9, 2025
eb00ebf
add codecov config
shahar4499 Apr 9, 2025
696c5b4
Merge branch 'main' into refactor/low-level-mcp-server-instead-of-fas…
shahar4499 Apr 9, 2025
99a80f2
update README
shahar4499 Apr 10, 2025
8b29e00
add example
shahar4499 Apr 10, 2025
5985af8
improve README
shahar4499 Apr 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[run]
omit =
examples/*
9 changes: 9 additions & 0 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
coverage:
status:
project:
default:
base: pr
target: auto
threshold: 0.5%
informational: false
only_pulls: true
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ jobs:

- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
uv run python -m build
uv run twine check dist/*
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,5 @@ ehthumbs.db
Desktop.ini

# Repomix output
repomix-output.txt
repomix-output.txt
repomix-output.xml
134 changes: 104 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
- **Automatic discovery** of all FastAPI endpoints and conversion to MCP tools
- **Preserving schemas** of your request models and response models
- **Preserve documentation** of all your endpoints, just as it is in Swagger
- **Extend** - Add custom MCP tools alongside the auto-generated ones
- **Flexible deployment** - Mount your MCP server to the same app, or deploy separately

## Installation

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

```python
from fastapi import FastAPI
from fastapi_mcp import add_mcp_server
from fastapi_mcp import FastApiMCP

# Your FastAPI app
app = FastAPI()

# Mount the MCP server to your app
add_mcp_server(
app, # Your FastAPI app
mount_path="/mcp", # Where to mount the MCP server
name="My API MCP", # Name for the MCP server
mcp = FastApiMCP(
app,

# Optional parameters
name="My API MCP",
description="My API description",
base_url="http://localhost:8000",
)

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

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

> **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.

## Tool Naming

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.

Compare these two endpoint definitions:

```python
# Auto-generated operation_id (something like "read_user_users__user_id__get")
@app.get("/users/{user_id}")
async def read_user(user_id: int):
return {"user_id": user_id}

# Explicit operation_id (tool will be named "get_user_info")
@app.get("/users/{user_id}", operation_id="get_user_info")
async def read_user(user_id: int):
return {"user_id": user_id}
```

For clearer, more intuitive tool names, we recommend adding explicit `operation_id` parameters to your FastAPI route definitions.

To find out more, read FastAPI's official docs about [advanced config of path operations.](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/)

## Advanced Usage

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

### Customizing Schema Description

```python
from fastapi import FastAPI
from fastapi_mcp import add_mcp_server
from fastapi_mcp import FastApiMCP

app = FastAPI()

mcp_server = add_mcp_server(
app, # Your FastAPI app
mount_path="/mcp", # Where to mount the MCP server
name="My API MCP", # Name for the MCP server
describe_all_responses=True, # False by default. Include all possible response schemas in tool descriptions, instead of just the successful response.
describe_full_response_schema=True # False by default. Include full JSON schema in tool descriptions, instead of just an LLM-friendly response example.
mcp = FastApiMCP(
app,
name="My API MCP",
base_url="http://localhost:8000",
describe_all_responses=True, # Include all possible response schemas in tool descriptions
describe_full_response_schema=True # Include full JSON schema in tool descriptions
)

# Optionally add custom tools in addition to existing APIs.
@mcp_server.tool()
async def get_server_time() -> str:
"""Get the current server time."""
from datetime import datetime
return datetime.now().isoformat()
mcp.mount()
```

### Deploying Separately from Original FastAPI App

You are not limited to serving the MCP on the same FastAPI app from which it was created.

You can create an MCP server from one FastAPI app, and mount it to a different app:

```python
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP

# Your API app
api_app = FastAPI()
# ... define your API endpoints on api_app ...

# A separate app for the MCP server
mcp_app = FastAPI()

# Create MCP server from the API app
mcp = FastApiMCP(
api_app,
base_url="http://api-host:8001", # The URL where the API app will be running
)

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

# Now you can run both apps separately:
# uvicorn main:api_app --host api-host --port 8001
# uvicorn main:mcp_app --host mcp-host --port 8000
```

### Adding Endpoints After MCP Server Creation

If you add endpoints to your FastAPI app after creating the MCP server, you'll need to refresh the server to include them:

```python
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP

app = FastAPI()
# ... define initial endpoints ...

# Create MCP server
mcp = FastApiMCP(app)
mcp.mount()

# Add new endpoints after MCP server creation
@app.get("/new/endpoint/", operation_id="new_endpoint")
async def new_endpoint():
return {"message": "Hello, world!"}

# Refresh the MCP server to include the new endpoint
mcp.setup_server()
```

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

## Development and Contributing

**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.

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.
Thank you for considering contributing to FastAPI-MCP! We encourage the community to post Issues and Pull Requests.

Before you get started, please see [CONTRIBUTING.md](CONTRIBUTING.md).
Before you get started, please see our [Contribution Guide](CONTRIBUTING.md).

## Community

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.

## Requirements

- Python 3.10+
- Python 3.10+ (Recommended 3.12)
- uv

## License

MIT License. Copyright (c) 2024 Tadata Inc.

## About

Developed and maintained by [Tadata Inc.](https://github.com/tadata-org)
Empty file added examples/__init__.py
Empty file.
25 changes: 25 additions & 0 deletions examples/full_schema_description_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from examples.shared.apps import items
from examples.shared.setup import setup_logging

from fastapi_mcp import FastApiMCP

setup_logging()


# Add MCP server to the FastAPI app
mcp = FastApiMCP(
items.app,
name="Item API MCP",
description="MCP server for the Item API",
base_url="http://localhost:8000",
describe_full_response_schema=True, # Describe the full response JSON-schema instead of just a response example
describe_all_responses=True, # Describe all the possible responses instead of just the success (2XX) response
)

mcp.mount()


if __name__ == "__main__":
import uvicorn

uvicorn.run(items.app, host="0.0.0.0", port=8000)
28 changes: 28 additions & 0 deletions examples/mount_specific_router_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from examples.shared.apps import items
from examples.shared.setup import setup_logging

from fastapi import APIRouter
from fastapi_mcp import FastApiMCP

setup_logging()


router = APIRouter(prefix="/other/route")
items.app.include_router(router)

mcp = FastApiMCP(
items.app,
name="Item API MCP",
description="MCP server for the Item API",
base_url="http://localhost:8000",
)

# Mount the MCP server to a specific router.
# It will now only be available at `/other/route/mcp`
mcp.mount(router)


if __name__ == "__main__":
import uvicorn

uvicorn.run(items.app, host="0.0.0.0", port=8000)
35 changes: 35 additions & 0 deletions examples/reregister_tools_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from examples.shared.apps import items
from examples.shared.setup import setup_logging

from fastapi_mcp import FastApiMCP

setup_logging()


# Add MCP server to the FastAPI app
mcp = FastApiMCP(
items.app,
name="Item API MCP",
description="MCP server for the Item API",
base_url="http://localhost:8000",
)


# MCP server
mcp.mount()


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


# But if you re-run the setup, the new endpoints will now be exposed.
mcp.setup_server()


if __name__ == "__main__":
import uvicorn

uvicorn.run(items.app, host="0.0.0.0", port=8000)
44 changes: 44 additions & 0 deletions examples/separate_server_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from fastapi import FastAPI
import asyncio
import uvicorn

from examples.shared.apps import items
from examples.shared.setup import setup_logging

from fastapi_mcp import FastApiMCP

setup_logging()


MCP_SERVER_HOST = "localhost"
MCP_SERVER_PORT = 8000
ITEMS_API_HOST = "localhost"
ITEMS_API_PORT = 8001


# Take the FastAPI app only as a source for MCP server generation
mcp = FastApiMCP(
items.app,
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
)

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


def run_items_app():
uvicorn.run(items.app, port=ITEMS_API_PORT)


def run_mcp_app():
uvicorn.run(mcp_app, port=MCP_SERVER_PORT)


# The MCP server depends on the Items API to be available, so we need to run both.
async def main():
await asyncio.gather(asyncio.to_thread(run_items_app), asyncio.to_thread(run_mcp_app))


if __name__ == "__main__":
asyncio.run(main())
Empty file added examples/shared/__init__.py
Empty file.
Empty file.
Loading