@@ -165,9 +165,9 @@ func (s *Server) RemovePrompts(names ...string) {
165165//
166166// The tool's input schema must be non-nil and have the type "object". For a tool
167167// that takes no input, or one where any input is valid, set [Tool.InputSchema] to
168- // &jsonschema.Schema{Type : "object"}.
168+ // `{"type" : "object"}`, using your preferred library or `json.RawMessage` .
169169//
170- // If present, the output schema must also have type "object".
170+ // If present, [Tool.OutputSchema] must also have type "object".
171171//
172172// When the handler is invoked as part of a CallTool request, req.Params.Arguments
173173// will be a json.RawMessage.
@@ -190,11 +190,29 @@ func (s *Server) AddTool(t *Tool, h ToolHandler) {
190190 // discovered until runtime, when the LLM sent bad data.
191191 panic (fmt .Errorf ("AddTool %q: missing input schema" , t .Name ))
192192 }
193- if t .InputSchema .Type != "object" {
193+ if s , ok := t .InputSchema .( * jsonschema. Schema ); ok && s .Type != "object" {
194194 panic (fmt .Errorf (`AddTool %q: input schema must have type "object"` , t .Name ))
195+ } else {
196+ var m map [string ]any
197+ if err := remarshal (t .InputSchema , & m ); err != nil {
198+ panic (fmt .Errorf ("AddTool %q: can't marshal input schema to a JSON object: %v" , t .Name , err ))
199+ }
200+ if typ := m ["type" ]; typ != "object" {
201+ panic (fmt .Errorf (`AddTool %q: input schema must have type "object" (got %v)` , t .Name , typ ))
202+ }
195203 }
196- if t .OutputSchema != nil && t .OutputSchema .Type != "object" {
197- panic (fmt .Errorf (`AddTool %q: output schema must have type "object"` , t .Name ))
204+ if t .OutputSchema != nil {
205+ if s , ok := t .OutputSchema .(* jsonschema.Schema ); ok && s .Type != "object" {
206+ panic (fmt .Errorf (`AddTool %q: output schema must have type "object"` , t .Name ))
207+ } else {
208+ var m map [string ]any
209+ if err := remarshal (t .OutputSchema , & m ); err != nil {
210+ panic (fmt .Errorf ("AddTool %q: can't marshal output schema to a JSON object: %v" , t .Name , err ))
211+ }
212+ if typ := m ["type" ]; typ != "object" {
213+ panic (fmt .Errorf (`AddTool %q: output schema must have type "object" (got %v)` , t .Name , typ ))
214+ }
215+ }
198216 }
199217 st := & serverTool {tool : t , handler : h }
200218 // Assume there was a change, since add replaces existing tools.
@@ -332,36 +350,50 @@ func toolForErr[In, Out any](t *Tool, h ToolHandlerFor[In, Out]) (*Tool, ToolHan
332350//
333351// TODO(rfindley): we really shouldn't ever return 'null' results. Maybe we
334352// should have a jsonschema.Zero(schema) helper?
335- func setSchema [T any ](sfield * * jsonschema.Schema , rfield * * jsonschema.Resolved ) (zero any , err error ) {
353+ func setSchema [T any ](sfield * any , rfield * * jsonschema.Resolved ) (zero any , err error ) {
354+ var internalSchema * jsonschema.Schema
336355 if * sfield == nil {
337356 rt := reflect .TypeFor [T ]()
338357 if rt .Kind () == reflect .Pointer {
339358 rt = rt .Elem ()
340359 zero = reflect .Zero (rt ).Interface ()
341360 }
342361 // TODO: we should be able to pass nil opts here.
343- * sfield , err = jsonschema .ForType (rt , & jsonschema.ForOptions {})
362+ internalSchema , err = jsonschema .ForType (rt , & jsonschema.ForOptions {})
363+ if err == nil {
364+ * sfield = internalSchema
365+ }
366+ } else {
367+ if err := remarshal (* sfield , & internalSchema ); err != nil {
368+ return zero , err
369+ }
344370 }
345371 if err != nil {
346372 return zero , err
347373 }
348- * rfield , err = ( * sfield ) .Resolve (& jsonschema.ResolveOptions {ValidateDefaults : true })
374+ * rfield , err = internalSchema .Resolve (& jsonschema.ResolveOptions {ValidateDefaults : true })
349375 return zero , err
350376}
351377
352378// AddTool adds a tool and typed tool handler to the server.
353379//
354380// If the tool's input schema is nil, it is set to the schema inferred from the
355- // In type parameter, using [jsonschema.For]. The In type argument must be a
356- // map or a struct, so that its inferred JSON Schema has type "object".
381+ // In type parameter. Types are inferred from Go types, and property
382+ // descriptions are read from the 'jsonschema' struct tag. Internally, the SDK
383+ // uses the github.com/google/jsonschema-go package for ineference and
384+ // validation. The In type argument must be a map or a struct, so that its
385+ // inferred JSON Schema has type "object", as required by the spec. As a
386+ // special case, if the In type is 'any', the tool's input schema is set to an
387+ // empty object schema value.
357388//
358389// If the tool's output schema is nil, and the Out type is not 'any', the
359390// output schema is set to the schema inferred from the Out type argument,
360- // which also must be a map or struct.
391+ // which must also be a map or struct. If the Out type is 'any', the output
392+ // schema is omitted.
361393//
362- // Unlike [Server.AddTool], AddTool does a lot automatically, and forces tools
363- // to conform to the MCP spec. See [ToolHandlerFor] for a detailed description
364- // of this automatic behavior.
394+ // Unlike [Server.AddTool], AddTool does a lot automatically, and forces
395+ // tools to conform to the MCP spec. See [ToolHandlerFor] for a detailed
396+ // description of this automatic behavior.
365397func AddTool [In , Out any ](s * Server , t * Tool , h ToolHandlerFor [In , Out ]) {
366398 tt , hh , err := toolForErr (t , h )
367399 if err != nil {
0 commit comments