Documentation
¶
Overview ¶
Package toolmodel provides a single, canonical data model for tools in the system, based on the MCP Tool specification (2025-11-25). Target protocol version: MCPVersion.
This package is the only place that defines what a "tool" is (fields, constraints, IDs), and all downstream modules depend on it for tool definitions. It provides:
- Tool and ToolIcon types matching the MCP Tool specification
- Namespace and Version extensions for stable tool identification
- Backend binding types (MCP, Provider, Local) for execution metadata
- Optional tool Tags for search/discovery layers
- JSON Schema validation helpers for inputs and outputs
- JSON serialization compatible with the MCP Tool spec
- Tag normalization helpers for discovery layers
The package enforces MCP schema rules by default:
- inputSchema MUST be a valid JSON Schema object
- Schemas may be provided as map[string]any, json.RawMessage, or []byte
- JSON Schema 2020-12 is assumed when no $schema is present
- External $ref resolution is disabled to prevent network access
This package has no networking or JSON-RPC dependencies and is safe to embed in public APIs.
Tool name validation mirrors MCP rules: 1-128 chars, [A-Za-z0-9_.-] only.
Example (BackendBinding) ¶
Example_backendBinding demonstrates creating backend binding information.
package main
import (
"fmt"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
// MCP backend (tool from an MCP server)
mcpBackend := model.ToolBackend{
Kind: model.BackendKindMCP,
MCP: &model.MCPBackend{
ServerName: "filesystem-server",
},
}
fmt.Printf("MCP Backend: kind=%s, server=%s\n", mcpBackend.Kind, mcpBackend.MCP.ServerName)
// Provider backend (external tool provider)
providerBackend := model.ToolBackend{
Kind: model.BackendKindProvider,
Provider: &model.ProviderBackend{
ProviderID: "openai",
ToolID: "gpt-4-vision",
},
}
fmt.Printf("Provider Backend: kind=%s, provider=%s, tool=%s\n",
providerBackend.Kind, providerBackend.Provider.ProviderID, providerBackend.Provider.ToolID)
// Local backend (locally implemented tool)
localBackend := model.ToolBackend{
Kind: model.BackendKindLocal,
Local: &model.LocalBackend{
Name: "custom-handler",
},
}
fmt.Printf("Local Backend: kind=%s, name=%s\n", localBackend.Kind, localBackend.Local.Name)
}
Output: MCP Backend: kind=mcp, server=filesystem-server Provider Backend: kind=provider, provider=openai, tool=gpt-4-vision Local Backend: kind=local, name=custom-handler
Example (CreateTool) ¶
Example_createTool demonstrates creating a Tool with the toolmodel library.
package main
import (
"fmt"
"github.com/jonwraymond/toolfoundation/model"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
// Create a tool that embeds the MCP Tool type
tool := model.Tool{
Tool: mcp.Tool{
Name: "search",
Description: "Search for documents by query",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"query": map[string]any{
"type": "string",
"description": "The search query",
},
"limit": map[string]any{
"type": "integer",
"description": "Maximum number of results",
"default": 10,
},
},
"required": []any{"query"},
},
},
Namespace: "docs",
Version: "1.0.0",
}
fmt.Printf("Tool ID: %s\n", tool.ToolID())
fmt.Printf("Name: %s\n", tool.Name)
fmt.Printf("Description: %s\n", tool.Description)
}
Output: Tool ID: docs:search Name: search Description: Search for documents by query
Example (FromMCPJSON) ¶
Example_fromMCPJSON demonstrates deserializing an MCP Tool from JSON.
package main
import (
"fmt"
"log"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
mcpJSON := `{
"name": "calculate",
"description": "Perform calculations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {"type": "string"}
},
"required": ["expression"]
}
}`
tool, err := model.FromMCPJSON([]byte(mcpJSON))
if err != nil {
log.Fatal(err)
}
fmt.Printf("Name: %s\n", tool.Name)
fmt.Printf("Namespace: %q (empty from MCP JSON)\n", tool.Namespace)
}
Output: Name: calculate Namespace: "" (empty from MCP JSON)
Example (NoParametersTool) ¶
Example_noParametersTool demonstrates creating a tool with no parameters.
package main
import (
"fmt"
"github.com/jonwraymond/toolfoundation/model"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
// MCP recommended schema for tools with no parameters
tool := model.Tool{
Tool: mcp.Tool{
Name: "get_time",
Description: "Get the current time",
InputSchema: map[string]any{
"type": "object",
"additionalProperties": false,
},
},
}
validator := model.NewDefaultValidator()
// Empty object is valid
err := validator.ValidateInput(&tool, map[string]any{})
fmt.Printf("Empty object: valid=%v\n", err == nil)
// Extra properties are rejected
err = validator.ValidateInput(&tool, map[string]any{"unexpected": "value"})
fmt.Printf("Extra properties: rejected=%v\n", err != nil)
}
Output: Empty object: valid=true Extra properties: rejected=true
Example (ParseToolID) ¶
Example_parseToolID demonstrates parsing a tool ID into its components.
package main
import (
"fmt"
"log"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
// Parse a namespaced tool ID
namespace, name, err := model.ParseToolID("filesystem:read")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Namespace: %q, Name: %q\n", namespace, name)
// Parse a simple tool ID (no namespace)
namespace, name, err = model.ParseToolID("echo")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Namespace: %q, Name: %q\n", namespace, name)
}
Output: Namespace: "filesystem", Name: "read" Namespace: "", Name: "echo"
Example (ToolJSON) ¶
Example_toolJSON demonstrates JSON serialization of tools.
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/jonwraymond/toolfoundation/model"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
tool := model.Tool{
Tool: mcp.Tool{
Name: "greet",
Description: "Greet a user",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]any{"type": "string"},
},
},
},
Namespace: "example",
Version: "2.0.0",
}
// ToMCPJSON strips toolmodel extensions (namespace, version)
mcpJSON, _ := tool.ToMCPJSON()
fmt.Println("MCP JSON (no namespace/version):")
var mcpResult map[string]any
if err := json.Unmarshal(mcpJSON, &mcpResult); err != nil {
log.Fatal(err)
}
_, hasNamespace := mcpResult["namespace"]
fmt.Printf(" Has namespace: %v\n", hasNamespace)
// ToJSON includes all fields
fullJSON, _ := tool.ToJSON()
fmt.Println("Full JSON (with namespace/version):")
var fullResult map[string]any
if err := json.Unmarshal(fullJSON, &fullResult); err != nil {
log.Fatal(err)
}
fmt.Printf(" namespace: %v\n", fullResult["namespace"])
fmt.Printf(" version: %v\n", fullResult["version"])
}
Output: MCP JSON (no namespace/version): Has namespace: false Full JSON (with namespace/version): namespace: example version: 2.0.0
Example (ValidateInput) ¶
Example_validateInput demonstrates validating tool input with SchemaValidator.
package main
import (
"fmt"
"github.com/jonwraymond/toolfoundation/model"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
tool := model.Tool{
Tool: mcp.Tool{
Name: "send_email",
Description: "Send an email",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"to": map[string]any{"type": "string"},
"subject": map[string]any{"type": "string"},
"body": map[string]any{"type": "string"},
},
"required": []any{"to", "subject"},
},
},
}
validator := model.NewDefaultValidator()
// Valid input
validArgs := map[string]any{
"to": "user@example.com",
"subject": "Hello",
"body": "Hi there!",
}
err := validator.ValidateInput(&tool, validArgs)
fmt.Printf("Valid input: error=%v\n", err)
// Invalid input (missing required field)
invalidArgs := map[string]any{
"body": "Hi there!",
}
err = validator.ValidateInput(&tool, invalidArgs)
fmt.Printf("Invalid input: error=%v\n", err != nil)
}
Output: Valid input: error=<nil> Invalid input: error=true
Example (ValidateSchema) ¶
Example_validateSchema demonstrates direct schema validation.
package main
import (
"fmt"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
schema := map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]any{"type": "string"},
"age": map[string]any{"type": "integer", "minimum": 0},
},
"required": []any{"name"},
}
validator := model.NewDefaultValidator()
// Valid instance
valid := map[string]any{"name": "Alice", "age": 30}
err := validator.Validate(schema, valid)
fmt.Printf("Valid: %v\n", err == nil)
// Invalid instance (wrong type)
invalid := map[string]any{"name": 123}
err = validator.Validate(schema, invalid)
fmt.Printf("Invalid type: %v\n", err != nil)
// Invalid instance (missing required)
missing := map[string]any{"age": 25}
err = validator.Validate(schema, missing)
fmt.Printf("Missing required: %v\n", err != nil)
}
Output: Valid: true Invalid type: true Missing required: true
Index ¶
Examples ¶
Constants ¶
const ( SchemaDialect202012 = "https://json-schema.org/draft/2020-12/schema" SchemaDialectDraft07 = "http://json-schema.org/draft-07/schema#" SchemaDialectDraft07Alt = "http://json-schema.org/draft-07/schema" )
Supported JSON Schema dialects.
const MCPVersion = "2025-11-25"
MCPVersion is the MCP protocol version this package targets. Keep in sync with the latest MCP spec.
Variables ¶
var ( // ErrUnsupportedSchema is returned when the $schema dialect is not supported. ErrUnsupportedSchema = errors.New("unsupported JSON Schema dialect") // ErrExternalRef is returned when an external $ref is encountered. ErrExternalRef = errors.New("external $ref resolution is disabled") // ErrInvalidSchema is returned when a schema is not a valid JSON Schema object. ErrInvalidSchema = errors.New("invalid JSON Schema") )
Common validation errors.
var ErrInvalidBackend = errors.New("invalid backend")
var ErrInvalidTool = errors.New("invalid tool")
var ErrInvalidToolID = errors.New("invalid tool ID format")
ErrInvalidToolID is returned when a tool ID string is malformed.
Functions ¶
func NormalizeTags ¶
NormalizeTags normalizes a list of tags for indexing/search. Rules: - lowercase - trim whitespace - replace internal whitespace with '-' - allow only [a-z0-9-_.] - dedupe while preserving order - drop empty/invalid tags - max tag length: 64 chars - max tag count: 20
func ParseToolID ¶
ParseToolID parses a tool ID string into namespace and name components. The format is "namespace:name" or just "name" (empty namespace). Returns an error if the ID is empty or contains multiple colons.
Types ¶
type BackendKind ¶
type BackendKind string
BackendKind defines the type of backend backing a tool.
const ( BackendKindMCP BackendKind = "mcp" BackendKindProvider BackendKind = "provider" BackendKindLocal BackendKind = "local" )
type DefaultValidator ¶
type DefaultValidator struct{}
DefaultValidator is the default SchemaValidator implementation using jsonschema-go. It supports JSON Schema 2020-12 (default) and draft-07. External $ref resolution is disabled to prevent network access.
Limitations (from jsonschema-go):
- The "format" keyword is not validated by default (treated as annotation)
- Content-related keywords (contentEncoding, contentMediaType) are not validated
func NewDefaultValidator ¶
func NewDefaultValidator() *DefaultValidator
NewDefaultValidator creates a new DefaultValidator.
func (*DefaultValidator) Validate ¶
func (v *DefaultValidator) Validate(schema any, instance any) error
Validate validates an instance against a JSON Schema.
func (*DefaultValidator) ValidateInput ¶
func (v *DefaultValidator) ValidateInput(tool *Tool, args any) error
ValidateInput validates tool input arguments against the tool's InputSchema.
func (*DefaultValidator) ValidateOutput ¶
func (v *DefaultValidator) ValidateOutput(tool *Tool, result any) error
ValidateOutput validates tool output against the tool's OutputSchema if present. Returns nil if OutputSchema is not defined.
type LocalBackend ¶
type LocalBackend struct {
// Name identifies the local function or handler.
Name string `json:"name"`
}
LocalBackend defines metadata for a locally executed tool.
type MCPBackend ¶
type MCPBackend struct {
// ServerName identifies the MCP server (e.g. in a registry or config).
ServerName string `json:"serverName,omitempty"`
}
MCPBackend defines metadata for an MCP server backend.
type ProviderBackend ¶
ProviderBackend defines metadata for an external/manual tool provider.
type SchemaValidator ¶
type SchemaValidator interface {
// Validate validates an instance against a JSON Schema.
// The schema must be a valid JSON Schema object (map[string]any or *jsonschema.Schema).
// The instance is the data to validate.
// Returns nil if validation passes, otherwise returns a validation error.
Validate(schema any, instance any) error
// ValidateInput validates tool input arguments against a tool's InputSchema.
// Convenience method that extracts InputSchema from Tool.
ValidateInput(tool *Tool, args any) error
// ValidateOutput validates tool output against a tool's OutputSchema if present.
// Returns nil if OutputSchema is not defined.
ValidateOutput(tool *Tool, result any) error
}
SchemaValidator validates JSON instances against JSON Schemas.
Contract:
- Thread-safety: implementations must be safe for concurrent use unless documented otherwise.
- Ownership: implementations must not mutate caller-owned schema objects or instances.
- Errors: validation failures should wrap/return ErrInvalidSchema or ErrUnsupportedSchema where appropriate.
- ValidateInput: must validate against tool.InputSchema; return ErrInvalidSchema if tool is nil or InputSchema is nil.
- ValidateOutput: must validate against tool.OutputSchema when present; return nil when OutputSchema is nil.
- Determinism: repeated calls with same inputs should be deterministic.
Implementations can be swapped to use different validation libraries.
type Tool ¶
type Tool struct {
mcp.Tool
// Namespace provides a way to namespace tools, e.g. for stable IDs.
Namespace string `json:"namespace,omitempty"`
// Version is an optional version string for the tool.
Version string `json:"version,omitempty"`
// Tags is an optional set of search keywords for discovery layers (e.g. toolindex).
Tags []string `json:"tags,omitempty"`
}
Tool mirrors the MCP Tool definition and adds Namespace and Version. It embeds mcp.Tool to ensure compatibility with the official SDK.
func FromJSON ¶
FromJSON deserializes a full Tool JSON (including toolmodel extensions) into a Tool struct.
func FromMCPJSON ¶
FromMCPJSON deserializes an MCP Tool JSON into a Tool struct. The Namespace and Version fields will be empty after this call.
func (*Tool) ToMCPJSON ¶
ToMCPJSON serializes the Tool to JSON that is compatible with the MCP Tool spec. This strips toolmodel-specific fields (Namespace, Version) and returns only the standard MCP Tool fields.
type ToolBackend ¶
type ToolBackend struct {
Kind BackendKind `json:"kind"`
MCP *MCPBackend `json:"mcp,omitempty"`
Provider *ProviderBackend `json:"provider,omitempty"`
Local *LocalBackend `json:"local,omitempty"`
}
ToolBackend defines the binding information for a tool's execution. A tool can have multiple backends, but typically one active one.
func (ToolBackend) Validate ¶
func (b ToolBackend) Validate() error
Validate checks basic invariants of ToolBackend.