Files
amcs/vendor/github.com/modelcontextprotocol/go-sdk/mcp/tool.go
Hein 1adf50e3db
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s
fix(go.sum): update ResolveSpec dependency to v1.0.87
2026-06-23 13:17:16 +02:00

141 lines
5.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"
"fmt"
"strings"
"github.com/google/jsonschema-go/jsonschema"
internaljson "github.com/modelcontextprotocol/go-sdk/internal/json"
)
// A ToolHandler handles a call to tools/call.
//
// This is a low-level API, for use with [Server.AddTool]. It does not do any
// pre- or post-processing of the request or result: the params contain raw
// arguments, no input validation is performed, and the result is returned to
// the user as-is, without any validation of the output.
//
// Most users will write a [ToolHandlerFor] and install it with the generic
// [AddTool] function.
//
// If ToolHandler returns an error, it is treated as a protocol error. By
// contrast, [ToolHandlerFor] automatically populates [CallToolResult.IsError]
// and [CallToolResult.Content] accordingly.
type ToolHandler func(context.Context, *CallToolRequest) (*CallToolResult, error)
// A ToolHandlerFor handles a call to tools/call with typed arguments and results.
//
// Use [AddTool] to add a ToolHandlerFor to a server.
//
// Unlike [ToolHandler], [ToolHandlerFor] provides significant functionality
// out of the box, and enforces that the tool conforms to the MCP spec:
// - The In type provides a default input schema for the tool, though it may
// be overridden in [AddTool].
// - The input value is automatically unmarshaled from req.Params.Arguments.
// - The input value is automatically validated against its input schema.
// Invalid input is rejected before getting to the handler.
// - If the Out type is not the empty interface [any], it provides the
// default output schema for the tool (which again may be overridden in
// [AddTool]).
// - The Out value is used to populate result.StructuredOutput.
// - If [CallToolResult.Content] is unset, it is populated with the JSON
// content of the output.
// - An error result is treated as a tool error, rather than a protocol
// error, and is therefore packed into CallToolResult.Content, with
// [IsError] set.
//
// For these reasons, most users can ignore the [CallToolRequest] argument and
// [CallToolResult] return values entirely. In fact, it is permissible to
// return a nil CallToolResult, if you only care about returning a output value
// or error. The effective result will be populated as described above.
type ToolHandlerFor[In, Out any] func(_ context.Context, request *CallToolRequest, input In) (result *CallToolResult, output Out, _ error)
// A serverTool is a tool definition that is bound to a tool handler.
type serverTool struct {
tool *Tool
handler ToolHandler
}
// applySchema validates whether data is valid JSON according to the provided
// schema, after applying schema defaults.
//
// Returns the JSON value augmented with defaults.
func applySchema(data json.RawMessage, resolved *jsonschema.Resolved) (json.RawMessage, error) {
// TODO: use reflection to create the struct type to unmarshal into.
// Separate validation from assignment.
// Use default JSON marshalling for validation.
//
// This avoids inconsistent representation due to custom marshallers, such as
// time.Time (issue #449).
//
// Additionally, unmarshalling into a map ensures that the resulting JSON is
// at least {}, even if data is empty. For example, arguments is technically
// an optional property of callToolParams, and we still want to apply the
// defaults in this case.
//
// TODO(rfindley): in which cases can resolved be nil?
if resolved != nil {
v := make(map[string]any)
if len(data) > 0 {
if err := internaljson.Unmarshal(data, &v); err != nil {
return nil, fmt.Errorf("unmarshaling arguments: %w", err)
}
}
if err := resolved.ApplyDefaults(&v); err != nil {
return nil, fmt.Errorf("applying schema defaults:\n%w", err)
}
if err := resolved.Validate(&v); err != nil {
return nil, err
}
// We must re-marshal with the default values applied.
var err error
data, err = json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("marshalling with defaults: %v", err)
}
}
return data, nil
}
// validateToolName checks whether name is a valid tool name, reporting a
// non-nil error if not.
func validateToolName(name string) error {
if name == "" {
return fmt.Errorf("tool name cannot be empty")
}
if len(name) > 128 {
return fmt.Errorf("tool name exceeds maximum length of 128 characters (current: %d)", len(name))
}
// For consistency with other SDKs, report characters in the order the appear
// in the name.
var invalidChars []string
seen := make(map[rune]bool)
for _, r := range name {
if !validToolNameRune(r) {
if !seen[r] {
invalidChars = append(invalidChars, fmt.Sprintf("%q", string(r)))
seen[r] = true
}
}
}
if len(invalidChars) > 0 {
return fmt.Errorf("tool name contains invalid characters: %s", strings.Join(invalidChars, ", "))
}
return nil
}
// validToolNameRune reports whether r is valid within tool names.
func validToolNameRune(r rune) bool {
return (r >= 'a' && r <= 'z') ||
(r >= 'A' && r <= 'Z') ||
(r >= '0' && r <= '9') ||
r == '_' || r == '-' || r == '.'
}