1package mcp23import (4 "encoding/json"5 "reflect"67 "github.com/google/jsonschema-go/jsonschema"89 "src.rest/cat/012/internal/headless"10)1112// The schema of a cell's value (headless.Value), which the tools that13// write take and the tools that read return, so a model sees in the14// tools' schemas how to write money, percentages, dates, times,15// durations and sizes, and what it reads back.1617// valueDescription says what a value is.18const valueDescription = `A cell's value with its type: a number, a string (always text, even "00123"), true, false, null (blank), ` +19 `or an object naming its type: {"currency": 3.5} ($3.50; "symbol": "€" for another currency), {"percent": 0.12} (12%), ` +20 `{"date": "2026-09-29"} or {"date": "2026-09-29T14:30:00"}, {"time": "14:30"}, {"duration": "90min"} (nushell's units, or seconds), ` +21 `{"size": 1500} (bytes, or "1.5kb"), {"number": 1234.5}, {"text": "00123"}. ` +22 `Any object may add "decimals" (2) or "format", a number format code ("#,##0.00", "yyyy-mm-dd").`2324// valueSchema is headless.Value's schema.25func valueSchema() *jsonschema.Schema {26 str := &jsonschema.Schema{Type: "string"}27 num := &jsonschema.Schema{Type: "number"}28 numOrStr := &jsonschema.Schema{Types: []string{"number", "string"}}29 typed := func(key string, v *jsonschema.Schema) *jsonschema.Schema {30 return &jsonschema.Schema{Type: "object", Required: []string{key}, Properties: map[string]*jsonschema.Schema{31 key: v, "decimals": {Type: "integer", Minimum: ptr(0.0), Maximum: ptr(15.0)}, "format": str, "symbol": str,32 }}33 }34 return &jsonschema.Schema{Description: valueDescription, AnyOf: []*jsonschema.Schema{35 {Types: []string{"number", "string", "boolean", "null"}},36 typed("currency", num), typed("percent", num), typed("date", str), typed("time", str),37 typed("duration", numOrStr), typed("size", numOrStr), typed("number", num), typed("text", str),38 }}39}4041// schemaTypes infer json.RawMessage, a value already in JSON, as any42// value, and a cell's value as valueSchema says.43var schemaTypes = map[reflect.Type]*jsonschema.Schema{44 reflect.TypeFor[json.RawMessage](): {},45 reflect.TypeFor[headless.Value](): valueSchema(),46 reflect.TypeFor[headless.Row](): {Description: "A row: its columns' values by name, or a list of them in columns' order, each a cell's value with its type",47 AnyOf: []*jsonschema.Schema{{Type: "object", AdditionalProperties: valueSchema()}, {Type: "array", Items: valueSchema()}}},48}4950// rawSchemas are the options inferring the tools' schemas.51var rawSchemas = &jsonschema.ForOptions{TypeSchemas: schemaTypes}