Skip to content

Commit 807316b

Browse files
committed
retry/backoff logic on remote mcp server init
This is a temporary fix to handle the case where the remote server hasn't finished its async init and we send the notifications/initialized message before the server is ready. Fix upstream in mcp-go if possible. references #92 Signed-off-by: Christopher Petito <chrisjpetito@gmail.com>
1 parent 6e4c3a5 commit 807316b

2 files changed

Lines changed: 42 additions & 5 deletions

File tree

pkg/tools/mcp/client.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"fmt"
88
"log/slog"
99
"slices"
10+
"strings"
11+
"time"
1012

1113
"github.com/mark3labs/mcp-go/mcp"
1214

@@ -51,16 +53,51 @@ func (c *Client) Start(ctx context.Context) error {
5153
Version: "1.0.0",
5254
}
5355

54-
_, err := c.client.Initialize(ctx, initRequest)
55-
if err != nil {
56-
slog.Error("Failed to initialize MCP client", "error", err)
57-
return fmt.Errorf("failed to initialize MCP client: %w", err)
56+
const maxRetries = 3
57+
for attempt := 0; ; attempt++ {
58+
_, err := c.client.Initialize(ctx, initRequest)
59+
if err == nil {
60+
break
61+
}
62+
// TODO(krissetto): This is a temporary fix to handle the case where the remote server hasn't finished its async init
63+
// and we send the notifications/initialized message before the server is ready. Fix upstream in mcp-go if possible.
64+
//
65+
// Only retry when initialization fails due to sending the initialized notification.
66+
if !isInitNotificationSendError(err) {
67+
slog.Error("Failed to initialize MCP client", "error", err)
68+
return fmt.Errorf("failed to initialize MCP client: %w", err)
69+
}
70+
if attempt >= maxRetries {
71+
slog.Error("Failed to initialize MCP client after retries", "error", err)
72+
return fmt.Errorf("failed to initialize MCP client after retries: %w", err)
73+
}
74+
backoff := time.Duration(200*(attempt+1)) * time.Millisecond
75+
slog.Debug("MCP initialize failed to send initialized notification; retrying", "id", c.logId, "attempt", attempt+1, "backoff_ms", backoff.Milliseconds())
76+
select {
77+
case <-time.After(backoff):
78+
case <-ctx.Done():
79+
return fmt.Errorf("failed to initialize MCP client: %w", ctx.Err())
80+
}
5881
}
5982

6083
slog.Debug("MCP client started and initialized successfully")
6184
return nil
6285
}
6386

87+
// isInitNotificationSendError returns true if initialization failed while sending the
88+
// notifications/initialized message to the server.
89+
func isInitNotificationSendError(err error) bool {
90+
if err == nil {
91+
return false
92+
}
93+
msg := strings.ToLower(err.Error())
94+
// mcp-go client returns this error
95+
if strings.Contains(msg, "failed to send initialized notification") {
96+
return true
97+
}
98+
return false
99+
}
100+
64101
// Stop stops the MCP server
65102
func (c *Client) Stop() error {
66103
slog.Debug("Stopping MCP client")

pkg/tools/mcp/toolset.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func NewToolsetCommand(command string, args, env, toolFilter []string) *Toolset
3030

3131
// NewToolsetRemote creates a new MCP toolset from a remote MCP Server.
3232
func NewToolsetRemote(url, transport string, headers map[string]string, toolFilter []string) (*Toolset, error) {
33-
slog.Debug("Creating MCP toolset", "url", url, "transport", transport, "headers", headers, "toolFilter", toolFilter)
33+
slog.Debug("Creating Remote MCP toolset", "url", url, "transport", transport, "headers", headers, "toolFilter", toolFilter)
3434

3535
mcpc, err := NewRemoteClient(url, transport, headers)
3636
if err != nil {

0 commit comments

Comments
 (0)