Skip to content

fix(mcp-server): rebuild the SSE transport when the message endpoint changes (#8044) - #8086

Merged
Aias00 merged 2 commits into
apache:masterfrom
konlue:fix/mcp-endpoint-update-rebuild
Oct 26, 2026
Merged

Aias00 merged 2 commits into
apache:masterfrom
konlue:fix/mcp-endpoint-update-rebuild

Conversation

@konlue

@konlue konlue commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

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

  • handlerSelector extends 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

  • Handle-only selector update rebuilds the transport with the new endpoint and replays registered tools. Fails on the previous code (removeMcpServer never called).
  • Unchanged-endpoint updates do not rebuild.

Aias00
Aias00 previously approved these changes Oct 24, 2026

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Worth a RELEASE-NOTES.md line: changing messageEndpoint on 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.
  2. testHandlerSelectorUnchangedEndpointDoesNotRebuildServer asserts never()).getOrCreateMcpServerTransport("/endpoint", "/rpc") — exact arguments. The meaningful assertion is the never()).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())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@konlue

konlue commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Adopted the gap you pointed out.

Instead of gating the teardown on hasMcpServer(previousServer.getPath()), the handler now always tears down the previous path on pathMoved / endpointMoved — removeMcpServer is idempotent for an unknown path, and serverCreated is recomputed after the real removal, so a server that escaped an earlier teardown can no longer keep advertising the stale endpoint.

  • Regression test: Covered by testEndpointUpdateRebuildsEvenWhenTeardownFindsNoServer (red on the old code).
  • Mock updates: Four existing tests' mock sequencing was updated to match the single hasMcpServer call (including the one added by fix(mcp-server): revoke MCP tool registrations by their applied identity #8032).
  • Docs & Rebase: Added the RELEASE-NOTES entry under Behavior Changes (rebased onto the latest master for it).

Module 220/220, checkstyle clean.

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Aias00
Aias00 merged commit 58d74a5 into apache:master Oct 26, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Updating an MCP selector messageEndpoint leaves the SSE transport on the previous endpoint

2 participants