Repository navigation
fix(mcp-server): rebuild the SSE transport when the message endpoint changes (#8044) - #8086
Conversation
Aias00
left a comment
There was a problem hiding this comment.
Approved. The diagnosis is right: ShenyuSseServerTransportProvider builds its advertised endpoint at construction, so a handle-only update that changes messageEndpoint leaves the transport telling clients to POST to the old path, and no amount of caching fixes that — the server has to be rebuilt. Detecting endpointMoved separately from pathMoved and reusing the existing teardown is the minimal correct shape, and replaying the rules onto the rebuilt server is the part that is easy to forget.
The one gap I would still close is the hasMcpServer guard on the teardown (inline): in the window where the manager does not hold an entry for the previous path, the removal is skipped and serverCreated is also false, so the transport is left advertising the old endpoint — the same bug, narrower window.
CI note: the single red check, build (shenyu-integrated-test-http), is infrastructure. That lane fails while starting Elasticsearch (curl http://localhost:9200/_cat/health?v response 000 until the job is cancelled) and is red on unrelated PRs today. A rebase/push will re-trigger it.
Non-blocking:
- Worth a
RELEASE-NOTES.mdline: changingmessageEndpointon a live selector now terminates existing SSE sessions and requires clients to reconnect. That is the intended trade-off, but operators should read it somewhere other than a code comment. testHandlerSelectorUnchangedEndpointDoesNotRebuildServerassertsnever()).getOrCreateMcpServerTransport("/endpoint", "/rpc")— exact arguments. The meaningful assertion is thenever()).removeMcpServer(anyString())next to it; the first one would not catch a rebuild that used a different pair. Not worth changing, just noting that it is the second line doing the work.
| // around the new endpoint; existing SSE sessions end and clients reconnect (#8044) | ||
| final boolean endpointMoved = Objects.nonNull(previousServer) && !pathMoved | ||
| && !Objects.equals(previousServer.getMessageEndpoint(), shenyuMcpServer.getMessageEndpoint()); | ||
| if ((pathMoved || endpointMoved) && StringUtils.isNotBlank(previousServer.getPath()) |
There was a problem hiding this comment.
endpointMoved only takes effect when the manager actually holds an entry for the previous path. If hasMcpServer(previousServer.getPath()) is false, the removal is skipped, and then serverCreated = !hasMcpServer(path) is also false for the unchanged path — so line 118 never runs, and the transport keeps advertising the old endpoint. That is the exact bug this PR fixes, just in a narrower window (for example after a removeSelector/handlerSelector interleaving, or when the entry was keyed under a path that no longer matches).
Since the intent is "the transport must be rebuilt around the new endpoint", the rebuild should not depend on the removal having succeeded. Calling getOrCreateMcpServerTransport(path, messageEndpoint) whenever endpointMoved is true (it is idempotent — it returns the existing transport when the key is present) closes the window without changing behaviour when the removal did happen.
|
Adopted the gap you pointed out. Instead of gating the teardown on
Module 220/220, checkstyle clean. |
917c6b0 to
ad93e21
Compare
Aias00
left a comment
There was a problem hiding this comment.
Approved. Rebuilding the SSE transport when the message endpoint changes (not only the path) fixes stale transports after endpoint rotation, and the teardown is idempotent. The three new tests cover the #8044 scenario. Good catch.
Fixes #8044.
Summary
Updating only the message endpoint on a selector (path unchanged) left the existing transport advertising the old endpoint forever — it is built at construction and immutable — so dispatch used the new endpoint while the advertised URL was the old one, and clients posting the advertised URL were misrouted.
Modifications
handlerSelectorextends its existing path-moved handling: when the message endpoint changes, the transport for that path is closed and rebuilt with the new endpoint. Closing the transport terminates the existing SSE sessions — clients reconnect and receive the new endpoint event; tool definitions are replayed onto the rebuilt server by the existing rules-replay path.Verifying this change
removeMcpServernever called).