Skip to content

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

@manuelibar

Description

@manuelibar

Problem

Currently, the StreamableClientTransport and SSEClientTransport requires a concrete *http.Client, which restricts its integration into systems with established HTTP client abstractions. This tight coupling forces users to implement workarounds when integrating the MCP client into environments with standardized middleware for concerns like authentication, tracing, and metrics.

As seen in issues #367 and #373, users are already creating custom RoundTripper implementations to inject headers and other middleware. This becomes particularly cumbersome in enterprise settings where HTTP clients are expected to handle numerous cross-cutting concerns, such as:

  • Authentication: OAuth2, API keys, mTLS, and other custom schemes.
  • Observability: Distributed tracing (OpenTelemetry), metrics collection (Prometheus), and structured logging.
  • Resilience: Circuit breakers, retry logic, and custom timeout policies.
  • Security & Compliance: Request/response auditing and rate limiting.

Forcing the use of a concrete *http.Client makes it difficult to reuse existing, instrumented clients, leading to duplicated logic or complex wrapper patterns around the MCP client.

Solution

To enhance flexibility and composability, StreamableClientTransport should accept an interface that satisfies the Do method, rather than a concrete *http.Client.

A standard HTTPClient interface could be defined as:

type HTTPClient interface {
    Do(req *http.Request) (*http.Response, error)
}

This change would be fully backward-compatible, as *http.Client already implements this method signature. Users could continue to initialize the transport as before:

// Existing usage remains unchanged and valid
&mcp.StreamableClientTransport{
    Endpoint:   endpoint,
    HTTPClient: &http.Client{},
}

However, it would also empower users to inject their own custom or instrumented HTTP client implementations:

// Enables integration with custom, standardized clients
&mcp.StreamableClientTransport{
    Endpoint:   endpoint,
    HTTPClient: myOrg.NewInstrumentedHTTPClient(), // Custom implementation
}

This approach aligns with Go's design principle of "accept interfaces, return structs," promoting loose coupling and greater flexibility.

Alternatives

  1. Custom http.RoundTripper: While this is the current workaround (as demonstrated in Add client examples for streamable http #367), it is suboptimal. Middleware implemented at the transport layer is less flexible and harder to compose than middleware applied to a client abstraction.
  2. Wrapper Pattern: Creating a wrapper around the entire MCP client introduces unnecessary complexity, adds a layer of indirection, and may have performance implications.

The proposed interface-based approach is cleaner and more idiomatic than these alternatives.

Additional context

Adopting an interface for the HTTP client is a standard best practice in the Go ecosystem, used by major libraries to ensure interoperability and extensibility:

  • AWS SDK for Go (v2)
  • Google Cloud Go libraries
  • OpenTelemetry instrumentation libraries
  • Popular HTTP clients like Resty and go-retryablehttp

Implementing this change would make the go-sdk more enterprise-friendly and easier to test, as it would allow users to mock the HTTPClient in their unit tests. It would also provide a more robust and idiomatic solution to the use cases described in issues #367 and #373.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions