Skip to content

Feature add support for fetching OpenAPI schema from a remote FastAPI server #141

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ mcp.mount()

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

## Remote OpenAPI Schema

You can configure FastApiMCP to fetch the OpenAPI schema from a remote FastAPI server by providing a custom httpx.AsyncClient and setting `fetch_openapi_from_remote=True`:

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

REMOTE_API_URL = "http://127.0.0.1:5000"
app = FastAPI()
client = httpx.AsyncClient(base_url=REMOTE_API_URL)

mcp = FastApiMCP(
app,
name="MCP for Remote API",
http_client=client,
fetch_openapi_from_remote=True,
)
mcp.mount()
```

If `fetch_openapi_from_remote` is set and a custom client is provided, FastApiMCP will retrieve the OpenAPI schema from the remote server's `/openapi.json` endpoint instead of generating it locally.

## Documentation, Examples and Advanced Usage

FastAPI-MCP provides [comprehensive documentation](https://fastapi-mcp.tadata.com/). Additionaly, check out the [examples directory](examples) for code samples demonstrating these features in action.
Expand Down
38 changes: 31 additions & 7 deletions fastapi_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ def __init__(
"""
),
] = None,
fetch_openapi_from_remote: Annotated[
bool,
Doc(
"""
If True and http_client is provided, fetch OpenAPI schema from remote /openapi.json instead of generating locally.
"""
),
] = False,
include_operations: Annotated[
Optional[List[str]],
Doc("List of operation IDs to include as MCP tools. Cannot be used with exclude_operations."),
Expand Down Expand Up @@ -137,6 +145,7 @@ def __init__(
self._include_tags = include_tags
self._exclude_tags = exclude_tags
self._auth_config = auth_config
self.fetch_openapi_from_remote = fetch_openapi_from_remote

if self._auth_config:
self._auth_config = self._auth_config.model_validate(self._auth_config)
Expand All @@ -150,13 +159,28 @@ def __init__(
self.setup_server()

def setup_server(self) -> None:
openapi_schema = get_openapi(
title=self.fastapi.title,
version=self.fastapi.version,
openapi_version=self.fastapi.openapi_version,
description=self.fastapi.description,
routes=self.fastapi.routes,
)
import asyncio

openapi_schema = None
if self.fetch_openapi_from_remote and self._http_client:

async def fetch_openapi():
resp = await self._http_client.get("/openapi.json")
resp.raise_for_status()
return resp.json()

try:
openapi_schema = asyncio.get_event_loop().run_until_complete(fetch_openapi())
except Exception as e:
logger.error(f"Failed to fetch remote OpenAPI schema: {e}. Falling back to local generation.")
if openapi_schema is None:
openapi_schema = get_openapi(
title=self.fastapi.title,
version=self.fastapi.version,
openapi_version=self.fastapi.openapi_version,
description=self.fastapi.description,
routes=self.fastapi.routes,
)

all_tools, self.operation_map = convert_openapi_to_mcp_tools(
openapi_schema,
Expand Down
28 changes: 28 additions & 0 deletions tests/test_remote_openapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import pytest
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP


@pytest.mark.asyncio
def test_fetch_openapi_from_remote(monkeypatch):
app = FastAPI()
remote_openapi = {"openapi": "3.0.0", "info": {"title": "Remote", "version": "1.0.0"}, "paths": {}}

class MockAsyncClient:
async def get(self, url):
class Resp:
def raise_for_status(self):
pass

def json(self):
return remote_openapi

return Resp()

client = MockAsyncClient()
mcp = FastApiMCP(app, http_client=client, fetch_openapi_from_remote=True)
# The openapi schema should be fetched from remote
assert mcp.tools is not None
assert mcp.operation_map is not None
# The schema should match what the mock returned
assert mcp.operation_map == {} # since paths is empty