From c8f02a9e34c293f428e0b0fb3a880a6624de58d8 Mon Sep 17 00:00:00 2001 From: Jairus Christensen Date: Thu, 23 Jul 2026 13:17:26 +0000 Subject: [PATCH] fix: tolerate duplicate initialize requests on the same session go-sdk v1.7.0-pre.1 (bumped in #2787) started rejecting a second "initialize" call on an already-initialized session with `duplicate "initialize" received`, fixing modelcontextprotocol/go-sdk#961 (a duplicate initialize with changed params could silently overwrite the session's stored InitializeParams). go-sdk v1.6.1 and earlier (github-mcp-server v1.5.0) accepted repeat calls and returned the same result every time. Some MCP clients resend "initialize" on the same transport/session when a handshake is retried (e.g. a slow first response triggers a client-side retry without tearing down the connection), which relied on that old idempotent behavior and now hard-fails on v1.6.0+. Add a receiving middleware that caches the first successful InitializeResult and replays it for later "initialize" calls on the same server, instead of forwarding to the SDK and hitting its error. The response is otherwise constant for the process's lifetime, so a single cached result (not a per-session cache) is enough and avoids any per-session memory growth. Co-Authored-By: Claude Sonnet 5 --- internal/ghmcp/server.go | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index f13bdc476e..c30f82eaf7 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -9,6 +9,7 @@ import ( "os" "os/signal" "strings" + "sync" "syscall" "time" @@ -190,10 +191,65 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se } ghServer.AddReceivingMiddleware(addUserAgentsMiddleware(cfg, clients.restUATransp, clients.gqlHTTP)) + ghServer.AddReceivingMiddleware(dedupeInitializeMiddleware()) return ghServer, nil } +// dedupeInitializeMiddleware makes a repeated "initialize" call on an +// already-initialized session return the original result instead of +// erroring. +// +// go-sdk v1.7.0-pre.1 (picked up by github-mcp-server v1.6.0, see go.mod) +// started rejecting a second "initialize" on the same session with +// `duplicate "initialize" received" (upstream: TestServerRejectsDuplicateInitialize +// in mcp/server_test.go, added to fix modelcontextprotocol/go-sdk#961). go-sdk +// v1.6.1 and earlier (github-mcp-server v1.5.0) silently accepted repeat +// calls, returning the same result every time. Some MCP clients resend +// "initialize" on the same transport/session when a handshake is retried +// (e.g. after a slow first response), relying on that old idempotent +// behavior. Restore it here instead of depending on unreleased upstream +// behavior, since the response for "initialize" is otherwise constant for +// the lifetime of this server. +func dedupeInitializeMiddleware() func(next mcp.MethodHandler) mcp.MethodHandler { + var ( + mu sync.Mutex + cached *mcp.InitializeResult + ) + + return func(next mcp.MethodHandler) mcp.MethodHandler { + return func(ctx context.Context, method string, request mcp.Request) (mcp.Result, error) { + if method != "initialize" { + return next(ctx, method, request) + } + + if sess, ok := request.GetSession().(*mcp.ServerSession); ok && sess.InitializeParams() != nil { + mu.Lock() + result := cached + mu.Unlock() + if result != nil { + return result, nil + } + // No cached result yet even though the session is already + // initialized: fall through and let the SDK produce its own + // error rather than guess at a response. + } + + result, err := next(ctx, method, request) + if err == nil { + if initResult, ok := result.(*mcp.InitializeResult); ok { + mu.Lock() + if cached == nil { + cached = initResult + } + mu.Unlock() + } + } + return result, err + } + } +} + type StdioServerConfig struct { // Version of the server Version string