Skip to content

[FEATURE] Expose DisableStandaloneSSE (or force HTTP/1.1) for RemoteMCPServer transports #2718

Description

@younsl

📋 Prerequisites

  • I have searched the existing issues to avoid creating a duplicate
  • By submitting this issue, you agree to follow the Code of Conduct

📝 Feature Summary

Add a disableStandaloneSSE field to RemoteMCPServer (Streamable HTTP transport) so the Go ADK MCP client can skip the standalone GET SSE stream, matching the option the go-sdk already exposes.

❓ Problem Statement / Motivation

The Go ADK MCP client opens a standalone GET SSE stream right after initialize and treats a failure on that GET as fatal for the whole session. When the stream cannot be established the client retries and then fails the session, so every agent run that binds the server errors or hangs.

This breaks any RemoteMCPServer that sits behind a proxy or load balancer that does not deliver the headers of a headers-only chunked SSE response to an HTTP/2 client. In our case the server is behind an AWS Application Load Balancer with HTTP/2 enabled. The Go client negotiates HTTP/2 over TLS, sends the GET, and never receives the response headers.

Architecture and failure path (diagram attached below):

Image

Observed on kagent v0.10.0 (go-sdk v1.6.1):

failed to connect MCP client for https://<host>/mcp: standalone SSE request failed
(session ID: mcp-session-...): connection failed after 5 attempts:
Get "https://<host>/mcp": context deadline exceeded (Client.Timeout exceeded while awaiting headers)

The tool server itself is healthy. initialize (POST), tools/list (POST) and tools/call (POST) all return 200 in well under a second through the same load balancer. Only the standalone GET SSE stream fails, because the load balancer holds the headers-only response for the HTTP/2 client until the client times out. The same GET returns 200 in 25 to 240 ms over HTTP/1.1 through the same load balancer, and returns 200 instantly when called directly inside the cluster.

Impact:

  • The affected agent falls back to lazy tool discovery and reconnects on every run, adding roughly 200 seconds per run (RemoteMCPServer timeout 30s times the go-sdk default of 5 retries).
  • Any agent that delegates to that agent then exceeds its own caller deadline.
  • The Python ADK is unaffected: its MCP client speaks HTTP/1.1 and treats a failed GET stream as non-fatal. This forces a mixed Python and Go runtime deployment as a workaround.

Anyone running a RemoteMCPServer behind an HTTP/2 aware load balancer or ingress is affected. Many managed load balancers enable HTTP/2 by default.

💡 Proposed Solution

The go-sdk StreamableClientTransport already has a DisableStandaloneSSE bool field. Its own doc notes it is useful when "you only need request-response communication and don't need server-initiated notifications" or when "the server doesn't properly handle GET requests for SSE streams". kagent constructs the transport but never sets it.

go/adk/pkg/mcp/registry.go builds the transport around line 251:

mcpTransport = &mcpsdk.StreamableClientTransport{
    Endpoint:   params.URL,
    HTTPClient: httpClient,
}

Request: surface this as a per-server option.

  1. Add DisableStandaloneSSE (as a pointer to bool) to RemoteMCPServerSpec in go/api/v1alpha2/remotemcpserver_types.go, next to the existing transport knobs (timeout, sseReadTimeout, terminateOnClose).
  2. Thread it through mcpServerParams in registry.go and set StreamableClientTransport.DisableStandaloneSSE from it.
  3. Default false, so behavior is unchanged unless a user opts in.

Tools that only need request and response (most read-only tool servers, including the tool server shipped with kagent) do not rely on server-initiated notifications such as ToolListChangedNotification, so disabling the standalone stream is safe for them.

🔄 Alternatives Considered

  • Force HTTP/1.1 on the MCP client transport, for example by setting TLSNextProto to a non-nil empty map on the underlying http.Transport in registry.go. This also resolves the case above but changes transport behavior for all Streamable HTTP servers and gives operators no per-server control. Could be offered as a separate forceHTTP1 option.
  • Disable HTTP/2 on the load balancer. Not always possible: the load balancer is often shared by many hosts and managed outside the kagent user's control, so turning off HTTP/2 affects unrelated services.
  • Keep the affected agent on the Python runtime. Works, but forces a permanently mixed-runtime deployment and blocks standardizing on the Go ADK runtime.

🎯 Affected Service(s)

App Service

📚 Additional Context

  • kagent v0.10.0, go-sdk v1.6.1
  • RemoteMCPServer protocol STREAMABLE_HTTP, timeout 30s
  • Server is read-only and does not emit ToolListChangedNotification, so it does not depend on the standalone SSE stream

🙋 Are you willing to contribute?

  • I am willing to submit a PR for this feature

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions