From e6dcde818d0d71ab93e33e0bfb2fd1f35a1049f0 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 6 Jun 2026 19:33:01 +0000 Subject: [PATCH 1/5] fix: accept omitted tool arguments for zero-param tools like get_me When clients omit the arguments field on tools/call, the typed tool wrapper received nil RawMessage and failed JSON unmarshaling before the handler ran. Treat nil or empty arguments as {} so zero-parameter tools work as documented. Closes #2587 --- pkg/github/context_tools_test.go | 41 +++++++++++++++++++++++++++++++ pkg/github/helper_test.go | 10 ++++++++ pkg/inventory/server_tool.go | 6 ++++- pkg/inventory/server_tool_test.go | 40 ++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/pkg/github/context_tools_test.go b/pkg/github/context_tools_test.go index ade54aba17..95fa6a3ec7 100644 --- a/pkg/github/context_tools_test.go +++ b/pkg/github/context_tools_test.go @@ -139,6 +139,47 @@ func Test_GetMe(t *testing.T) { } } +func Test_GetMe_OmittedArguments(t *testing.T) { + t.Parallel() + + serverTool := GetMe(translations.NullTranslationHelper) + + mockUser := &github.User{ + Login: github.Ptr("testuser"), + Name: github.Ptr("Test User"), + HTMLURL: github.Ptr("https://github.com/testuser"), + CreatedAt: &github.Timestamp{Time: time.Now()}, + } + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUser: mockResponse(t, http.StatusOK, mockUser), + }) + deps := BaseDeps{Client: mustNewGHClient(t, mockedClient), Obsv: stubExporters()} + handler := serverTool.Handler(deps) + + tests := []struct { + name string + arguments json.RawMessage + }{ + {name: "nil arguments", arguments: nil}, + {name: "empty byte slice", arguments: json.RawMessage{}}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + request := createMCPRequestWithRawArguments(tc.arguments) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + var returnedUser MinimalUser + err = json.Unmarshal([]byte(textContent.Text), &returnedUser) + require.NoError(t, err) + assert.Equal(t, "testuser", returnedUser.Login) + }) + } +} + func Test_GetMe_IFC_FeatureFlag(t *testing.T) { t.Parallel() diff --git a/pkg/github/helper_test.go b/pkg/github/helper_test.go index fdac78ce3f..c6d30c5b6e 100644 --- a/pkg/github/helper_test.go +++ b/pkg/github/helper_test.go @@ -323,6 +323,16 @@ func createMCPRequest(args any) mcp.CallToolRequest { } } +// createMCPRequestWithRawArguments creates a CallToolRequest with the given raw JSON arguments. +// Use nil or an empty slice to simulate clients that omit the arguments field. +func createMCPRequestWithRawArguments(args json.RawMessage) mcp.CallToolRequest { + return mcp.CallToolRequest{ + Params: &mcp.CallToolParamsRaw{ + Arguments: args, + }, + } +} + // Well-known MCP client names used in tests. const ( ClientNameVSCodeInsiders = "Visual Studio Code - Insiders" diff --git a/pkg/inventory/server_tool.go b/pkg/inventory/server_tool.go index 326009b59f..57ec80d2c2 100644 --- a/pkg/inventory/server_tool.go +++ b/pkg/inventory/server_tool.go @@ -133,7 +133,11 @@ func NewServerToolWithContextHandler[In any, Out any](tool mcp.Tool, toolset Too HandlerFunc: func(_ any) mcp.ToolHandler { return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { var arguments In - if err := json.Unmarshal(req.Params.Arguments, &arguments); err != nil { + args := req.Params.Arguments + if len(args) == 0 { + args = json.RawMessage(`{}`) + } + if err := json.Unmarshal(args, &arguments); err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: fmt.Sprintf("invalid arguments: %s", err)}, diff --git a/pkg/inventory/server_tool_test.go b/pkg/inventory/server_tool_test.go index 69cee94af0..db133e0368 100644 --- a/pkg/inventory/server_tool_test.go +++ b/pkg/inventory/server_tool_test.go @@ -78,3 +78,43 @@ func TestNewServerToolWithContextHandler_ValidArguments_Succeeds(t *testing.T) { require.True(t, ok) assert.Equal(t, "success: octocat/hello-world", textContent.Text) } + +func TestNewServerToolWithContextHandler_EmptyArguments_TreatedAsEmptyObject(t *testing.T) { + tool := NewServerToolWithContextHandler( + mcp.Tool{Name: "zero_arg_tool"}, + testToolsetMetadata("test"), + func(_ context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "ok"}, + }, + }, args, nil + }, + ) + + handler := tool.HandlerFunc(nil) + + tests := []struct { + name string + arguments json.RawMessage + }{ + {name: "nil arguments", arguments: nil}, + {name: "empty byte slice", arguments: json.RawMessage{}}, + {name: "explicit empty object", arguments: json.RawMessage(`{}`)}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := handler(context.Background(), &mcp.CallToolRequest{ + Params: &mcp.CallToolParamsRaw{ + Name: "zero_arg_tool", + Arguments: tt.arguments, + }, + }) + + require.NoError(t, err) + require.NotNil(t, result) + assert.False(t, result.IsError) + }) + } +} From 70728d84a3d4004b85edd51a22001e69c1734458 Mon Sep 17 00:00:00 2001 From: advancedresearcharray Date: Tue, 9 Jun 2026 03:33:19 +0000 Subject: [PATCH 2/5] test: cover explicit empty object in get_me omitted-arguments test Align get_me integration test cases with the inventory-level empty-arguments coverage so all three client shapes (nil, empty slice, {}) are verified. Co-authored-by: Cursor --- pkg/github/context_tools_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/github/context_tools_test.go b/pkg/github/context_tools_test.go index 95fa6a3ec7..b2562f6de4 100644 --- a/pkg/github/context_tools_test.go +++ b/pkg/github/context_tools_test.go @@ -162,6 +162,7 @@ func Test_GetMe_OmittedArguments(t *testing.T) { }{ {name: "nil arguments", arguments: nil}, {name: "empty byte slice", arguments: json.RawMessage{}}, + {name: "explicit empty object", arguments: json.RawMessage(`{}`)}, } for _, tc := range tests { From 7b7afc605a3804f758a061c4ad2eb69829b587a7 Mon Sep 17 00:00:00 2001 From: advancedresearcharray Date: Tue, 9 Jun 2026 03:36:35 +0000 Subject: [PATCH 3/5] test: assert zero-arg handler receives empty map for omitted arguments Strengthen the inventory-level empty-arguments test so nil, empty byte slice, and explicit {} inputs must all unmarshal to an empty map before the handler runs. Co-authored-by: Cursor --- pkg/inventory/server_tool_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/inventory/server_tool_test.go b/pkg/inventory/server_tool_test.go index db133e0368..c72355a10e 100644 --- a/pkg/inventory/server_tool_test.go +++ b/pkg/inventory/server_tool_test.go @@ -84,6 +84,14 @@ func TestNewServerToolWithContextHandler_EmptyArguments_TreatedAsEmptyObject(t * mcp.Tool{Name: "zero_arg_tool"}, testToolsetMetadata("test"), func(_ context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + if len(args) != 0 { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "expected empty arguments"}, + }, + IsError: true, + }, nil, nil + } return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: "ok"}, From 3457fd6da1565062a66c41e2bd58661dfbf778fb Mon Sep 17 00:00:00 2001 From: advancedresearcharray Date: Tue, 9 Jun 2026 11:37:24 +0000 Subject: [PATCH 4/5] fix: document empty-arguments normalization for zero-param tools Clarify why nil or empty arguments are coerced to {} before unmarshaling so future readers understand the get_me client-compat behavior. Co-authored-by: Cursor --- pkg/inventory/server_tool.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/inventory/server_tool.go b/pkg/inventory/server_tool.go index 57ec80d2c2..d84f8dc88d 100644 --- a/pkg/inventory/server_tool.go +++ b/pkg/inventory/server_tool.go @@ -134,6 +134,7 @@ func NewServerToolWithContextHandler[In any, Out any](tool mcp.Tool, toolset Too return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { var arguments In args := req.Params.Arguments + // Some MCP clients omit arguments for zero-parameter tools; treat as {}. if len(args) == 0 { args = json.RawMessage(`{}`) } From 638976e3da803338b0fea92b2decbe162b2729fa Mon Sep 17 00:00:00 2001 From: advancedresearcharray Date: Tue, 9 Jun 2026 11:39:44 +0000 Subject: [PATCH 5/5] test: cover typed empty struct for omitted tool arguments Verify zero-parameter tools using struct input types also succeed when clients omit the arguments field entirely. Co-authored-by: Cursor --- pkg/inventory/server_tool_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pkg/inventory/server_tool_test.go b/pkg/inventory/server_tool_test.go index c72355a10e..c21e5205d7 100644 --- a/pkg/inventory/server_tool_test.go +++ b/pkg/inventory/server_tool_test.go @@ -126,3 +126,32 @@ func TestNewServerToolWithContextHandler_EmptyArguments_TreatedAsEmptyObject(t * }) } } + +func TestNewServerToolWithContextHandler_EmptyArguments_TypedEmptyStruct(t *testing.T) { + type emptyArgs struct{} + + tool := NewServerToolWithContextHandler( + mcp.Tool{Name: "typed_zero_arg_tool"}, + testToolsetMetadata("test"), + func(_ context.Context, _ *mcp.CallToolRequest, args emptyArgs) (*mcp.CallToolResult, any, error) { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "ok"}, + }, + }, args, nil + }, + ) + + handler := tool.HandlerFunc(nil) + + result, err := handler(context.Background(), &mcp.CallToolRequest{ + Params: &mcp.CallToolParamsRaw{ + Name: "typed_zero_arg_tool", + Arguments: nil, + }, + }) + + require.NoError(t, err) + require.NotNil(t, result) + assert.False(t, result.IsError) +}