Skip to content

Commit 767dacb

Browse files
mcp/streamable: remove StreamableReconnectOptions (#319)
Moves MaxRetries into the parent struct and assume a negative integer signifies to not retry. Fixes #308
1 parent eb5eb06 commit 767dacb

File tree

2 files changed

+36
-43
lines changed

2 files changed

+36
-43
lines changed

mcp/streamable.go

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -849,26 +849,15 @@ func (c *streamableServerConn) Close() error {
849849
// endpoint serving the streamable HTTP transport defined by the 2025-03-26
850850
// version of the spec.
851851
type StreamableClientTransport struct {
852-
Endpoint string
853-
HTTPClient *http.Client
854-
ReconnectOptions *StreamableReconnectOptions
855-
}
856-
857-
// StreamableReconnectOptions defines parameters for client reconnect attempts.
858-
type StreamableReconnectOptions struct {
852+
Endpoint string
853+
HTTPClient *http.Client
859854
// MaxRetries is the maximum number of times to attempt a reconnect before giving up.
860-
// A value of 0 or less means never retry.
855+
// It defaults to 5. To disable retries, use a negative number.
861856
MaxRetries int
862857
}
863858

864-
// DefaultReconnectOptions provides sensible defaults for reconnect logic.
865-
var DefaultReconnectOptions = &StreamableReconnectOptions{
866-
MaxRetries: 5,
867-
}
868-
869859
// These settings are not (yet) exposed to the user in
870-
// StreamableReconnectOptions. Since they're invisible, keep them const rather
871-
// than requiring the user to start from DefaultReconnectOptions and mutate.
860+
// StreamableClientTransport.
872861
const (
873862
// reconnectGrowFactor is the multiplicative factor by which the delay increases after each attempt.
874863
// A value of 1.0 results in a constant delay, while a value of 2.0 would double it each time.
@@ -887,8 +876,10 @@ const (
887876
type StreamableClientTransportOptions struct {
888877
// HTTPClient is the client to use for making HTTP requests. If nil,
889878
// http.DefaultClient is used.
890-
HTTPClient *http.Client
891-
ReconnectOptions *StreamableReconnectOptions
879+
HTTPClient *http.Client
880+
// MaxRetries is the maximum number of times to attempt a reconnect before giving up.
881+
// It defaults to 5. To disable retries, use a negative number.
882+
MaxRetries int
892883
}
893884

894885
// NewStreamableClientTransport returns a new client transport that connects to
@@ -901,7 +892,7 @@ func NewStreamableClientTransport(url string, opts *StreamableClientTransportOpt
901892
t := &StreamableClientTransport{Endpoint: url}
902893
if opts != nil {
903894
t.HTTPClient = opts.HTTPClient
904-
t.ReconnectOptions = opts.ReconnectOptions
895+
t.MaxRetries = opts.MaxRetries
905896
}
906897
return t
907898
}
@@ -919,34 +910,36 @@ func (t *StreamableClientTransport) Connect(ctx context.Context) (Connection, er
919910
if client == nil {
920911
client = http.DefaultClient
921912
}
922-
reconnOpts := t.ReconnectOptions
923-
if reconnOpts == nil {
924-
reconnOpts = DefaultReconnectOptions
913+
maxRetries := t.MaxRetries
914+
if maxRetries == 0 {
915+
maxRetries = 5
916+
} else if maxRetries < 0 {
917+
maxRetries = 0
925918
}
926919
// Create a new cancellable context that will manage the connection's lifecycle.
927920
// This is crucial for cleanly shutting down the background SSE listener by
928921
// cancelling its blocking network operations, which prevents hangs on exit.
929922
connCtx, cancel := context.WithCancel(context.Background())
930923
conn := &streamableClientConn{
931-
url: t.Endpoint,
932-
client: client,
933-
incoming: make(chan jsonrpc.Message, 10),
934-
done: make(chan struct{}),
935-
ReconnectOptions: reconnOpts,
936-
ctx: connCtx,
937-
cancel: cancel,
938-
failed: make(chan struct{}),
924+
url: t.Endpoint,
925+
client: client,
926+
incoming: make(chan jsonrpc.Message, 10),
927+
done: make(chan struct{}),
928+
maxRetries: maxRetries,
929+
ctx: connCtx,
930+
cancel: cancel,
931+
failed: make(chan struct{}),
939932
}
940933
return conn, nil
941934
}
942935

943936
type streamableClientConn struct {
944-
url string
945-
ReconnectOptions *StreamableReconnectOptions
946-
client *http.Client
947-
ctx context.Context
948-
cancel context.CancelFunc
949-
incoming chan jsonrpc.Message
937+
url string
938+
client *http.Client
939+
ctx context.Context
940+
cancel context.CancelFunc
941+
incoming chan jsonrpc.Message
942+
maxRetries int
950943

951944
// Guard calls to Close, as it may be called multiple times.
952945
closeOnce sync.Once
@@ -1222,7 +1215,7 @@ func (c *streamableClientConn) reconnect(lastEventID string) (*http.Response, er
12221215
attempt = 1
12231216
}
12241217

1225-
for ; attempt <= c.ReconnectOptions.MaxRetries; attempt++ {
1218+
for ; attempt <= c.maxRetries; attempt++ {
12261219
select {
12271220
case <-c.done:
12281221
return nil, fmt.Errorf("connection closed by client during reconnect")
@@ -1244,9 +1237,9 @@ func (c *streamableClientConn) reconnect(lastEventID string) (*http.Response, er
12441237
}
12451238
// If the loop completes, all retries have failed.
12461239
if finalErr != nil {
1247-
return nil, fmt.Errorf("connection failed after %d attempts: %w", c.ReconnectOptions.MaxRetries, finalErr)
1240+
return nil, fmt.Errorf("connection failed after %d attempts: %w", c.maxRetries, finalErr)
12481241
}
1249-
return nil, fmt.Errorf("connection failed after %d attempts", c.ReconnectOptions.MaxRetries)
1242+
return nil, fmt.Errorf("connection failed after %d attempts", c.maxRetries)
12501243
}
12511244

12521245
// isResumable checks if an HTTP response indicates a valid SSE stream that can be processed.

mcp/streamable_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,8 @@ func TestStreamableTransports(t *testing.T) {
193193
// outage.
194194
func TestClientReplay(t *testing.T) {
195195
for _, test := range []clientReplayTest{
196-
{"default", nil, true},
197-
{"no retries", &StreamableReconnectOptions{}, false},
196+
{"default", 0, true},
197+
{"no retries", -1, false},
198198
} {
199199
t.Run(test.name, func(t *testing.T) {
200200
testClientReplay(t, test)
@@ -204,7 +204,7 @@ func TestClientReplay(t *testing.T) {
204204

205205
type clientReplayTest struct {
206206
name string
207-
options *StreamableReconnectOptions
207+
maxRetries int
208208
wantRecovered bool
209209
}
210210

@@ -258,8 +258,8 @@ func testClientReplay(t *testing.T, test clientReplayTest) {
258258
},
259259
})
260260
clientSession, err := client.Connect(ctx, &StreamableClientTransport{
261-
Endpoint: proxy.URL,
262-
ReconnectOptions: test.options,
261+
Endpoint: proxy.URL,
262+
MaxRetries: test.maxRetries,
263263
}, nil)
264264
if err != nil {
265265
t.Fatalf("client.Connect() failed: %v", err)

0 commit comments

Comments
 (0)