Skip to content

Commit bed83e1

Browse files
fix: reject duplicate initialize requests (#962)
## Summary - reject a second `initialize` request on an already initialized server session - keep the original `ServerSession.InitializeParams()` instead of replacing it with later client parameters - add a raw JSON-RPC regression test for the duplicate-initialize path Fixes #961. ## To verify - `go test ./mcp -run TestServerRejectsDuplicateInitialize -count=1` - `go test ./mcp -count=1` - `go test ./...` --------- Co-authored-by: guglielmoc <guglielmoc@google.com>
1 parent bf69179 commit bed83e1

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

mcp/server.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1488,9 +1488,17 @@ func (ss *ServerSession) initialize(ctx context.Context, params *InitializeParam
14881488
if params == nil {
14891489
return nil, fmt.Errorf("%w: \"params\" must be be provided", jsonrpc2.ErrInvalidParams)
14901490
}
1491+
var wasInit bool
14911492
ss.updateState(func(state *ServerSessionState) {
1492-
state.InitializeParams = params
1493+
wasInit = state.InitializeParams != nil
1494+
if !wasInit {
1495+
state.InitializeParams = params
1496+
}
14931497
})
1498+
if wasInit {
1499+
ss.server.opts.Logger.Error("duplicate initialize request")
1500+
return nil, fmt.Errorf("duplicate %q received", methodInitialize)
1501+
}
14941502

14951503
s := ss.server
14961504
return &InitializeResult{

mcp/server_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,92 @@ func TestClientRootCapabilities(t *testing.T) {
825825
}
826826
}
827827

828+
func TestServerRejectsDuplicateInitialize(t *testing.T) {
829+
ctx := context.Background()
830+
831+
server := NewServer(&Implementation{Name: "testServer", Version: "v1.0.0"}, nil)
832+
cTransport, sTransport := NewInMemoryTransports()
833+
ss, err := server.Connect(ctx, sTransport, nil)
834+
if err != nil {
835+
t.Fatal(err)
836+
}
837+
defer ss.Close()
838+
839+
cConn, err := cTransport.Connect(ctx)
840+
if err != nil {
841+
t.Fatal(err)
842+
}
843+
defer cConn.Close()
844+
845+
firstParams := json.RawMessage(`{
846+
"protocolVersion": "2025-11-25",
847+
"clientInfo": {"name": "first-client", "version": "1.0.0"}
848+
}`)
849+
firstReq, err := jsonrpc2.NewCall(jsonrpc2.Int64ID(1), methodInitialize, firstParams)
850+
if err != nil {
851+
t.Fatal(err)
852+
}
853+
if err := cConn.Write(ctx, firstReq); err != nil {
854+
t.Fatalf("first initialize write failed: %v", err)
855+
}
856+
msg, err := cConn.Read(ctx)
857+
if err != nil {
858+
t.Fatalf("first initialize read failed: %v", err)
859+
}
860+
resp, ok := msg.(*jsonrpc2.Response)
861+
if !ok {
862+
t.Fatalf("expected Response, got %T", msg)
863+
}
864+
if resp.Error != nil {
865+
t.Fatalf("first initialize failed: %v", resp.Error)
866+
}
867+
868+
initializedReq, err := jsonrpc2.NewNotification(notificationInitialized, &InitializedParams{})
869+
if err != nil {
870+
t.Fatal(err)
871+
}
872+
if err := cConn.Write(ctx, initializedReq); err != nil {
873+
t.Fatalf("initialized notification write failed: %v", err)
874+
}
875+
876+
secondParams := json.RawMessage(`{
877+
"protocolVersion": "2024-11-05",
878+
"clientInfo": {"name": "second-client", "version": "2.0.0"}
879+
}`)
880+
secondReq, err := jsonrpc2.NewCall(jsonrpc2.Int64ID(2), methodInitialize, secondParams)
881+
if err != nil {
882+
t.Fatal(err)
883+
}
884+
if err := cConn.Write(ctx, secondReq); err != nil {
885+
t.Fatalf("second initialize write failed: %v", err)
886+
}
887+
msg, err = cConn.Read(ctx)
888+
if err != nil {
889+
t.Fatalf("second initialize read failed: %v", err)
890+
}
891+
resp, ok = msg.(*jsonrpc2.Response)
892+
if !ok {
893+
t.Fatalf("expected Response, got %T", msg)
894+
}
895+
if resp.Error == nil {
896+
t.Fatal("second initialize unexpectedly succeeded")
897+
}
898+
if !strings.Contains(resp.Error.Error(), `duplicate "initialize" received`) {
899+
t.Fatalf("second initialize error = %v, want duplicate initialize", resp.Error)
900+
}
901+
902+
got := ss.InitializeParams()
903+
if got == nil {
904+
t.Fatal("InitializeParams is nil")
905+
}
906+
if got.ProtocolVersion != "2025-11-25" {
907+
t.Fatalf("ProtocolVersion = %q, want first initialize value", got.ProtocolVersion)
908+
}
909+
if got.ClientInfo == nil || got.ClientInfo.Name != "first-client" {
910+
t.Fatalf("ClientInfo = %#v, want first initialize value", got.ClientInfo)
911+
}
912+
}
913+
828914
// TODO: move this to tool_test.go
829915
func TestToolForSchemas(t *testing.T) {
830916
// Validate that toolForErr handles schemas properly.

0 commit comments

Comments
 (0)