internal/mcp: generic handling of CallToolParams

Make Tool APIs future-proof by pushing generics into CallToolParams, and
using CallToolParams throughout. This has several advantages:

- We no longer need the awkward CallToolOptions type, which is almost
  but not quite like CallToolParams.
- NewTool no longer needs to define its own handler type: it accepts a
  ToolHandler[TArgs]. This also means its signature is future-proof.

Additionally, provide a `CallTool` helper (the analog of NewTool) to
serve as a more strongly typed variant of `ClientSession.CallTool`.

+ design, protocol generation

Change-Id: I80172a25bc50734dae84a541aa4776ed9161694e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/676495
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Auto-Submit: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Rob Findley 2025-05-27 14:15:09 +00:00 • committed by Gopher Robot
commit 04ca8ac03b

View file

@ -38,9 +38,10 @@ var schemaFile = flag.String("schema_file", "", "if set, use this file as the pe
// struct field. In others, we may want to extract the type definition to a
// name.
type typeConfig struct {
Name string // declaration name for the type
Substitute string // type definition to substitute
Fields config // individual field configuration, or nil
Name string // declaration name for the type
TypeParams [][2]string // formatted type parameter list ({name, constraint}), if any
Substitute string // type definition to substitute
Fields config // individual field configuration, or nil
}
type config map[string]*typeConfig
@ -57,9 +58,10 @@ var declarations = config{
Name: "-",
Fields: config{
"Params": {
Name: "CallToolParams",
Name: "CallToolParams",
TypeParams: [][2]string{{"TArgs", "any"}},
Fields: config{
"Arguments": {Substitute: "json.RawMessage"},
"Arguments": {Substitute: "TArgs"},
},
},
},
@ -224,8 +226,6 @@ func main() {
package mcp
import (
"encoding/json"
"github.com/modelcontextprotocol/go-sdk/jsonschema"
)
`)
@ -320,7 +320,18 @@ func writeDecl(configName string, config typeConfig, def *jsonschema.Schema, nam
if def.Description != "" {
fmt.Fprintf(buf, "%s\n", toComment(def.Description))
}
fmt.Fprintf(buf, "type %s ", typeName)
typeParams := new(strings.Builder)
if len(config.TypeParams) > 0 {
typeParams.WriteByte('[')
for i, p := range config.TypeParams {
if i > 0 {
typeParams.WriteString(", ")
}
fmt.Fprintf(typeParams, "%s %s", p[0], p[1])
}
typeParams.WriteByte(']')
}
fmt.Fprintf(buf, "type %s%s ", typeName, typeParams)
}
if err := writeType(w, &config, def, named); err != nil {
return err // Better error here?
@ -329,7 +340,18 @@ func writeDecl(configName string, config typeConfig, def *jsonschema.Schema, nam
// Any decl with a _meta field gets a GetMeta method.
if _, ok := def.Properties["_meta"]; ok {
fmt.Fprintf(w, "\nfunc (x *%s) GetMeta() *Meta { return &x.Meta }", typeName)
targs := new(strings.Builder)
if len(config.TypeParams) > 0 {
targs.WriteByte('[')
for i, p := range config.TypeParams {
if i > 0 {
targs.WriteString(", ")
}
fmt.Fprintf(targs, "%s", p[0])
}
targs.WriteByte(']')
}
fmt.Fprintf(w, "\nfunc (x *%s%s) GetMeta() *Meta { return &x.Meta }", typeName, targs)
}
if _, ok := def.Properties["cursor"]; ok {