Uh oh!
There was an error while loading. Please reload this page.
fix(sse-client): consume control frames; refresh message endpoint - #448
Conversation
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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
4t145
commented
Oct 9, 2025
Sorry for late response, it looks good to me |
Uh oh!
There was an error while loading. Please reload this page.
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.