Python MCP SDK sse_app() ignores Mount prefix: client 404 on /messages/
When I mount FastMCP's sse_app() under a prefix with Starlette Mount('/mcp', app=mcp.sse_app()) on macOS, the SSE handshake advertises data: /messages/?session_id=... instead of /mcp/messages/..., and the MCP client fails with 404 for url 'http://127.0.0.1:8000/messages/'. Is there a workaround that makes sse_app() respect the mount prefix?
Originally asked on GitHub: https://github.com/modelcontextprotocol/python-sdk/issues/412
5 answers
The root cause is that
sse_app()hardcodesself.settings.message_pathintoSseServerTransport, so when StarletteMount('/mcp', app=mcp.sse_app())is used, the SSE handshake still advertises/messages/and the client 404s onhttp://host/messages/. The issue is closed, but the thread shows no confirmed fix in a released SDK version; PR #524 and later #540 are referenced as potential fixes.Workaround the commenter says works: add a
base_pathparameter tosse_app()insrc/mcp/server/fastmcp/server.py:pythondef sse_app(self, base_path: str = '') -> Starlette: sse = SseServerTransport(f'{base_path}{self.settings.message_path}')Then mount explicitly:
Mount('/mcp', mcp.sse_app(base_path='/mcp')). The commenter notes it is redundant but works.A second proposed helper builds the routes directly on the parent Starlette/FastAPI app:
pythonsse = SseServerTransport(f"{base_path}/messages/") starlette_app.add_route(f"{base_path}/sse", handle_sse) starlette_app.mount(f"{base_path}/messages/", sse.handle_post_message)The thread does not confirm this helper was tested; one user behind an OpenFaaS gateway that strips the
/function/mcpprefix still got404 ... /messages/. A third workaround: avoid path prefixes entirely by serving MCP on its own hostname using StarletteHostroutes plus/etc/hostsentries (backend,mcp), sosse_app()lives at the root. Multi-level prefixes inserted by a reverse proxy were also reported as failing; client-side handling was requested as feature request #795.Did this work for you?What @xorcus tried on GitHub (Worked)
Patched FastMCP's sse_app() to accept base_path='' and used SseServerTransport(f'{base_path}{self.settings.message_path}'), then mounted with Mount('/mcp', mcp.sse_app(base_path='/mcp')). The commenter said it is redundant but works.
Did this work for you?What @pztrick tried on GitHub (Worked)
Served MCP SSE on a dedicated hostname instead of a path prefix: added /etc/hosts entries for 'backend' and 'mcp' and used Starlette Host routes so sse_app() is served at the root of the 'mcp' host.
Did this work for you?What @MarkDunne tried on GitHub (Outcome unclear)
Proposed a register_mcp_router(starlette_app, mcp_server, base_path) helper that creates SseServerTransport(f"{base_path}/messages/") and registers the SSE route and message mount manually. The thread does not record whether this was tested.
Did this work for you?What @alexellis tried on GitHub (Didn't work)
Applied the register_mcp_router helper inside an OpenFaaS function mounted at /function/mcp, where the gateway strips the prefix before the request reaches the app; the client still errored with 404 for http://127.0.0.1:8080/messages/.
Did this work for you?
Related questions
- MCP python-sdk simple-chatbot: 'Attempted to exit cancel scope in a different task than it was entered in'
- Why is my MCP resource template (greeting://{name}) not available in Claude Desktop?
- How to disable TLS certificate verification (verify=False) in MCP streamablehttp_client?
- anyio.ClosedResourceError in MCP StreamableHTTP message router on fast-failing requests?
- Python MCP SDK streamable HTTP: 'Error in message router' ClosedResourceError after 307 redirect