-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
Hi,
We are trying to create a web server with 2 endpoints:
/mcp
: the MCP server endpoint/hello
: whatever custom endpoint getting JSON returning JSON
This is important because it allows to build more advanced app than just a MCP server, and avoid having 35 micro services to manage.
We use a really popular solution for doing so: FastAPI, which claims full support for mounting ASGI apps, such as the Starlette
object returned by mcp.streamable_http_app()
from fastapi import FastAPI
from mcp.server.fastmcp import FastMCP
app = FastAPI(title="Hello MCP")
mcp = FastMCP(
"SIB BioData MCP",
debug=True,
dependencies=["mcp"],
)
@mcp.tool()
async def just_for_test(question: str) -> str:
"""Nothing special
Args:
question: The question to be answered
Returns:
The question
"""
return question
mcp_app = mcp.streamable_http_app()
app.mount("/mcp", mcp_app)
@app.post("/hello")
async def chat():
print("hello")
Run with:
uv run uvicorn main:app --port 8000
Going directly to http://localhost:8000/mcp gives plain text Not Found
Going to any actually not defined path gives the usual FastAPI {"detail":"Not Found"}
So this shows something has been mounted on /mcp
, but yourStarlette
object returns an incorrect error (it can't be not found, maximum it could be method not allowed, or something else, but not found does not seems correct here)
Try to access it with the official MCP client using the official documentation:
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
mcp_url = "http://localhost:8000/mcp"
async with streamablehttp_client(mcp_url) as (read_stream, write_stream, _):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
tools = await session.list_tools()
print(f"Available tools: {[tool.name for tool in tools.tools]}")
if __name__ == "__main__":
asyncio.run(main())
We get a redirect and a 404 in the server logs:
INFO: 192.168.97.1:49048 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 192.168.97.1:49048 - "POST /mcp/ HTTP/1.1" 404 Not Found
So now if we change the mcp_url to http://localhost:8000/mcp/mcp we get:
File "/app/.venv/lib/python3.12/site-packages/mcp/server/streamable_http_manager.py", line 138, in handle_request
raise RuntimeError("Task group is not initialized. Make sure to use run().")
RuntimeError: Task group is not initialized. Make sure to use run().
Many problems here
- Not sure how I should use run here, since I am deploying this with uvicorn?
- On your side, having the
Starlette
already preconfigured to mount the endpoint on/mcp
does not seems like a good pattern. From our (devs using your lib) pov, mounting on/
is problematic, we should be able to easily mount the MCP endpoint on any path no? Or is there somewhere in the protocol specified that the Streamable HTTP endpoints absolutely needs to be mounted on/mcp
? Not sure exactly of your lib internals, but I should be able to deploy multiple MCP endpoints on different path on the same server
Strangely we had less issues implementing the same thing in rust with axum and the official rmcp crate. And it seems like the rmcp crate let us mount the MCP endpoint on any path, so we should be able to do the same with python
Note that deploying the exact same MCP app directly with mcp.run(transport="streamable-http")
works fine (the client scripts above works and print the tools)
Would it be possible to clarify a bit how can this be done properly? A basic working example is the usual way of documenting such feature (it's just 10 lines)
Thanks a lot
Example Code
See above for the multiple code snippets
Python & MCP Python SDK
python 3.12
mcp[cli] >=1.14.0