diff --git a/mcp/shared.go b/mcp/shared.go index 0fc19c5f..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,30 @@ 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 { + 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 { + 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..bae46cbd 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,82 @@ 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) + } + 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) { + 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",