Description
Describe the bug
When mounting sse_app() from FastMCP with a URL prefix using Starlette’s Mount, the SSE stream still returns the default /messages/ endpoint without the prefix. This causes the MCP client to resolve an incorrect URL (e.g., /messages/ instead of /mcp/messages/), resulting in a 404 error.
To Reproduce
Steps to reproduce the behavior:
- Define an MCP server as below:
from mcp.server.fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount
mcp = FastMCP(
# sse_path="/sse", # default
# message_path="/messages/", # default
)
application = Starlette(
routes=[
Mount("/mcp", app=mcp.sse_app()), # HERE !!!
]
)
- Start the server and navigate to http://127.0.0.1:8000/mcp/sse in your browser
- Observe the response in the browser. You will receive an SSE event like:
event: endpoint
data: /messages/?session_id=...
At this point, the MCP client performs a urljoin
operation between the SSE URL (http://127.0.0.1:8000/mcp/sse) and the endpoint path (/messages/). This causes the resolved endpoint URL to become http://127.0.0.1:8000/messages/.
- The client tries to connect to http://127.0.0.1:8000/messages/, which results in a 404.
Expected behavior
The SSE stream should return the correct full path reflecting the prefix, e.g.:
data: /mcp/messages/?session_id=...
This would allow the client to connect to the actual valid message endpoint.
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
- OS: macOS
- Browser : Arc
- Version : 1.87.1 (60573)
Smartphone (please complete the following information):
- Device: N/A
- OS: N/A
- Browser : N/A
- Version : N/A
Additional context
This behavior seems to originate from hardcoded endpoint generation inside sse_app():
python-sdk/src/mcp/server/sse.py
Line 98 in c2ca8e0
It would be great to have support for specifying a prefix in sse_app() or for the prefix to be auto-detected from the ASGI scope.
Thank you!