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>
This commit is contained in:
parent
bf69179066
commit
bed83e104b
2 changed files with 95 additions and 1 deletions
|
|
@ -1488,9 +1488,17 @@ func (ss *ServerSession) initialize(ctx context.Context, params *InitializeParam
|
|||
if params == nil {
|
||||
return nil, fmt.Errorf("%w: \"params\" must be be provided", jsonrpc2.ErrInvalidParams)
|
||||
}
|
||||
var wasInit bool
|
||||
ss.updateState(func(state *ServerSessionState) {
|
||||
state.InitializeParams = params
|
||||
wasInit = state.InitializeParams != nil
|
||||
if !wasInit {
|
||||
state.InitializeParams = params
|
||||
}
|
||||
})
|
||||
if wasInit {
|
||||
ss.server.opts.Logger.Error("duplicate initialize request")
|
||||
return nil, fmt.Errorf("duplicate %q received", methodInitialize)
|
||||
}
|
||||
|
||||
s := ss.server
|
||||
return &InitializeResult{
|
||||
|
|
|
|||
|
|
@ -825,6 +825,92 @@ func TestClientRootCapabilities(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestServerRejectsDuplicateInitialize(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
server := NewServer(&Implementation{Name: "testServer", Version: "v1.0.0"}, nil)
|
||||
cTransport, sTransport := NewInMemoryTransports()
|
||||
ss, err := server.Connect(ctx, sTransport, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ss.Close()
|
||||
|
||||
cConn, err := cTransport.Connect(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cConn.Close()
|
||||
|
||||
firstParams := json.RawMessage(`{
|
||||
"protocolVersion": "2025-11-25",
|
||||
"clientInfo": {"name": "first-client", "version": "1.0.0"}
|
||||
}`)
|
||||
firstReq, err := jsonrpc2.NewCall(jsonrpc2.Int64ID(1), methodInitialize, firstParams)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cConn.Write(ctx, firstReq); err != nil {
|
||||
t.Fatalf("first initialize write failed: %v", err)
|
||||
}
|
||||
msg, err := cConn.Read(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("first initialize read failed: %v", err)
|
||||
}
|
||||
resp, ok := msg.(*jsonrpc2.Response)
|
||||
if !ok {
|
||||
t.Fatalf("expected Response, got %T", msg)
|
||||
}
|
||||
if resp.Error != nil {
|
||||
t.Fatalf("first initialize failed: %v", resp.Error)
|
||||
}
|
||||
|
||||
initializedReq, err := jsonrpc2.NewNotification(notificationInitialized, &InitializedParams{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cConn.Write(ctx, initializedReq); err != nil {
|
||||
t.Fatalf("initialized notification write failed: %v", err)
|
||||
}
|
||||
|
||||
secondParams := json.RawMessage(`{
|
||||
"protocolVersion": "2024-11-05",
|
||||
"clientInfo": {"name": "second-client", "version": "2.0.0"}
|
||||
}`)
|
||||
secondReq, err := jsonrpc2.NewCall(jsonrpc2.Int64ID(2), methodInitialize, secondParams)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cConn.Write(ctx, secondReq); err != nil {
|
||||
t.Fatalf("second initialize write failed: %v", err)
|
||||
}
|
||||
msg, err = cConn.Read(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("second initialize read failed: %v", err)
|
||||
}
|
||||
resp, ok = msg.(*jsonrpc2.Response)
|
||||
if !ok {
|
||||
t.Fatalf("expected Response, got %T", msg)
|
||||
}
|
||||
if resp.Error == nil {
|
||||
t.Fatal("second initialize unexpectedly succeeded")
|
||||
}
|
||||
if !strings.Contains(resp.Error.Error(), `duplicate "initialize" received`) {
|
||||
t.Fatalf("second initialize error = %v, want duplicate initialize", resp.Error)
|
||||
}
|
||||
|
||||
got := ss.InitializeParams()
|
||||
if got == nil {
|
||||
t.Fatal("InitializeParams is nil")
|
||||
}
|
||||
if got.ProtocolVersion != "2025-11-25" {
|
||||
t.Fatalf("ProtocolVersion = %q, want first initialize value", got.ProtocolVersion)
|
||||
}
|
||||
if got.ClientInfo == nil || got.ClientInfo.Name != "first-client" {
|
||||
t.Fatalf("ClientInfo = %#v, want first initialize value", got.ClientInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move this to tool_test.go
|
||||
func TestToolForSchemas(t *testing.T) {
|
||||
// Validate that toolForErr handles schemas properly.
|
||||
|
|
|
|||
Loading…
Reference in a new issue