Skip to content

Conversation

@manuelibar
Copy link
Contributor

Description

This change decouples client transports from the concrete net/http.Client by accepting any HTTP client that implements Do(http.Request) (http.Response, error). This enables middleware (auth, tracing, retries), improves testability, and unifies behavior across streamable and SSE transports.

What changed

  • Introduce HTTPClient interface in the mcp package.
  • StreamableClientTransport: change HTTPClient field type to the interface; default to http.DefaultClient when nil.
  • SSEClientTransport: same change as streamable.

Why

  • Flexibility to inject custom HTTP clients with middleware.
  • Better testability via interface-based dependencies.
  • Consistency across transports (streamable and SSE).

Compatibility

  • Backward compatible: existing code passing http.Client continues to work (it satisfies the interface).
  • No behavior changes; defaults remain the same when HTTPClient is nil.

Testing

  • go test ./... passes locally.
  • Existing transport tests validate streamable/SSE flows.

Fixes #522

Context propagation was broken in StreamableClientTransport because:
1. Connect() used context.Background() instead of the parent context
2. Close() created a race condition where DELETE requests were cancelled

This change fixes both issues by:
- Using the parent context when creating the connection context
- Reordering Close() to perform cleanup DELETE before cancelling context

This ensures request-scoped values (auth tokens, trace IDs) propagate
correctly to background HTTP operations.

Fixes modelcontextprotocol#513
Adds TestStreamableClientContextPropagation to verify that context values
are properly propagated to background HTTP operations (SSE GET and cleanup
DELETE requests) in StreamableClientTransport.

The test uses a custom HTTP handler to capture request contexts and verify
that context values from the parent context are accessible in both the
SSE connection establishment and session cleanup requests.
The test now properly verifies that StreamableClientTransport can handle
contexts with values without errors, demonstrating that the context
propagation fix is working correctly.

The test validates the fix at streamable.go:1021 where context.WithCancel(ctx)
is used instead of context.WithCancel(context.Background()).
…nsport

Replace bloated context propagation test with clean, idiomatic Go code that:
- Follows existing test patterns in the codebase
- Removes unnecessary comments and complex synchronization
- Tests actual context derivation (cancellable contexts)
- Demonstrates the fix works without HTTP value propagation complexity

The test verifies that background HTTP operations (SSE GET and DELETE cleanup)
use properly derived contexts from the original Connect() context, confirming
the fix in streamable.go where context.WithCancel(ctx) replaced
context.WithCancel(context.Background()).
Replace unsafe global testAuth boolean with atomic.Bool to prevent
data race between test setup/teardown and background HTTP operations
in StreamableClientTransport.
Add test to verify that context values and cancellation are properly
propagated through StreamableClientTransport.Connect().

The test ensures:
- Context values are preserved in the connection context
- Connection context remains cancellable
- Parent context cancellation propagates to connection context

This addresses the context propagation requirements for the streamable
transport implementation.
…centralize interface; docs: update protocol notes
@manuelibar manuelibar marked this pull request as draft September 24, 2025 16:48
@manuelibar
Copy link
Contributor Author

Wrong branch! :(

@manuelibar manuelibar closed this Sep 24, 2025
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.

Decouple client transports from *http.Client by accepting an interface

1 participant