mirror of
https://github.com/therootcompany/golib.git
synced 2026-11-04 06:55:00 +00:00
270 lines
7.8 KiB
Go
270 lines
7.8 KiB
Go
package mcptypes
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tool definition
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Tool describes an MCP tool with its name, description, input schema, and hints.
|
|
//
|
|
// Differences from mcp-go:
|
|
// - Annotations is a pointer (omitted when nil); mcp-go uses a value (always serialized).
|
|
// - Omits: RawInputSchema, OutputSchema, RawOutputSchema, DeferLoading, Icons, Execution.
|
|
// These are unused; add them when needed.
|
|
//
|
|
// We intentionally reject complex runtime schema validation here. The schema
|
|
// is typed instead, and handlers validate and decode their arguments using Go
|
|
// types and the accessors on CallToolRequest.
|
|
type Tool struct {
|
|
Meta *Meta `json:"_meta,omitempty"`
|
|
Name string `json:"name"`
|
|
Title string `json:"title,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
InputSchema ToolInputSchema `json:"inputSchema"`
|
|
Annotations *ToolAnnotation `json:"annotations,omitempty"`
|
|
}
|
|
|
|
// Property describes a single property in a tool's input schema.
|
|
type Property struct {
|
|
Type string `json:"type"`
|
|
Description string `json:"description,omitempty"`
|
|
Enum []string `json:"enum,omitempty"`
|
|
Default string `json:"default,omitempty"`
|
|
Items *ToolInputSchema `json:"items,omitempty"`
|
|
}
|
|
|
|
// ToolInputSchema is the JSON Schema advertised for tool input. It covers the
|
|
// simple typed schemas supported by this package; handlers enforce semantics.
|
|
type ToolInputSchema struct {
|
|
Type string `json:"type"`
|
|
Properties map[string]Property `json:"properties,omitempty"`
|
|
Required []string `json:"required,omitempty"`
|
|
}
|
|
|
|
// ToolAnnotation contains behavioral hints for clients.
|
|
type ToolAnnotation struct {
|
|
Title string `json:"title,omitempty"`
|
|
ReadOnlyHint *bool `json:"readOnlyHint,omitempty"`
|
|
DestructiveHint *bool `json:"destructiveHint,omitempty"`
|
|
IdempotentHint *bool `json:"idempotentHint,omitempty"`
|
|
OpenWorldHint *bool `json:"openWorldHint,omitempty"`
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tool call request
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// CallToolRequest is the request object passed to tool handlers.
|
|
type CallToolRequest struct {
|
|
Method string
|
|
Params CallToolParams
|
|
}
|
|
|
|
// CallToolParams contains the tool call parameters.
|
|
type CallToolParams struct {
|
|
Name string `json:"name"`
|
|
Arguments map[string]any `json:"arguments,omitempty"`
|
|
}
|
|
|
|
// GetArguments returns the raw arguments map.
|
|
func (r CallToolRequest) GetArguments() map[string]any {
|
|
return r.Params.Arguments
|
|
}
|
|
|
|
// GetString returns a string argument or the default value.
|
|
func (r CallToolRequest) GetString(key, defaultValue string) string {
|
|
v, ok := r.Params.Arguments[key]
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
s, ok := v.(string)
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
return s
|
|
}
|
|
|
|
// GetInt returns an integer argument or the default value.
|
|
func (r CallToolRequest) GetInt(key string, defaultValue int) int {
|
|
v, ok := r.Params.Arguments[key]
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return int(n)
|
|
case json.Number:
|
|
i, err := n.Int64()
|
|
if err != nil {
|
|
return defaultValue
|
|
}
|
|
return int(i)
|
|
case int:
|
|
return n
|
|
case int64:
|
|
return int(n)
|
|
default:
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
// GetFloat returns a float argument or the default value.
|
|
func (r CallToolRequest) GetFloat(key string, defaultValue float64) float64 {
|
|
v, ok := r.Params.Arguments[key]
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return n
|
|
case json.Number:
|
|
f, err := n.Float64()
|
|
if err != nil {
|
|
return defaultValue
|
|
}
|
|
return f
|
|
default:
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
// GetBool returns a boolean argument or the default value.
|
|
func (r CallToolRequest) GetBool(key string, defaultValue bool) bool {
|
|
v, ok := r.Params.Arguments[key]
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
b, ok := v.(bool)
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
return b
|
|
}
|
|
|
|
// RequireString returns the string argument or an error if missing.
|
|
func (r CallToolRequest) RequireString(key string) (string, error) {
|
|
v, ok := r.Params.Arguments[key]
|
|
if !ok {
|
|
return "", fmt.Errorf("missing required argument: %s", key)
|
|
}
|
|
s, ok := v.(string)
|
|
if !ok {
|
|
return "", fmt.Errorf("argument %s must be a string", key)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// RequireInt returns the integer argument or an error if missing.
|
|
func (r CallToolRequest) RequireInt(key string) (int, error) {
|
|
v, ok := r.Params.Arguments[key]
|
|
if !ok {
|
|
return 0, fmt.Errorf("missing required argument: %s", key)
|
|
}
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return int(n), nil
|
|
case json.Number:
|
|
i, err := n.Int64()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("argument %s must be an integer", key)
|
|
}
|
|
return int(i), nil
|
|
default:
|
|
return 0, fmt.Errorf("argument %s must be a number", key)
|
|
}
|
|
}
|
|
|
|
// BindArguments unmarshals arguments into the target struct.
|
|
func (r CallToolRequest) BindArguments(target any) error {
|
|
data, err := json.Marshal(r.Params.Arguments)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(data, target)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tool call result
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// CallToolResult is the response from a tool handler.
|
|
type CallToolResult struct {
|
|
Meta *Meta `json:"-"` // marshaled manually; see MarshalJSON
|
|
Content []Content `json:"-"`
|
|
StructuredContent any `json:"-"`
|
|
IsError bool `json:"-"`
|
|
}
|
|
|
|
// MarshalJSON writes only non-empty fields and omits _meta when it carries no
|
|
// data — matching the wire format produced by mcp-go.
|
|
func (r CallToolResult) MarshalJSON() ([]byte, error) {
|
|
m := make(map[string]any, 4)
|
|
if r.Meta != nil && !r.Meta.IsEmpty() {
|
|
m["_meta"] = r.Meta
|
|
}
|
|
m["content"] = r.Content
|
|
if r.StructuredContent != nil {
|
|
m["structuredContent"] = r.StructuredContent
|
|
}
|
|
if r.IsError {
|
|
m["isError"] = r.IsError
|
|
}
|
|
return json.Marshal(m)
|
|
}
|
|
|
|
// NewToolResultText creates a result with a single text content.
|
|
func NewToolResultText(text string) *CallToolResult {
|
|
return &CallToolResult{
|
|
Content: []Content{TextContent{Type: "text", Text: text}},
|
|
}
|
|
}
|
|
|
|
// NewToolResultError creates an error result with text content.
|
|
func NewToolResultError(message string) *CallToolResult {
|
|
return &CallToolResult{
|
|
Content: []Content{TextContent{Type: "text", Text: message}},
|
|
IsError: true,
|
|
}
|
|
}
|
|
|
|
// NewToolResultJSON creates a result with JSON-serialized structured content and text.
|
|
func NewToolResultJSON(data any) (*CallToolResult, error) {
|
|
textBytes, err := json.Marshal(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &CallToolResult{
|
|
Content: []Content{TextContent{Type: "text", Text: string(textBytes)}},
|
|
StructuredContent: data,
|
|
}, nil
|
|
}
|
|
|
|
// NewToolResultStructured creates a result with both structured and text content.
|
|
func NewToolResultStructured(structured any, text string) *CallToolResult {
|
|
return &CallToolResult{
|
|
Content: []Content{TextContent{Type: "text", Text: text}},
|
|
StructuredContent: structured,
|
|
}
|
|
}
|
|
|
|
// NewToolResultStructuredOnly creates a result with structured content and a
|
|
// JSON text fallback for backward compatibility with clients that don't
|
|
// support structuredContent.
|
|
func NewToolResultStructuredOnly(structured any) *CallToolResult {
|
|
var fallbackText string
|
|
jsonBytes, err := json.Marshal(structured)
|
|
if err != nil {
|
|
fallbackText = fmt.Sprintf("Error serializing structured content: %v", err)
|
|
} else {
|
|
fallbackText = string(jsonBytes)
|
|
}
|
|
return &CallToolResult{
|
|
Content: []Content{TextContent{Type: "text", Text: fallbackText}},
|
|
StructuredContent: structured,
|
|
}
|
|
}
|