Skip to content

Commit bdf0233

Browse files
committed
v2
1 parent 5237cd1 commit bdf0233

13 files changed

+216
-215
lines changed

mcp/content_nil_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ func TestContentUnmarshalNil(t *testing.T) {
5252
{
5353
name: "CallToolResultFor nil Content",
5454
json: `{"content":[{"type":"text","text":"hello"}]}`,
55-
content: &mcp.CallToolResultFor[string]{},
56-
want: &mcp.CallToolResultFor[string]{Content: []mcp.Content{&mcp.TextContent{Text: "hello"}}},
55+
content: &mcp.CallToolResult{},
56+
want: &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: "hello"}}},
5757
},
5858
}
5959

mcp/example_middleware_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func Example_loggingMiddleware() {
7272
server.AddReceivingMiddleware(loggingMiddleware)
7373

7474
// Add a simple tool
75-
server.AddTool(
75+
mcp.AddTool(server,
7676
&mcp.Tool{
7777
Name: "greet",
7878
Description: "Greet someone with logging.",
@@ -89,19 +89,19 @@ func Example_loggingMiddleware() {
8989
},
9090
func(
9191
ctx context.Context,
92-
req *mcp.ServerRequest[*mcp.CallToolParamsFor[map[string]any]],
93-
) (*mcp.CallToolResultFor[any], error) {
94-
name, ok := req.Params.Arguments["name"].(string)
92+
req *mcp.ServerRequest[*mcp.CallToolParams], args map[string]any,
93+
) (*mcp.CallToolResult, any, error) {
94+
name, ok := args["name"].(string)
9595
if !ok {
96-
return nil, fmt.Errorf("name parameter is required and must be a string")
96+
return nil, nil, fmt.Errorf("name parameter is required and must be a string")
9797
}
9898

9999
message := fmt.Sprintf("Hello, %s!", name)
100-
return &mcp.CallToolResultFor[any]{
100+
return &mcp.CallToolResult{
101101
Content: []mcp.Content{
102102
&mcp.TextContent{Text: message},
103103
},
104-
}, nil
104+
}, nil, nil
105105
},
106106
)
107107

mcp/mcp_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ func TestEndToEnd(t *testing.T) {
246246
t.Errorf("tools/call 'fail' mismatch (-want +got):\n%s", diff)
247247
}
248248

249-
s.AddRawTool(&Tool{Name: "T", InputSchema: &jsonschema.Schema{}}, nopHandler)
249+
s.AddTool(&Tool{Name: "T", InputSchema: &jsonschema.Schema{}}, nopHandler)
250250
waitForNotification(t, "tools")
251251
s.RemoveTools("T")
252252
waitForNotification(t, "tools")
@@ -657,7 +657,7 @@ func TestCancellation(t *testing.T) {
657657
return nil, nil
658658
}
659659
_, cs := basicConnection(t, func(s *Server) {
660-
s.AddRawTool(&Tool{Name: "slow"}, slowRequest)
660+
s.AddTool(&Tool{Name: "slow", InputSchema: &jsonschema.Schema{}}, slowRequest)
661661
})
662662
defer cs.Close()
663663

@@ -946,8 +946,8 @@ func TestAddTool_DuplicateNoPanicAndNoDuplicate(t *testing.T) {
946946
// This case was written specifically to reproduce a bug where duplicate tools where causing jsonschema errors
947947
t1 := &Tool{Name: "dup", Description: "first", InputSchema: &jsonschema.Schema{}}
948948
t2 := &Tool{Name: "dup", Description: "second", InputSchema: &jsonschema.Schema{}}
949-
s.AddRawTool(t1, nopHandler)
950-
s.AddRawTool(t2, nopHandler)
949+
s.AddTool(t1, nopHandler)
950+
s.AddTool(t2, nopHandler)
951951
})
952952
defer cs.Close()
953953

mcp/protocol.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -879,8 +879,6 @@ type Tool struct {
879879
// If not provided, Annotations.Title should be used for display if present,
880880
// otherwise Name.
881881
Title string `json:"title,omitempty"`
882-
883-
newArgs func() any
884882
}
885883

886884
// Additional properties describing a Tool to clients.

mcp/protocol_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ func TestContentUnmarshal(t *testing.T) {
519519
Meta: Meta{"m": true},
520520
Content: content,
521521
IsError: true,
522-
StructuredContent: 3,
522+
StructuredContent: 3.0,
523523
}
524524
var gotf CallToolResult
525525
roundtrip(ctrf, &gotf)

mcp/server.go

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func (s *Server) RemovePrompts(names ...string) {
140140
func() bool { return s.prompts.remove(names...) })
141141
}
142142

143-
// AddRawTool adds a [Tool] to the server, or replaces one with the same name.
143+
// AddTool adds a [Tool] to the server, or replaces one with the same name.
144144
// The Tool argument must not be modified after this call.
145145
//
146146
// The tool's input schema must be non-nil. For a tool that takes no input,
@@ -150,7 +150,14 @@ func (s *Server) RemovePrompts(names ...string) {
150150
// When the handler is invoked as part of a CallTool request, req.Params.Arguments
151151
// will be a json.RawMessage. Unmarshaling the arguments and validating them against the
152152
// input schema are the handler author's responsibility.
153-
func (s *Server) AddRawTool(t *Tool, h RawToolHandler) {
153+
func (s *Server) AddTool(t *Tool, h ToolHandler) {
154+
if t.InputSchema == nil {
155+
// This prevents the tool author from forgetting to write a schema where
156+
// one should be provided. If we papered over this by supplying the empty
157+
// schema, then every input would be validated and the problem wouldn't be
158+
// discovered until runtime, when the LLM sent bad data.
159+
panic(fmt.Errorf("AddTool %q: missing input schema", t.Name))
160+
}
154161
st := &serverTool{tool: t, handler: h}
155162
// Assume there was a change, since add replaces existing tools.
156163
// (It's possible a tool was replaced with an identical one, but not worth checking.)
@@ -160,43 +167,50 @@ func (s *Server) AddRawTool(t *Tool, h RawToolHandler) {
160167
func() bool { s.tools.add(st); return true })
161168
}
162169

170+
// ToolFor returns a shallow copy of t and a [ToolHandler] that wraps h.
163171
// If the tool's input schema is nil, it is set to the schema inferred from the In
164172
// type parameter, using [jsonschema.For].
165173
// If the tool's output schema is nil and the Out type parameter is not the empty
166174
// interface, then the output schema is set to the schema inferred from Out.
167-
func RawToolHandlerFor[In, Out any](t *Tool, h ToolHandlerFor[In, Out]) RawToolHandler {
168-
hh, err := toolForErr(t, h)
175+
//
176+
// Most users will call [AddTool]. Use [ToolFor] if you wish to wrap the ToolHandler
177+
// before calling [Server.AddTool].
178+
func ToolFor[In, Out any](t *Tool, h ToolHandlerFor[In, Out]) (*Tool, ToolHandler) {
179+
tt, hh, err := toolForErr(t, h)
169180
if err != nil {
170181
panic(fmt.Sprintf("ToolFor: tool %q: %v", t.Name, err))
171182
}
172-
return hh
183+
return tt, hh
173184
}
174185

175186
// TODO(v0.3.0): test
176-
func toolForErr[In, Out any](t *Tool, h ToolHandlerFor[In, Out]) (RawToolHandler, error) {
187+
func toolForErr[In, Out any](t *Tool, h ToolHandlerFor[In, Out]) (*Tool, ToolHandler, error) {
177188
var err error
178-
inputSchema := t.InputSchema
179-
if inputSchema == nil {
180-
inputSchema, err = jsonschema.For[In](nil)
189+
tt := *t
190+
tt.InputSchema = t.InputSchema
191+
if tt.InputSchema == nil {
192+
tt.InputSchema, err = jsonschema.For[In](nil)
181193
if err != nil {
182-
return nil, fmt.Errorf("input schema: %w", err)
194+
return nil, nil, fmt.Errorf("input schema: %w", err)
183195
}
184196
}
185-
inputResolved, err := inputSchema.Resolve(&jsonschema.ResolveOptions{ValidateDefaults: true})
197+
inputResolved, err := tt.InputSchema.Resolve(&jsonschema.ResolveOptions{ValidateDefaults: true})
186198
if err != nil {
187-
return nil, fmt.Errorf("resolving input schema: %w", err)
199+
return nil, nil, fmt.Errorf("resolving input schema: %w", err)
188200
}
189201

190-
outputSchema := t.OutputSchema
191-
if outputSchema == nil && reflect.TypeFor[Out]() != reflect.TypeFor[any]() {
192-
outputSchema, err = jsonschema.For[Out](nil)
202+
if tt.OutputSchema == nil && reflect.TypeFor[Out]() != reflect.TypeFor[any]() {
203+
tt.OutputSchema, err = jsonschema.For[Out](nil)
193204
}
194205
if err != nil {
195-
return nil, fmt.Errorf("output schema: %w", err)
206+
return nil, nil, fmt.Errorf("output schema: %w", err)
196207
}
197-
outputResolved, err := outputSchema.Resolve(&jsonschema.ResolveOptions{ValidateDefaults: true})
198-
if err != nil {
199-
return nil, fmt.Errorf("resolving output schema: %w", err)
208+
var outputResolved *jsonschema.Resolved
209+
if tt.OutputSchema != nil {
210+
outputResolved, err = tt.OutputSchema.Resolve(&jsonschema.ResolveOptions{ValidateDefaults: true})
211+
if err != nil {
212+
return nil, nil, fmt.Errorf("resolving output schema: %w", err)
213+
}
200214
}
201215

202216
th := func(ctx context.Context, req *ServerRequest[*CallToolParams]) (*CallToolResult, error) {
@@ -228,12 +242,12 @@ func toolForErr[In, Out any](t *Tool, h ToolHandlerFor[In, Out]) (RawToolHandler
228242
return res, nil
229243
}
230244

231-
return th, nil
245+
return &tt, th, nil
232246
}
233247

234-
// AddTool is a convenience for s.AddRawTool(t, RawToolHandler(t, h)).
248+
// AddTool is a convenience for s.AddTool(ToolFor(t, h)).
235249
func AddTool[In, Out any](s *Server, t *Tool, h ToolHandlerFor[In, Out]) {
236-
s.AddRawTool(t, RawToolHandlerFor(t, h))
250+
s.AddTool(ToolFor(t, h))
237251
}
238252

239253
// RemoveTools removes the tools with the given names.

mcp/server_example_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ type SayHiParams struct {
1616
Name string `json:"name"`
1717
}
1818

19-
func SayHi(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[SayHiParams]]) (*mcp.CallToolResultFor[any], error) {
20-
return &mcp.CallToolResultFor[any]{
19+
func SayHi(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParams], args SayHiParams) (*mcp.CallToolResult, any, error) {
20+
return &mcp.CallToolResult{
2121
Content: []mcp.Content{
22-
&mcp.TextContent{Text: "Hi " + req.Params.Arguments.Name},
22+
&mcp.TextContent{Text: "Hi " + args.Name},
2323
},
24-
}, nil
24+
}, nil, nil
2525
}
2626

2727
func ExampleServer() {

mcp/server_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ func TestServerCapabilities(t *testing.T) {
296296
{
297297
name: "With tools",
298298
configureServer: func(s *Server) {
299-
s.AddRawTool(tool, nil)
299+
s.AddTool(tool, nil)
300300
},
301301
wantCapabilities: &ServerCapabilities{
302302
Logging: &LoggingCapabilities{},
@@ -322,7 +322,7 @@ func TestServerCapabilities(t *testing.T) {
322322
s.AddPrompt(&Prompt{Name: "p"}, nil)
323323
s.AddResource(&Resource{URI: "file:///r"}, nil)
324324
s.AddResourceTemplate(&ResourceTemplate{URITemplate: "file:///rt"}, nil)
325-
s.AddRawTool(tool, nil)
325+
s.AddTool(tool, nil)
326326
},
327327
serverOpts: ServerOptions{
328328
SubscribeHandler: func(context.Context, *ServerRequest[*SubscribeParams]) error {

0 commit comments

Comments
 (0)