-
Notifications
You must be signed in to change notification settings - Fork 1.2k
sse_app() ignores mount prefix, resulting in 404 from client #412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This might be related with #386 |
The clients shouldn't be patching the endpoints. The server should just give it the right endpoint during the handshake. |
is there any workaround? application = Starlette( can't get the routes either? what's the standard way of linking a fastapi/starlette app with mcp? i need some custom routes |
This is all going to change once streamable HTTP lands, but my work-around for now is to just update Something like:
|
There is in issue currently with message routing for non-root mounts. It seems to be ignored by 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 def register_mcp_router(
starlette_app: Starlette,
mcp_server: FastMCP,
base_path: str,
):
sse = SseServerTransport(f"{base_path}/messages/")
async def handle_sse(request: Request) -> None:
async with sse.connect_sse(
request.scope,
request.receive,
request._send, # noqa: SLF001
) as (read_stream, write_stream):
await mcp_server._mcp_server.run(
read_stream,
write_stream,
mcp_server._mcp_server.create_initialization_options(),
)
starlette_app.add_route(f"{base_path}/sse", handle_sse)
starlette_app.mount(f"{base_path}/messages/", sse.handle_post_message) Other issues/PRs raising the same bug |
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 I still seem to get the same errors from the client with the above workaround from fastmcp import Client
from fastmcp.client.transports import (
SSETransport
)
import os
from dotenv import load_dotenv
import asyncio
import httpx
load_dotenv()
API_KEY = os.getenv('API_KEY')
async def main():
base_url = "http://127.0.0.1:8080/function/mcp/sse"
# Connect to a server over SSE (common for web-based MCP servers)
transport = SSETransport(
f"{base_url}"
)
async with Client(transport) as client:
await client.ping()
print(await client.call_tool("list_functions"))
asyncio.run(main())
In other words, the server needs to be mounted at |
Have a potential fix. #524 |
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: 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. |
#540 is the latest on this |
I just hit the same problem. Could you please consider allowing for the def sse_app(self, base_path: str = '') -> Starlette:
"""Return an instance of the SSE server app."""
sse = SseServerTransport(f'{base_path}{self.settings.message_path}') The original code looks like this: def sse_app(self) -> Starlette:
"""Return an instance of the SSE server app."""
sse = SseServerTransport(self.settings.message_path)
async def handle_sse(request: Request) -> None:
async with sse.connect_sse(
request.scope,
request.receive,
request._send, # type: ignore[reportPrivateUsage]
) as streams:
await self._mcp_server.run(
streams[0],
streams[1],
self._mcp_server.create_initialization_options(),
)
return Starlette(
debug=self.settings.debug,
routes=[
Route(self.settings.sse_path, endpoint=handle_sse),
Mount(self.settings.message_path, app=sse.handle_post_message),
],
) Exposing the Mount('/mcp', mcp.sse_app(base_path='/mcp')), It is a bit redundant, but it works. |
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:
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/.Expected behavior
The SSE stream should return the correct full path reflecting the prefix, e.g.:
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):
Smartphone (please complete the following information):
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!
The text was updated successfully, but these errors were encountered: