Skip to content

Commit f8eac9d

Browse files
committed
fix: default empty, nil, or null arguments to empty object to prevent unmarshal error
1 parent 3422703 commit f8eac9d

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

pkg/inventory/server_tool.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,15 @@ func NewServerToolWithContextHandler[In any, Out any](tool mcp.Tool, toolset Too
132132
// HandlerFunc ignores deps - deps are retrieved from context at call time
133133
HandlerFunc: func(_ any) mcp.ToolHandler {
134134
return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
135+
argumentsRaw := []byte("{}")
136+
if req.Params != nil && len(req.Params.Arguments) > 0 {
137+
argumentsRaw = req.Params.Arguments
138+
}
139+
if string(argumentsRaw) == "null" {
140+
argumentsRaw = []byte("{}")
141+
}
135142
var arguments In
136-
if err := json.Unmarshal(req.Params.Arguments, &arguments); err != nil {
143+
if err := json.Unmarshal(argumentsRaw, &arguments); err != nil {
137144
return &mcp.CallToolResult{
138145
Content: []mcp.Content{
139146
&mcp.TextContent{Text: fmt.Sprintf("invalid arguments: %s", err)},

pkg/inventory/server_tool_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,70 @@ func TestNewServerToolWithContextHandler_ValidArguments_Succeeds(t *testing.T) {
7878
require.True(t, ok)
7979
assert.Equal(t, "success: octocat/hello-world", textContent.Text)
8080
}
81+
82+
func TestNewServerToolWithContextHandler_EmptyArguments_Succeeds(t *testing.T) {
83+
type expectedArgs struct {
84+
Owner string `json:"owner,omitempty"`
85+
Repo string `json:"repo,omitempty"`
86+
}
87+
88+
tool := NewServerToolWithContextHandler(
89+
mcp.Tool{Name: "test_tool"},
90+
testToolsetMetadata("test"),
91+
func(_ context.Context, _ *mcp.CallToolRequest, args expectedArgs) (*mcp.CallToolResult, any, error) {
92+
return &mcp.CallToolResult{
93+
Content: []mcp.Content{
94+
&mcp.TextContent{Text: "success: " + args.Owner + "/" + args.Repo},
95+
},
96+
}, nil, nil
97+
},
98+
)
99+
100+
handler := tool.HandlerFunc(nil)
101+
102+
testCases := []struct {
103+
name string
104+
arguments json.RawMessage
105+
expected string
106+
}{
107+
{
108+
name: "nil arguments",
109+
arguments: nil,
110+
expected: "success: /",
111+
},
112+
{
113+
name: "empty arguments",
114+
arguments: json.RawMessage(``),
115+
expected: "success: /",
116+
},
117+
{
118+
name: "null arguments",
119+
arguments: json.RawMessage(`null`),
120+
expected: "success: /",
121+
},
122+
{
123+
name: "empty object arguments",
124+
arguments: json.RawMessage(`{}`),
125+
expected: "success: /",
126+
},
127+
}
128+
129+
for _, tc := range testCases {
130+
t.Run(tc.name, func(t *testing.T) {
131+
result, err := handler(context.Background(), &mcp.CallToolRequest{
132+
Params: &mcp.CallToolParamsRaw{
133+
Name: "test_tool",
134+
Arguments: tc.arguments,
135+
},
136+
})
137+
138+
require.NoError(t, err)
139+
require.NotNil(t, result)
140+
assert.False(t, result.IsError)
141+
textContent, ok := result.Content[0].(*mcp.TextContent)
142+
require.True(t, ok)
143+
assert.Equal(t, tc.expected, textContent.Text)
144+
})
145+
}
146+
}
147+

0 commit comments

Comments
 (0)