package mcp import ( "context" "encoding/json" "fmt" ) // Tool is one MCP tool: its advertised schema plus the Invoke func that parses // arguments, does the work, and returns a JSON-encodable payload. Invoke's error // is surfaced to the client as a tool error (isError: true), not a protocol // error. The context carries request scope (deadline, cancellation, trace span); // it flows from the HTTP request through to Invoke. type Tool struct { Name string `json:"name"` Description string `json:"description"` InputSchema json.RawMessage `json:"inputSchema"` Invoke func(ctx context.Context, args json.RawMessage) (any, error) `json:"-"` } // Text wraps a payload as a successful tools/call result: one JSON text block. func Text(v any) any { b, err := json.Marshal(v) if err != nil { return ErrorResult(err) } return map[string]any{ "content": []map[string]any{{"type": "text", "text": string(b)}}, } } // ErrorResult wraps an operational error as an isError tools/call result (a tool // failure the model can read, not a JSON-RPC protocol error). func ErrorResult(err error) any { return map[string]any{ "isError": true, "content": []map[string]any{{"type": "text", "text": err.Error()}}, } } // Decode unmarshals tool arguments into v, treating empty/null args as a no-op // (zero value). Returns a wrapped error suitable for returning from Invoke. func Decode(args json.RawMessage, v any) error { if len(args) == 0 || string(args) == "null" { return nil } if err := json.Unmarshal(args, v); err != nil { return fmt.Errorf("invalid arguments: %w", err) } return nil }