go-sdk/mcp/tool.go
Rob Findley fe938f6eb7 internal/mcp: implement ping, and test request interleaving
Also validate that requests observe the lifecycle rules defined in the
spec.

Change-Id: I302282f81e053e76926e0bb295f7dbf8d526f02d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/667575
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Commit-Queue: Robert Findley <rfindley@google.com>
Auto-Submit: Robert Findley <rfindley@google.com>
TryBot-Bypass: Robert Findley <rfindley@google.com>
2025-04-23 19:15:29 -07:00

70 lines
2.2 KiB
Go

// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package mcp
import (
"context"
"encoding/json"
"github.com/modelcontextprotocol/go-sdk/internal/jsonschema"
"github.com/modelcontextprotocol/go-sdk/internal/protocol"
)
// A ToolHandler handles a call to tools/call.
type ToolHandler func(context.Context, *ClientConnection, json.RawMessage) (*protocol.CallToolResult, error)
// A Tool is a tool definition that is bound to a tool handler.
type Tool struct {
Definition protocol.Tool
Handler ToolHandler
}
// MakeTool is a helper to make a tool using reflection on the given handler.
//
// The input schema for the tool is extracted from the request type for the
// handler, and used to unmmarshal and validate requests to the handler.
//
// It is the caller's responsibility that the handler request type can produce
// a valid schema, as documented by [jsonschema.ForType]; otherwise, MakeTool
// panics.
func MakeTool[TReq any](name, description string, handler func(context.Context, *ClientConnection, TReq) ([]Content, error)) *Tool {
schema, err := jsonschema.ForType[TReq]()
if err != nil {
panic(err)
}
wrapped := func(ctx context.Context, cc *ClientConnection, args json.RawMessage) (*protocol.CallToolResult, error) {
var v TReq
if err := unmarshalSchema(args, schema, &v); err != nil {
return nil, err
}
content, err := handler(ctx, cc, v)
if err != nil {
return &protocol.CallToolResult{
Content: marshalContent([]Content{TextContent{Text: err.Error()}}),
IsError: true,
}, nil
}
res := &protocol.CallToolResult{
Content: marshalContent(content),
}
return res, nil
}
return &Tool{
Definition: protocol.Tool{
Name: name,
Description: description,
InputSchema: schema,
},
Handler: wrapped,
}
}
// unmarshalSchema unmarshals data into v and validates the result according to
// the given schema.
func unmarshalSchema(data json.RawMessage, _ *jsonschema.Schema, v any) error {
// TODO: use reflection to create the struct type to unmarshal into.
// Separate validation from assignment.
return json.Unmarshal(data, v)
}