Repository navigation
sse_app() ignores mount prefix, resulting in 404 from client #412
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
| session_uri = f"{quote(self._endpoint)}?session_id={session_id.hex}" |
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!
Activity
JorgeRuizITCL commentedon Apr 3, 2025
This might be related with #386
richardhundt commentedon Apr 6, 2025
The clients shouldn't be patching the endpoints. The server should just give it the right endpoint during the handshake.
earonesty commentedon Apr 10, 2025
is there any workaround?
application = Starlette(
routes=[
Mount("/", app=mcp.sse_app()), # HERE !!!
]
)
can't get the routes either?
what's the standard way of linking a fastapi/starlette app with mcp? i need some custom routes
pztrick commentedon Apr 10, 2025
This is all going to change once streamable HTTP lands, but my work-around for now is to just update
/etc/hosts(MacOS/Linux) or Hosts File Editor (Windows) to add two hostnamesbackendandmcp. Then just serve MCP/SSE on only themcphostname.Something like:
MarkDunne commentedon Apr 11, 2025
There is in issue currently with message routing for non-root mounts. It seems to be ignored by
sse_app. Here is a workaround.As FastAPI is a subclass of Starlette, this works just as well for FastAPI. This is just a slight modification of the sse_app method
Other issues/PRs raising the same bug
alexellis commentedon Apr 14, 2025
I ran into this also and am trying the above. A complete example would be really helpful.
In my example the MCP server is written within an openfaas function mounted at
http://127.0.0.1:8080/function/mcp- then/sseand/messagesare sub-paths. The path of/function/mcpis stripped by the openfaas gateway before the request hits the HTTP server.I still seem to get the same errors from the client with the above workaround
In other words, the server needs to be mounted at
/to work... since all paths are stripped off before/function/mcp- but it also needs to reply back as if it were mounted at/function/mcpsince the client is unable to understand relative paths.rkondra-eightfold commentedon Apr 16, 2025
Have a potential fix. #524
anchen-007 commentedon Apr 22, 2025
I’m experiencing a very similar issue, but in my case, the URL prefix is not defined inside the application code using Mount. Instead, my MCP service is deployed behind an external reverse proxy that uses a multi-level path to distinguish between services.
For example, my SSE server is mounted at /sse, but it's exposed publicly at:
https://****-mcp-server/my/sse
In this case, /my is part of a multi-level URL path added by the reverse proxy, which forwards requests to the root of my MCP server.
When I configure the MCP client to use this kind of multi-level path, I encounter a 404 error.
rkondra-eightfold commentedon Apr 22, 2025
#540 is the latest on this
xorcus commentedon Apr 23, 2025
I just hit the same problem. Could you please consider allowing for the
base_pathto be supplied?Example modification in
python-sdk/src/mcp/server/fastmcp/server.py:The original code looks like this:
Exposing the
base_pathas suggested would allow for mounting withStarletteas follows:It is a bit redundant, but it works.
lizzzcai commentedon May 23, 2025
I have the same issue, and I think this should be resolved in client side. Open a feature request for it: #795
vikigenius commentedon Jul 1, 2025
It's still not clear what is supposed to happen from the docs
When I do this
http://localhost:8000/search/sseseems to work buthttp://localhost:8000/searchgives 404Is that intended?
maxisbey commentedon May 18, 2026
Fixed by #659 —
SseServerTransportnow reads the ASGIroot_path, so mounted prefixes are respected. The external-reverse-proxy case (where the SDK can't see the prefix) is tracked separately in #795.AI Disclaimer