-
Notifications
You must be signed in to change notification settings - Fork 7
Open
Description
Issue Details
Problem Description
The HTTP codec filter still couldn't properly communicate with MCP servers because:
- No SSE GET request generation: The HTTP codec didn't know how to generate a
GET /sserequest with proper SSE headers (Accept: text/event-stream,Cache-Control: no-cache) - Hardcoded POST path: POST requests always went to
/rpcregardless of the message endpoint received from the server - No URL path extraction: When the server sends
endpointevent with full URL likehttp://host:8080/api/message, the codec couldn't extract just the path/api/message - SSE body chunks not forwarded: For SSE streams (which never "complete"), body data chunks weren't being forwarded to callbacks, causing messages to be lost
- State machine blocking: The codec blocked requests while receiving response body, but SSE streams have continuous body data
Root Cause
The HttpCodecFilter::onWrite() method was designed for simple request/response patterns:
- Always generated POST requests
- Used hardcoded path
/rpcand hostlocalhost - Required body data (empty buffer = early return)
- Blocked new requests while receiving response body
SSE pattern requires:
- Initial GET request with no body
- Configurable path/host
- POST to different path than GET
- Ability to send requests while SSE stream is active
Technical Flow (Before Fix)
Client Server
| |
|-- POST /rpc (wrong!) -------------->| ❌ Should be GET /sse
| |
| (even if endpoint event received) |
|-- POST /rpc ----------------------->| ❌ Should be POST /api/message
| |
Technical Flow (After Fix)
Client Server
| |
|-- GET /sse HTTP/1.1 --------------->| ✅ SSE initialization
| Accept: text/event-stream |
| Cache-Control: no-cache |
| |
|<-- event: endpoint -----------------| (handled by filter chain)
| data: http://host/api/message |
| |
|-- POST /api/message HTTP/1.1 ------>| ✅ Correct path extracted
| {"jsonrpc":"2.0",...} |
How to Reproduce
Prerequisites
- gopher-mcp client with HTTP/SSE transport
- SSE-based MCP server that sends endpoint events
Steps to Reproduce
1. Configure client for SSE endpoint:
HttpCodecFilter filter(callbacks, dispatcher, false /* client mode */);
// Before fix: These methods didn't exist or didn't work
filter.setClientEndpoint("/sse", "localhost:8080");
filter.setUseSseGet(true);2. Attempt to send initial request:
OwnedBuffer buffer;
// Empty buffer should trigger SSE GET
filter.onWrite(buffer, false);
// Before fix: buffer is empty, returns immediately
// After fix: buffer contains "GET /sse HTTP/1.1\r\n..."3. Observe the failure (before fix):
Expected: GET /sse HTTP/1.1
Accept: text/event-stream
...
Actual: (nothing - early return on empty buffer)
4. After receiving endpoint event, send POST:
filter.setMessageEndpoint("http://localhost:8080/api/message");
OwnedBuffer post_buffer;
post_buffer.add(R"({"jsonrpc":"2.0","method":"test","id":1})");
filter.onWrite(post_buffer, false);
// Before fix: POST /rpc HTTP/1.1 (hardcoded path)
// After fix: POST /api/message HTTP/1.1 (extracted from endpoint)Minimal Reproduction Test
TEST(HttpCodecFilter, SseGetAndPostRouting) {
HttpCodecFilter filter(callbacks, dispatcher, false);
filter.setClientEndpoint("/sse", "localhost:8080");
filter.setUseSseGet(true);
// Step 1: Generate SSE GET
OwnedBuffer get_buffer;
filter.onWrite(get_buffer, false);
std::string get_req = get_buffer.toString();
// Before fix: FAILS - get_req is empty
EXPECT_NE(get_req.find("GET /sse HTTP/1.1"), std::string::npos);
EXPECT_NE(get_req.find("Accept: text/event-stream"), std::string::npos);
// Step 2: Set endpoint (simulating endpoint event)
filter.setMessageEndpoint("http://localhost:8080/api/message");
// Step 3: Send POST
OwnedBuffer post_buffer;
post_buffer.add(R"({"jsonrpc":"2.0"})");
filter.onWrite(post_buffer, false);
std::string post_req = post_buffer.toString();
// Before fix: FAILS - uses /rpc instead of /api/message
EXPECT_NE(post_req.find("POST /api/message HTTP/1.1"), std::string::npos);
}Key Changes in the Fix
| Component | Change |
|---|---|
onWrite() early return |
Allow empty buffers when use_sse_get_ is true and GET not yet sent |
| State machine check | Allow requests in ReceivingResponseBody state for SSE streams |
| SSE GET generation | Generate proper GET request with SSE headers when triggered |
| POST path routing | Extract path from message_endpoint_ URL, fall back to client_path_ |
| URL path extraction | Parse http://host:port/path to extract just /path |
| Body forwarding | Call onBody() callback immediately in client mode for SSE chunks |
New Filter Methods
// Configure client endpoint for requests
void setClientEndpoint(const std::string& path, const std::string& host);
// Store POST endpoint from SSE "endpoint" event
void setMessageEndpoint(const std::string& endpoint);
bool hasMessageEndpoint() const;
const std::string& getMessageEndpoint() const;
// Enable SSE GET mode
void setUseSseGet(bool use_sse_get);
bool hasSentSseGetRequest() const;Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels