99 "context"
1010 "encoding/base64"
1111 "encoding/gob"
12+ "encoding/json"
1213 "fmt"
1314 "iter"
1415 "maps"
@@ -153,10 +154,18 @@ func (s *Server) RemovePrompts(names ...string) {
153154// If present, the output schema must also have type "object".
154155//
155156// When the handler is invoked as part of a CallTool request, req.Params.Arguments
156- // will be a json.RawMessage. Unmarshaling the arguments and validating them against the
157- // input schema are the handler author's responsibility.
157+ // will be a json.RawMessage.
158158//
159- // Most users should use the top-level function [AddTool].
159+ // Unmarshaling the arguments and validating them against the input schema are the
160+ // caller's responsibility.
161+ //
162+ // Validating the result against the output schema, if any, is the caller's responsibility.
163+ //
164+ // Setting the result's Content, StructuredContent and IsError fields are the caller's
165+ // responsibility.
166+ //
167+ // Most users should use the top-level function [AddTool], which handles all these
168+ // responsibilities.
160169func (s * Server ) AddTool (t * Tool , h ToolHandler ) {
161170 if t .InputSchema == nil {
162171 // This prevents the tool author from forgetting to write a schema where
@@ -180,26 +189,6 @@ func (s *Server) AddTool(t *Tool, h ToolHandler) {
180189 func () bool { s .tools .add (st ); return true })
181190}
182191
183- // ToolFor returns a shallow copy of t and a [ToolHandler] that wraps h.
184- //
185- // If the tool's input schema is nil, it is set to the schema inferred from the In
186- // type parameter, using [jsonschema.For]. The In type parameter must be a map
187- // or a struct, so that its inferred JSON Schema has type "object".
188- //
189- // For tools that don't return structured output, Out should be 'any'.
190- // Otherwise, if the tool's output schema is nil the output schema is set to
191- // the schema inferred from Out, which must be a map or a struct.
192- //
193- // Most users will call [AddTool]. Use [ToolFor] if you wish to modify the
194- // tool's schemas or wrap the ToolHandler before calling [Server.AddTool].
195- func ToolFor [In , Out any ](t * Tool , h ToolHandlerFor [In , Out ]) (* Tool , ToolHandler ) {
196- tt , hh , err := toolForErr (t , h )
197- if err != nil {
198- panic (fmt .Sprintf ("ToolFor: tool %q: %v" , t .Name , err ))
199- }
200- return tt , hh
201- }
202-
203192// TODO(v0.3.0): test
204193func toolForErr [In , Out any ](t * Tool , h ToolHandlerFor [In , Out ]) (* Tool , ToolHandler , error ) {
205194 tt := * t
@@ -265,26 +254,35 @@ func toolForErr[In, Out any](t *Tool, h ToolHandlerFor[In, Out]) (*Tool, ToolHan
265254 return nil , fmt .Errorf ("tool output: %w" , err )
266255 }
267256
268- // TODO: return the serialized JSON in a TextContent block, as per spec?
269- // https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content
270- // But people may use res.Content for other things.
271257 if res == nil {
272258 res = & CallToolResult {}
273259 }
274- if res .Content == nil {
275- res .Content = []Content {} // avoid returning 'null'
276- }
277- res .StructuredContent = out
260+ // Marshal the output and put the RawMessage in the StructuredContent field.
261+ var outval any = out
278262 if elemZero != nil {
279263 // Avoid typed nil, which will serialize as JSON null.
280- // Instead, use the zero value of the non-zero
264+ // Instead, use the zero value of the unpointered type.
281265 var z Out
282266 if any (out ) == any (z ) { // zero is only non-nil if Out is a pointer type
283- res . StructuredContent = elemZero
267+ outval = elemZero
284268 }
285269 }
270+ outbytes , err := json .Marshal (outval )
271+ if err != nil {
272+ return nil , fmt .Errorf ("marshaling output: %w" , err )
273+ }
274+ res .StructuredContent = json .RawMessage (outbytes ) // avoid a second marshal over the wire
275+
276+ // If the Content field isn't being used, return the serialized JSON in a
277+ // TextContent block, as the spec suggests:
278+ // https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content.
279+ if res .Content == nil {
280+ res .Content = []Content {& TextContent {
281+ Text : string (outbytes ),
282+ }}
283+ }
286284 return res , nil
287- }
285+ } // end of handler
288286
289287 return & tt , th , nil
290288}
@@ -329,10 +327,12 @@ func setSchema[T any](sfield **jsonschema.Schema, rfield **jsonschema.Resolved)
329327// For tools that don't return structured output, Out should be 'any'.
330328// Otherwise, if the tool's output schema is nil the output schema is set to
331329// the schema inferred from Out, which must be a map or a struct.
332- //
333- // It is a convenience for s.AddTool(ToolFor(t, h)).
334330func AddTool [In , Out any ](s * Server , t * Tool , h ToolHandlerFor [In , Out ]) {
335- s .AddTool (ToolFor (t , h ))
331+ tt , hh , err := toolForErr (t , h )
332+ if err != nil {
333+ panic (fmt .Sprintf ("AddTool: tool %q: %v" , t .Name , err ))
334+ }
335+ s .AddTool (tt , hh )
336336}
337337
338338// RemoveTools removes the tools with the given names.
0 commit comments