Skip to content

Commit 28c9cc3

Browse files
refactor: make CallToolRequest.Arguments more flexible (Breaking Change) (#287)
* refactor: make CallToolRequest.Arguments more flexible This change allows for more flexible argument types in CallToolRequest. Arguments field is now of type 'any' instead of 'map[string]any'. Added GetArguments() and GetRawArguments() methods for backward compatibility. Fixes #104 * refactor: move tests to tools_test.go * feat: add BindArguments and helper functions for CallToolRequest * test: add tests for BindArguments and helper functions * docs: update README with helper function examples * update tests * feat: add more slice helper functions * Update mcp/tools.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update mcp/tools.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix tests * implement `NewTypedToolHandlerFunc` * fix --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 077f546 commit 28c9cc3

File tree

13 files changed

+1095
-22
lines changed

13 files changed

+1095
-22
lines changed

README.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,21 @@ func main() {
149149

150150
// Add the calculator handler
151151
s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
152-
op := request.Params.Arguments["operation"].(string)
153-
x := request.Params.Arguments["x"].(float64)
154-
y := request.Params.Arguments["y"].(float64)
152+
// Using helper functions for type-safe argument access
153+
op, err := request.RequireString("operation")
154+
if err != nil {
155+
return mcp.NewToolResultError(err.Error()), nil
156+
}
157+
158+
x, err := request.RequireFloat("x")
159+
if err != nil {
160+
return mcp.NewToolResultError(err.Error()), nil
161+
}
162+
163+
y, err := request.RequireFloat("y")
164+
if err != nil {
165+
return mcp.NewToolResultError(err.Error()), nil
166+
}
155167

156168
var result float64
157169
switch op {
@@ -312,9 +324,10 @@ calculatorTool := mcp.NewTool("calculate",
312324
)
313325

314326
s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
315-
op := request.Params.Arguments["operation"].(string)
316-
x := request.Params.Arguments["x"].(float64)
317-
y := request.Params.Arguments["y"].(float64)
327+
args := request.GetArguments()
328+
op := args["operation"].(string)
329+
x := args["x"].(float64)
330+
y := args["y"].(float64)
318331

319332
var result float64
320333
switch op {
@@ -355,10 +368,11 @@ httpTool := mcp.NewTool("http_request",
355368
)
356369

357370
s.AddTool(httpTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
358-
method := request.Params.Arguments["method"].(string)
359-
url := request.Params.Arguments["url"].(string)
371+
args := request.GetArguments()
372+
method := args["method"].(string)
373+
url := args["url"].(string)
360374
body := ""
361-
if b, ok := request.Params.Arguments["body"].(string); ok {
375+
if b, ok := args["body"].(string); ok {
362376
body = b
363377
}
364378

client/inprocess_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestInProcessMCPClient(t *testing.T) {
3232
Content: []mcp.Content{
3333
mcp.TextContent{
3434
Type: "text",
35-
Text: "Input parameter: " + request.Params.Arguments["parameter-1"].(string),
35+
Text: "Input parameter: " + request.GetArguments()["parameter-1"].(string),
3636
},
3737
mcp.AudioContent{
3838
Type: "audio",

client/sse_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestSSEMCPClient(t *testing.T) {
3636
Content: []mcp.Content{
3737
mcp.TextContent{
3838
Type: "text",
39-
Text: "Input parameter: " + request.Params.Arguments["parameter-1"].(string),
39+
Text: "Input parameter: " + request.GetArguments()["parameter-1"].(string),
4040
},
4141
},
4242
}, nil

examples/custom_context/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func handleMakeAuthenticatedRequestTool(
8181
ctx context.Context,
8282
request mcp.CallToolRequest,
8383
) (*mcp.CallToolResult, error) {
84-
message, ok := request.Params.Arguments["message"].(string)
84+
message, ok := request.GetArguments()["message"].(string)
8585
if !ok {
8686
return nil, fmt.Errorf("missing message")
8787
}

examples/dynamic_path/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func main() {
2020

2121
// Add a trivial tool for demonstration
2222
mcpServer.AddTool(mcp.NewTool("echo"), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
23-
return mcp.NewToolResultText(fmt.Sprintf("Echo: %v", req.Params.Arguments["message"])), nil
23+
return mcp.NewToolResultText(fmt.Sprintf("Echo: %v", req.GetArguments()["message"])), nil
2424
})
2525

2626
// Use a dynamic base path based on a path parameter (Go 1.22+)

examples/everything/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ func handleEchoTool(
312312
ctx context.Context,
313313
request mcp.CallToolRequest,
314314
) (*mcp.CallToolResult, error) {
315-
arguments := request.Params.Arguments
315+
arguments := request.GetArguments()
316316
message, ok := arguments["message"].(string)
317317
if !ok {
318318
return nil, fmt.Errorf("invalid message argument")
@@ -331,7 +331,7 @@ func handleAddTool(
331331
ctx context.Context,
332332
request mcp.CallToolRequest,
333333
) (*mcp.CallToolResult, error) {
334-
arguments := request.Params.Arguments
334+
arguments := request.GetArguments()
335335
a, ok1 := arguments["a"].(float64)
336336
b, ok2 := arguments["b"].(float64)
337337
if !ok1 || !ok2 {
@@ -382,7 +382,7 @@ func handleLongRunningOperationTool(
382382
ctx context.Context,
383383
request mcp.CallToolRequest,
384384
) (*mcp.CallToolResult, error) {
385-
arguments := request.Params.Arguments
385+
arguments := request.GetArguments()
386386
progressToken := request.Params.Meta.ProgressToken
387387
duration, _ := arguments["duration"].(float64)
388388
steps, _ := arguments["steps"].(float64)

examples/typed_tools/main.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/mark3labs/mcp-go/mcp"
8+
"github.com/mark3labs/mcp-go/server"
9+
)
10+
11+
// Define a struct for our typed arguments
12+
type GreetingArgs struct {
13+
Name string `json:"name"`
14+
Age int `json:"age"`
15+
IsVIP bool `json:"is_vip"`
16+
Languages []string `json:"languages"`
17+
Metadata struct {
18+
Location string `json:"location"`
19+
Timezone string `json:"timezone"`
20+
} `json:"metadata"`
21+
}
22+
23+
func main() {
24+
// Create a new MCP server
25+
s := server.NewMCPServer(
26+
"Typed Tools Demo 🚀",
27+
"1.0.0",
28+
server.WithToolCapabilities(false),
29+
)
30+
31+
// Add tool with complex schema
32+
tool := mcp.NewTool("greeting",
33+
mcp.WithDescription("Generate a personalized greeting"),
34+
mcp.WithString("name",
35+
mcp.Required(),
36+
mcp.Description("Name of the person to greet"),
37+
),
38+
mcp.WithNumber("age",
39+
mcp.Description("Age of the person"),
40+
mcp.Min(0),
41+
mcp.Max(150),
42+
),
43+
mcp.WithBoolean("is_vip",
44+
mcp.Description("Whether the person is a VIP"),
45+
mcp.DefaultBool(false),
46+
),
47+
mcp.WithArray("languages",
48+
mcp.Description("Languages the person speaks"),
49+
mcp.Items(map[string]any{"type": "string"}),
50+
),
51+
mcp.WithObject("metadata",
52+
mcp.Description("Additional information about the person"),
53+
mcp.Properties(map[string]any{
54+
"location": map[string]any{
55+
"type": "string",
56+
"description": "Current location",
57+
},
58+
"timezone": map[string]any{
59+
"type": "string",
60+
"description": "Timezone",
61+
},
62+
}),
63+
),
64+
)
65+
66+
// Add tool handler using the typed handler
67+
s.AddTool(tool, mcp.NewTypedToolHandler(typedGreetingHandler))
68+
69+
// Start the stdio server
70+
if err := server.ServeStdio(s); err != nil {
71+
fmt.Printf("Server error: %v\n", err)
72+
}
73+
}
74+
75+
// Our typed handler function that receives strongly-typed arguments
76+
func typedGreetingHandler(ctx context.Context, request mcp.CallToolRequest, args GreetingArgs) (*mcp.CallToolResult, error) {
77+
if args.Name == "" {
78+
return mcp.NewToolResultError("name is required"), nil
79+
}
80+
81+
// Build a personalized greeting based on the complex arguments
82+
greeting := fmt.Sprintf("Hello, %s!", args.Name)
83+
84+
if args.Age > 0 {
85+
greeting += fmt.Sprintf(" You are %d years old.", args.Age)
86+
}
87+
88+
if args.IsVIP {
89+
greeting += " Welcome back, valued VIP customer!"
90+
}
91+
92+
if len(args.Languages) > 0 {
93+
greeting += fmt.Sprintf(" You speak %d languages: %v.", len(args.Languages), args.Languages)
94+
}
95+
96+
if args.Metadata.Location != "" {
97+
greeting += fmt.Sprintf(" I see you're from %s.", args.Metadata.Location)
98+
99+
if args.Metadata.Timezone != "" {
100+
greeting += fmt.Sprintf(" Your timezone is %s.", args.Metadata.Timezone)
101+
}
102+
}
103+
104+
return mcp.NewToolResultText(greeting), nil
105+
}

0 commit comments

Comments
 (0)