fix(sse-client): consume control frames; refresh message endpoint#448
Merged
4t145 merged 2 commits intomodelcontextprotocol:mainfrom Oct 9, 2025
Merged
fix(sse-client): consume control frames; refresh message endpoint#4484t145 merged 2 commits intomodelcontextprotocol:mainfrom
4t145 merged 2 commits intomodelcontextprotocol:mainfrom
Conversation
Contributor
There was a problem hiding this comment.
Pull Request Overview
This PR improves SSE client compatibility by gracefully handling control frames and auto-refreshing message endpoints on reconnect, addressing issues with MCP servers that emit non-JSON control frames like event:endpoint.
- Add hook-based system to consume SSE control frames without parsing them as JSON-RPC messages
- Auto-refresh POST message endpoint when servers send
event:endpointcontrol frames with new session IDs - Reduce log noise by downgrading JSON decode failures to debug level with contextual information
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| crates/rmcp/src/transport/sse_client.rs | Implements endpoint refresh logic and reconnect hooks for SSE client transport |
| crates/rmcp/src/transport/common/client_side_sse.rs | Adds control frame handling and stream error hooks to SSE auto-reconnect stream |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Contributor
|
Sorry for late response, it looks good to me |
4t145
approved these changes
Oct 9, 2025
Merged
takumi-earth
pushed a commit
to earthlings-dev/rmcp
that referenced
this pull request
Jan 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gracefully handle SSE control frames; refresh endpoint on reconnect; add hooks; reduce noisy logs
event:endpoint)sessionId)Resolves repeated warnings like:
and reconnection issues with servers that emit
endpointcontrol frames (e.g., git-mcp). Improves compatibility with mixed/extended SSE implementations while preserving strict MCP parsing for actual messages.Motivation and Context
Certain MCP servers resend control information over SSE when a stream reconnects. Typical examples include
event: endpointframes whose data carries the message POST endpoint (often with a newsessionId). Previously, the client-side SSE path attempted to parse every SSEdataas a JSON-RPC message, which produced repeated warnings and interfered with reconnection and message submission.In particular, GitMCP emits
event:endpointwith a refreshed endpoint that can include asessionId. This triggered repeated decode warnings and unstable reconnection in downstream projects (e.g., MCPMate) and motivated this compatibility fix.This change clearly separates JSON message frames from control frames: control frames are consumed by a reconnect hook and never fed into the JSON parser; valid JSON message frames continue to be parsed strictly per MCP.
How Has This Been Tested?
event:endpointupdates the shared POST endpoint URI on reconnectdebugwithlast_event_idcontextBreaking Changes
None. Changes are internal. Hooks are
pub(crate)and do not alter public APIs. Default behavior for compliant servers remains identical.Types of changes
Checklist
Additional context
Implementation Details
crates/rmcp/src/transport/common/client_side_sse.rs:214–223— non-""|"message"SSE events are treated as control frames and passed tohandle_control_event(&Sse); only""|"message"are parsed as JSON.crates/rmcp/src/transport/common/client_side_sse.rs:228–239— JSON decode failures downgraded todebugand includelast_event_idfor troubleshooting.crates/rmcp/src/transport/common/client_side_sse.rs:101–124—SseStreamReconnectprovideshandle_control_event(no-op by default) andhandle_stream_error.handle_stream_errornow accepts&(dyn std::error::Error + 'static)so call sites can forward underlyingsse_stream::Errordirectly, avoiding generic constraints and fixing the E0308 type mismatch observed in integration builds.crates/rmcp/src/transport/sse_client.rs:63–105—SseClientReconnect<C>implements the hooks: consumesevent:endpoint, resolves the new message endpoint, updates a sharedArc<RwLock<Uri)>, and logs stream errors withuriandlast_event_idcontext. The override ofhandle_stream_errormatches the new signature (&(dyn Error)), see:98–104.http(s)://data → used as-is.?queryonly → keep base path, append query.path_and_queryin base.message_endpoint(base, endpoint)incrates/rmcp/src/transport/sse_client.rs:300–308.sse.idviaif let Some(ref event_id)before cloning tolast_event_id(crates/rmcp/src/transport/common/client_side_sse.rs:211–213).&(dyn Error)and forwarding the underlying SSE error at the call site (:251).Design Decisions
event == "" | "message"go through JSON parsing; others are consumed by a hook.handle_control_eventandhandle_stream_errorenable future extensions (e.g., ping, other control signals) without API changes; default implementations remain backward compatible.