Skip to content

Mounting a Streamable HTTP MCP endpoint on existing FastAPI app does not workΒ #1367

@vemonet

Description

@vemonet

Initial Checks

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds confirmationNeeds confirmation that the PR is actually required or needed.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions