Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion pkg/inventory/server_tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,15 @@ func NewServerToolWithContextHandler[In any, Out any](tool mcp.Tool, toolset Too
// HandlerFunc ignores deps - deps are retrieved from context at call time
HandlerFunc: func(_ any) mcp.ToolHandler {
return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
argumentsRaw := []byte("{}")
if req.Params != nil && len(req.Params.Arguments) > 0 {
argumentsRaw = req.Params.Arguments
}
if string(argumentsRaw) == "null" {
argumentsRaw = []byte("{}")
}
var arguments In
if err := json.Unmarshal(req.Params.Arguments, &arguments); err != nil {
if err := json.Unmarshal(argumentsRaw, &arguments); err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: fmt.Sprintf("invalid arguments: %s", err)},
Expand Down
91 changes: 56 additions & 35 deletions pkg/inventory/server_tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,49 +81,70 @@ func TestNewServerToolWithContextHandler_ValidArguments_Succeeds(t *testing.T) {
assert.Equal(t, "success: octocat/hello-world", textContent.Text)
}

func TestServerToolRegisterFuncAppliesMiddleware(t *testing.T) {
tool := NewServerTool(
mcp.Tool{
Name: "wrapped_tool",
InputSchema: &jsonschema.Schema{Type: "object"},
},
func TestNewServerToolWithContextHandler_EmptyArguments_Succeeds(t *testing.T) {
type expectedArgs struct {
Owner string `json:"owner,omitempty"`
Repo string `json:"repo,omitempty"`
}

tool := NewServerToolWithContextHandler(
mcp.Tool{Name: "test_tool"},
testToolsetMetadata("test"),
func(_ context.Context, _ *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
func(_ context.Context, _ *mcp.CallToolRequest, args expectedArgs) (*mcp.CallToolResult, any, error) {
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: "handler"}},
}, nil
Content: []mcp.Content{
&mcp.TextContent{Text: "success: " + args.Owner + "/" + args.Repo},
},
}, nil, nil
},
)

middlewareCalled := make(chan struct{}, 1)
middleware := func(next mcp.ToolHandler) mcp.ToolHandler {
return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
middlewareCalled <- struct{}{}
return next(ctx, req)
}
}

server := mcp.NewServer(&mcp.Implementation{Name: "test-server", Version: "v0.0.1"}, nil)
tool.RegisterFunc(server, nil, middleware)
st, ct := mcp.NewInMemoryTransports()
ss, err := server.Connect(context.Background(), st, nil)
require.NoError(t, err)
t.Cleanup(func() { _ = ss.Close() })
handler := tool.HandlerFunc(nil)

client := mcp.NewClient(&mcp.Implementation{Name: "test-client", Version: "v0.0.1"}, nil)
cs, err := client.Connect(context.Background(), ct, nil)
require.NoError(t, err)
t.Cleanup(func() { _ = cs.Close() })
testCases := []struct {
name string
arguments json.RawMessage
expected string
}{
{
name: "nil arguments",
arguments: nil,
expected: "success: /",
},
{
name: "empty arguments",
arguments: json.RawMessage(``),
expected: "success: /",
},
{
name: "null arguments",
arguments: json.RawMessage(`null`),
expected: "success: /",
},
{
name: "empty object arguments",
arguments: json.RawMessage(`{}`),
expected: "success: /",
},
}

result, err := cs.CallTool(context.Background(), &mcp.CallToolParams{Name: "wrapped_tool"})
require.NoError(t, err)
select {
case <-middlewareCalled:
default:
t.Fatal("tool middleware was not called")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := handler(context.Background(), &mcp.CallToolRequest{
Params: &mcp.CallToolParamsRaw{
Name: "test_tool",
Arguments: tc.arguments,
},
})

require.NoError(t, err)
require.NotNil(t, result)
assert.False(t, result.IsError)
textContent, ok := result.Content[0].(*mcp.TextContent)
require.True(t, ok)
assert.Equal(t, tc.expected, textContent.Text)
})
}
require.Len(t, result.Content, 1)
assert.Equal(t, "handler", result.Content[0].(*mcp.TextContent).Text)
}

func TestAnnotateHeaderParams(t *testing.T) {
Expand Down