Uh oh!
There was an error while loading. Please reload this page.
Fix SSE client handling of nested path URLs - #386
Conversation
Fix issue with SSE client failing when connecting to servers with nested paths (e.g., http://domain/path1/path2/sse). Previously, the client worked only with simple URLs like http://domain/sse. The fix extracts the base path segment between domain and /sse endpoint using regex, then correctly constructs the response URL by preserving the path structure. This ensures proper communication with MCP servers hosted under non-root paths. Without this fix, clients couldn't connect to SSE servers deployed under nested paths, as the endpoint URLs for posting messages were constructed incorrectly.
Thank you very much @danilpavlov!!! You saved my day with your PR. I hope it will be merged soon. |
jonathanglima
commented
Apr 7, 2025
Nice! Just wanted to add that using this alongside langchain's adapters that use the
Please note that I also had to change the urljoin |
jonathanglima
commented
Apr 7, 2025
Please see danilpavlov#1@danilpavlov |
fixing /mcp case prefix on langchain
danilpavlov
commented
Apr 8, 2025
Thank you @jonathanglima so much for that feature! |
danilpavlov
commented
Apr 8, 2025
@dsp-ant Can you please check this PR. Forget to mark as reviewer :/ |
sterankin
commented
Apr 8, 2025
Be good to get this merged and released |
sebin1213
commented
Apr 9, 2025
Thanks a lot for the great PR! I have a suggestion regarding the current implementation. Right now, the SSE client dynamically extracts the base path using regex to reconstruct the endpoint URL. While this works, it places too much responsibility on the client to figure out the correct path structure. This can lead to edge cases and inconsistent behavior—especially in environments with path-based routing like Kubernetes. A more reliable and maintainable approach would be to have the server explicitly provide the full and correct session_id=uuid4()
# === mount_prefix ===request_path=scope["path"] # e.g., "/test/sse"match=re.match(r"^/([^/]+(?:/mcp)?)/sse$", request_path)
mount_prefix=match.group(1) ifmatchelse""session_uri=f"/{quote(mount_prefix)}{quote(self._endpoint)}?session_id={session_id.hex}"If the server constructs the
|
jonathanglima
commented
Apr 9, 2025
That's nice @sebin1213 . I wasn't aware that the server could actually provide the session uri to the client. Responsibility-wise, that's much better |
Fix generate accurate session_uri for nested SSE paths
danilpavlov
commented
Apr 9, 2025
Yeah, @sebin1213 that's a really good point -- the server seem to be a better place for that feature. Great job! Thank you for joining us |
Hi @danilpavlov
Can you please suggest? cc: @dsp-ant |
pritam-dey3
commented
Apr 16, 2025
I just ran into this. When can we expect this to be merged? The current workarounds are really jarring, and this small fix can save a lot of work. Thanks for all the work. |
tim-watcha
commented
Apr 19, 2025
I wanted to share some thoughts on PR #540 vs your approach: Your regex solution is clever, but runs pattern matching on every request (performance overhead) and may struggle with certain path patterns. PR #540's advantages:
Both approaches solve the core issue, but #540 may offer better performance and reliability in complex deployments. Appreciate your work on this! |
xorcus
commented
Apr 23, 2025
I just hit the same problem. Could you please consider allowing for the defsse_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: defsse_app(self) ->Starlette:
"""Return an instance of the SSE server app."""sse=SseServerTransport(self.settings.message_path)
asyncdefhandle_sse(request: Request) ->None:
asyncwithsse.connect_sse(
request.scope,
request.receive,
request._send, # type: ignore[reportPrivateUsage]
) asstreams:
awaitself._mcp_server.run(
streams[0],
streams[1],
self._mcp_server.create_initialization_options(),
)
returnStarlette(
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. |
ihrpr
commented
May 7, 2025
Thanks @danilpavlov for this PR! We're closing this in favor of PR #540, which provides a solution using server-side mount_path configuration. Appreciate your contribution! 🙏 |
For me, this works for sse and stramable... but mount path with ".."? |
Fix SSE client failing when connecting to servers with nested paths (e.g., http://domain/path1/path2/sse). Previously, the client worked only with simple URLs like http://domain/sse.
The fix extracts the base path segment between domain and /sse endpoint using regex, then correctly constructs the response URL by preserving the path structure. This ensures proper communication with MCP servers hosted under non-root paths.
Without this fix, clients couldn't connect to SSE servers deployed under nested paths, as the endpoint URLs for posting messages were constructed incorrectly.
Motivation and Context
I got problem while deploying my MCP server inside a Kubernetes cluster. The SSE client was only working with simple URLs like http://example-domain/sse, but failed to properly handle more complex paths such as http://example-domain/path1/path2/sse.
When the server was deployed under nested paths, the client couldn't establish a proper connection because it was incorrectly constructing the endpoint URLs for posting messages. This made it impossible to use MCP servers hosted under non-root paths, which is a common scenario in Kubernetes environments where services often run behind path-based routing.
How Has This Been Tested?
I ve tested on version for local mcp server (http://localhost:8000/sse) and with special mask (http://domain/dummy/sse)
Types of changes
Checklist