Skip to content

Conversation

@zxxf18
Copy link

@zxxf18 zxxf18 commented Dec 18, 2025

mcp: add DisableListening option to control standalone SSE stream

Currently, StreamableClientTransport always establishes a standalone SSE connection after initialization to receive server-initiated messages. While this is useful for receiving notifications like ToolListChangedNotification, it causes problems in several real-world scenarios:

  1. Unnecessary resource usage: Many use cases (for me), especially in scientific computing scenarios, only require simple request-response communication. Clients don't need server-initiated notifications, making the persistent SSE connection wasteful.

  2. Server compatibility issues: Some third-party MCP servers don't properly handle GET requests for SSE streams. When the client automatically tries to establish the SSE connection, it fails or hangs, blocking the entire connection process. This is particularly problematic when using third-party servers that cannot be modified. This issue is similar to what was discussed in issue streamable HTTP client: Don't block on standalone SSE Get #634.

  3. Lack of user control: The MCP specification states that the standalone SSE stream is optional ("The client MAY issue an HTTP GET"), but the current SDK implementation doesn't provide a way to opt out. This implementation is also inconsistent with other MCP SDKs like github.com/mark3labs/mcp-go, which provides getListeningEnabled control (defaults to false, requires explicit enablement).

This change adds a DisableListening boolean field to StreamableClientTransport that allows users to control whether the client establishes the standalone SSE stream. The default value is false, maintaining backward compatibility with current behavior. When set to true, the client skips establishing the standalone SSE connection.

The implementation includes DisableListening bool field in StreamableClientTransport, WithDisableListening() convenience function, conditional logic in sessionUpdated() to respect the option, and comprehensive tests in TestStreamableClientDisableListening.

Fixes #728

Copy link
Contributor

@findleyr findleyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR!


// DisableListening disables receiving server-to-client notifications when no request is in flight.
//
// By default, the client establishes a standalone long-live GET HTTP connection to the server
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long-lived

// It defaults to 5. To disable retries, use a negative number.
MaxRetries int

// DisableListening disables receiving server-to-client notifications when no request is in flight.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"disables the standalone GET request that receives server-initiated messages and notifications."

  • it's not just notifications: it can also be requests
  • it's not related to whether a request is in flight: a request could be in flight, and yet the server sends messages unassociated with that request.

// It defaults to 5. To disable retries, use a negative number.
MaxRetries int

// DisableListening disables receiving server-to-client notifications when no request is in flight.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just call this "DisableStandaloneSSE" to be clear? What do other SDKs call it, if they have such a setting?

// If true, the client will not establish the standalone SSE stream and will only receive
// responses to its own requests.
//
// Defaults to false to maintain backward compatibility with existing behavior.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's omit this last sentence: it's documenting the change (adding a new field), not the steady state.

It defaults to false because we thought the SSE stream should be established by default (which IIRC is consistent with other SDKs). I don't think we need to caveat it.

// Endpoint: "http://localhost:8080/mcp",
// }
// mcp.WithDisableListening(transport)
func WithDisableListening(transport *StreamableClientTransport) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we have this function when there's already a field on the StreamableClientTransport?

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.

Proposal: Add option to disable continuous listening in StreamableClientTransport

2 participants