Skip to content

Resolved MCP client is not working with SSE transport#208

Open
bettercallsaulj wants to merge 7 commits intofix/improve_for_mcp_oauthfrom
fix/mcp-client-sse
Open

Resolved MCP client is not working with SSE transport#208
bettercallsaulj wants to merge 7 commits intofix/improve_for_mcp_oauthfrom
fix/mcp-client-sse

Conversation

@bettercallsaulj
Copy link
Copy Markdown
Collaborator

This PR must be merged after #205

RahulHere added 7 commits March 28, 2026 17:47
…nt (#207)

Bug: connect() returned success immediately after initiating TCP connect
(which returns EINPROGRESS). When initialize() was called shortly after,
connected_=false triggered reconnection logic, closing the in-progress
connection before it could complete.

Root Cause: The connect() method fulfilled its promise as soon as
connection_manager_->connect() returned success, but that only meant the
TCP connect was initiated, not completed. The actual TCP+SSL handshake
happens asynchronously in the event loop.

Fix: Added pending_connect_promise_ member that is fulfilled by
handleConnectionEvent() when ConnectionEvent::Connected fires (after
TCP+SSL handshake completes). The connect() method now correctly blocks
until the connection is fully established.

Changes:
- Added pending_connect_promise_ and connect_promise_mutex_ members
- Modified connect() to store promise and wait for event callback
- Modified handleConnectionEvent() to fulfill promise on Connected
- Handle error cases (RemoteClose, exceptions) by fulfilling with error
- Thread-safe promise fulfillment with mutex protection
- Comprehensive unit tests for promise behavior and thread safety
Bug: closeSocket() could be called multiple times during destruction chain,
leading to:
1. Multiple raiseEvent() calls on destroyed ConnectionImpl
2. Pure virtual function calls
3. Use-after-free crashes

Root Cause: When ConnectionImpl is destroyed, the destructor chain can
trigger multiple closeSocket() calls on the transport socket. Each call
would invoke raiseEvent() on the callbacks_ pointer, which points to the
already-destroyed ConnectionImpl object.

Fix: Added guard at the beginning of closeSocket() implementations to
check if already in Closed state. If so, return immediately without
processing. This prevents:
- Double raiseEvent() calls
- Timer cancellation on destroyed timers
- State transitions from invalid states
…te (#207)

Bug: After SSL handshake completed successfully, the handshake_retry_timer
continued to fire, calling performHandshakeStep() on an already-connected
socket. This caused the state machine to transition from Connected to Error,
triggering a RemoteClose event and closing the working connection.

Root Cause: onHandshakeComplete() only cancelled handshake_timer_ but not
handshake_retry_timer_. The retry timer was enabled during handshake with
exponential backoff to poll for handshake progress. After handshake
completion, it continued firing every interval.

Fix: Added handshake_retry_timer_->disableTimer() call in
onHandshakeComplete(), matching the existing handshake_timer_ cancellation.
Both timers are now properly cleaned up when handshake succeeds.
…shakes (#207)

Bug: SSL handshakes frequently stalled or timed out, especially with remote
servers. The handshake would initiate but never complete, with the client
waiting indefinitely for handshake data that had already arrived.

Root Cause: Three interconnected issues prevented handshake data from being
properly processed:

1. doRead() returned stop() during handshake without actually reading data
   from the socket. When read events fired, the handshake data (ServerHello,
   Certificate, etc.) was never fed to the SSL BIO, causing the state machine
   to stall.

2. moveToBio() only read once from the socket. Large TLS records (up to 16KB)
   might not be returned in a single read(), causing incomplete handshake
   messages and stalls.

3. After sending handshake data, no readable event was signaled. For
   level-triggered events, data that arrived during callback processing
   wouldn't trigger a new read event.
   - Handles pre-handshake states correctly (return stop, not close)
   - Fast path for connected state
…ects (#207)

Bug: Connections would report as established before TCP handshake completed,
causing send() operations to fail with ENOTCONN error. This race condition
occurred primarily with local connections where the fallback timer fired
before the actual write event.

Root Cause: A fallback timer checked SO_ERROR to detect connection completion.
However, SO_ERROR == 0 does NOT mean "connected" - it means "no error yet".
A socket still in EINPROGRESS state (TCP handshake ongoing) will have
SO_ERROR == 0. The timer incorrectly interpreted this as connection success
and called onConnected() prematurely.

The proper approach: Wait for the Write event from libevent/kqueue, which
fires when:
1. Connection succeeds (socket becomes writable)
2. Connection fails (error is signaled)

Only THEN check SO_ERROR. At that point, 0 means success, non-zero means
failure.
Added a complete, production-ready example demonstrating how to use the
MCP client with HTTP+SSE (Server-Sent Events) transport. This example
serves as both documentation and a testing tool for the HTTP/SSE fixes.

Features:
- Full HTTPS/SSE connection establishment with TLS
- MCP protocol initialization with capability negotiation
- Tool discovery and listing
- Tool invocation with JSON arguments
- Comprehensive error handling and logging
- Architecture diagram in code comments
- Detailed SSE communication pattern documentation

Example Usage:
  ./mcp_client_sse_example http://localhost:8080/sse
  ./mcp_client_sse_example https://mcp.example.com/v1/sse

Architecture Documented:
- Complete transport stack (TCP → SSL → HTTP → SSE → JSON-RPC → MCP)
- Filter chain configuration
- Bidirectional communication pattern
- SSE event stream handling
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.

1 participant