From 880589a0ec1d41352ff878e704346d1085b18331 Mon Sep 17 00:00:00 2001 From: ychampion Date: Wed, 8 Jul 2026 10:18:50 +0000 Subject: [PATCH 1/2] mcp: return nil params for missing optional requests Requests with missing optional params could store a typed nil pointer in the typed request. When middleware called GetParams, that pointer was returned as a non-nil Params interface, so standard nil checks did not protect later method calls. Normalize typed nil params in GetParams for client and server requests, and cover missing optional server params plus custom params that embed ParamsBase. Fixes #1052 --- mcp/shared.go | 15 +++++++++++++-- mcp/shared_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/mcp/shared.go b/mcp/shared.go index 0fc19c5f..6c58db3b 100644 --- a/mcp/shared.go +++ b/mcp/shared.go @@ -604,8 +604,19 @@ func (*ServerRequest[P]) isRequest() {} func (r *ClientRequest[P]) GetSession() Session { return r.Session } func (r *ServerRequest[P]) GetSession() Session { return r.Session } -func (r *ClientRequest[P]) GetParams() Params { return r.Params } -func (r *ServerRequest[P]) GetParams() Params { return r.Params } +func (r *ClientRequest[P]) GetParams() Params { return requestParams(r.Params) } +func (r *ServerRequest[P]) GetParams() Params { return requestParams(r.Params) } + +func requestParams[P Params](params P) Params { + if any(params) == nil { + return nil + } + value := reflect.ValueOf(params) + if value.Kind() == reflect.Ptr && value.IsNil() { + return nil + } + return params +} func (r *ClientRequest[P]) GetExtra() *RequestExtra { return nil } func (r *ServerRequest[P]) GetExtra() *RequestExtra { return r.Extra } diff --git a/mcp/shared_test.go b/mcp/shared_test.go index 1d0ec700..37e4db9f 100644 --- a/mcp/shared_test.go +++ b/mcp/shared_test.go @@ -5,6 +5,7 @@ package mcp import ( + "context" "encoding/json" "errors" "strings" @@ -260,6 +261,47 @@ func TestServerRequest_PerRequestAccessors_Empty(t *testing.T) { } } +func TestServerRequestGetParamsMissingOptionalParams(t *testing.T) { + info := newServerMethodInfo( + func(context.Context, *ServerRequest[*InitializedParams]) (*emptyResult, error) { + return &emptyResult{}, nil + }, + missingParamsOK, + ) + params, err := info.unmarshalParams(nil) + if err != nil { + t.Fatal(err) + } + if params != nil { + t.Fatalf("unmarshalParams returned %T, want nil", params) + } + + req := info.newRequest(&ServerSession{}, params, nil) + if got := req.GetParams(); got != nil { + t.Fatalf("GetParams() = %T, want nil", got) + } +} + +func TestRequestGetParamsTypedNilCustomParams(t *testing.T) { + type customParams struct{ ParamsBase } + + var params *customParams + if got := (&ClientRequest[*customParams]{Params: params}).GetParams(); got != nil { + t.Fatalf("client GetParams() = %T, want nil", got) + } + if got := (&ServerRequest[*customParams]{Params: params}).GetParams(); got != nil { + t.Fatalf("server GetParams() = %T, want nil", got) + } + + params = &customParams{} + if got := (&ClientRequest[*customParams]{Params: params}).GetParams(); got != params { + t.Fatalf("client GetParams() = %T, want original params", got) + } + if got := (&ServerRequest[*customParams]{Params: params}).GetParams(); got != params { + t.Fatalf("server GetParams() = %T, want original params", got) + } +} + func TestImplementationDescriptionJSON(t *testing.T) { impl := &Implementation{ Name: "greeter", From d0e13bfca2411f0f0b6c2c9fb4d5f2f86e994246 Mon Sep 17 00:00:00 2001 From: ychampion Date: Thu, 9 Jul 2026 09:11:14 +0000 Subject: [PATCH 2/2] mcp: initialize missing request params --- mcp/shared.go | 33 ++++++++++++++++++++++++++------- mcp/shared_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/mcp/shared.go b/mcp/shared.go index 6c58db3b..f36071de 100644 --- a/mcp/shared.go +++ b/mcp/shared.go @@ -269,6 +269,9 @@ func newClientMethodInfo[P paramsPtr[T], R Result, T any](d typedClientMethodHan r := &ClientRequest[P]{Session: s.(*ClientSession)} if p != nil { r.Params = p.(P) + } else { + r.Params = P(new(T)) + r.missingParams = true } return r } @@ -284,6 +287,9 @@ func newServerMethodInfo[P paramsPtr[T], R Result, T any](d typedServerMethodHan r := &ServerRequest[P]{Session: s.(*ServerSession), Extra: re} if p != nil { r.Params = p.(P) + } else { + r.Params = P(new(T)) + r.missingParams = true } return r } @@ -560,15 +566,17 @@ type Request interface { // A ClientRequest is a request to a client. type ClientRequest[P Params] struct { - Session *ClientSession - Params P + Session *ClientSession + Params P + missingParams bool } // A ServerRequest is a request to a server. type ServerRequest[P Params] struct { - Session *ServerSession - Params P - Extra *RequestExtra + Session *ServerSession + Params P + Extra *RequestExtra + missingParams bool } // RequestExtra is extra information included in requests, typically from @@ -604,8 +612,19 @@ func (*ServerRequest[P]) isRequest() {} func (r *ClientRequest[P]) GetSession() Session { return r.Session } func (r *ServerRequest[P]) GetSession() Session { return r.Session } -func (r *ClientRequest[P]) GetParams() Params { return requestParams(r.Params) } -func (r *ServerRequest[P]) GetParams() Params { return requestParams(r.Params) } +func (r *ClientRequest[P]) GetParams() Params { + if r.missingParams { + return nil + } + return requestParams(r.Params) +} + +func (r *ServerRequest[P]) GetParams() Params { + if r.missingParams { + return nil + } + return requestParams(r.Params) +} func requestParams[P Params](params P) Params { if any(params) == nil { diff --git a/mcp/shared_test.go b/mcp/shared_test.go index 37e4db9f..bae46cbd 100644 --- a/mcp/shared_test.go +++ b/mcp/shared_test.go @@ -280,6 +280,41 @@ func TestServerRequestGetParamsMissingOptionalParams(t *testing.T) { if got := req.GetParams(); got != nil { t.Fatalf("GetParams() = %T, want nil", got) } + typedReq := req.(*ServerRequest[*InitializedParams]) + if typedReq.Params == nil { + t.Fatal("Params field is nil, want zero InitializedParams") + } + if typedReq.Params.GetMeta() != nil { + t.Fatalf("Params.GetMeta() = %v, want nil", typedReq.Params.GetMeta()) + } +} + +func TestClientRequestMissingOptionalParamsUsesZeroValue(t *testing.T) { + info := newClientMethodInfo( + func(context.Context, *ClientRequest[*PingParams]) (*emptyResult, error) { + return &emptyResult{}, nil + }, + missingParamsOK, + ) + params, err := info.unmarshalParams(nil) + if err != nil { + t.Fatal(err) + } + if params != nil { + t.Fatalf("unmarshalParams returned %T, want nil", params) + } + + req := info.newRequest(&ClientSession{}, params, nil) + if got := req.GetParams(); got != nil { + t.Fatalf("GetParams() = %T, want nil", got) + } + typedReq := req.(*ClientRequest[*PingParams]) + if typedReq.Params == nil { + t.Fatal("Params field is nil, want zero PingParams") + } + if typedReq.Params.GetMeta() != nil { + t.Fatalf("Params.GetMeta() = %v, want nil", typedReq.Params.GetMeta()) + } } func TestRequestGetParamsTypedNilCustomParams(t *testing.T) {