1package mcp
   2
   3import (
   4	"encoding/json"
   5	"errors"
   6	"fmt"
   7	"reflect"
   8	"strconv"
   9)
  10
  11var errToolSchemaConflict = errors.New("provide either InputSchema or RawInputSchema, not both")
  12
  13// ListToolsRequest is sent from the client to request a list of tools the
  14// server has.
  15type ListToolsRequest struct {
  16	PaginatedRequest
  17}
  18
  19// ListToolsResult is the server's response to a tools/list request from the
  20// client.
  21type ListToolsResult struct {
  22	PaginatedResult
  23	Tools []Tool `json:"tools"`
  24}
  25
  26// CallToolResult is the server's response to a tool call.
  27//
  28// Any errors that originate from the tool SHOULD be reported inside the result
  29// object, with `isError` set to true, _not_ as an MCP protocol-level error
  30// response. Otherwise, the LLM would not be able to see that an error occurred
  31// and self-correct.
  32//
  33// However, any errors in _finding_ the tool, an error indicating that the
  34// server does not support tool calls, or any other exceptional conditions,
  35// should be reported as an MCP error response.
  36type CallToolResult struct {
  37	Result
  38	Content []Content `json:"content"` // Can be TextContent, ImageContent, AudioContent, or EmbeddedResource
  39	// Whether the tool call ended in an error.
  40	//
  41	// If not set, this is assumed to be false (the call was successful).
  42	IsError bool `json:"isError,omitempty"`
  43}
  44
  45// CallToolRequest is used by the client to invoke a tool provided by the server.
  46type CallToolRequest struct {
  47	Request
  48	Params CallToolParams `json:"params"`
  49}
  50
  51type CallToolParams struct {
  52	Name      string `json:"name"`
  53	Arguments any    `json:"arguments,omitempty"`
  54	Meta      *Meta  `json:"_meta,omitempty"`
  55}
  56
  57// GetArguments returns the Arguments as map[string]any for backward compatibility
  58// If Arguments is not a map, it returns an empty map
  59func (r CallToolRequest) GetArguments() map[string]any {
  60	if args, ok := r.Params.Arguments.(map[string]any); ok {
  61		return args
  62	}
  63	return nil
  64}
  65
  66// GetRawArguments returns the Arguments as-is without type conversion
  67// This allows users to access the raw arguments in any format
  68func (r CallToolRequest) GetRawArguments() any {
  69	return r.Params.Arguments
  70}
  71
  72// BindArguments unmarshals the Arguments into the provided struct
  73// This is useful for working with strongly-typed arguments
  74func (r CallToolRequest) BindArguments(target any) error {
  75	if target == nil || reflect.ValueOf(target).Kind() != reflect.Ptr {
  76		return fmt.Errorf("target must be a non-nil pointer")
  77	}
  78
  79	// Fast-path: already raw JSON
  80	if raw, ok := r.Params.Arguments.(json.RawMessage); ok {
  81		return json.Unmarshal(raw, target)
  82	}
  83
  84	data, err := json.Marshal(r.Params.Arguments)
  85	if err != nil {
  86		return fmt.Errorf("failed to marshal arguments: %w", err)
  87	}
  88
  89	return json.Unmarshal(data, target)
  90}
  91
  92// GetString returns a string argument by key, or the default value if not found
  93func (r CallToolRequest) GetString(key string, defaultValue string) string {
  94	args := r.GetArguments()
  95	if val, ok := args[key]; ok {
  96		if str, ok := val.(string); ok {
  97			return str
  98		}
  99	}
 100	return defaultValue
 101}
 102
 103// RequireString returns a string argument by key, or an error if not found or not a string
 104func (r CallToolRequest) RequireString(key string) (string, error) {
 105	args := r.GetArguments()
 106	if val, ok := args[key]; ok {
 107		if str, ok := val.(string); ok {
 108			return str, nil
 109		}
 110		return "", fmt.Errorf("argument %q is not a string", key)
 111	}
 112	return "", fmt.Errorf("required argument %q not found", key)
 113}
 114
 115// GetInt returns an int argument by key, or the default value if not found
 116func (r CallToolRequest) GetInt(key string, defaultValue int) int {
 117	args := r.GetArguments()
 118	if val, ok := args[key]; ok {
 119		switch v := val.(type) {
 120		case int:
 121			return v
 122		case float64:
 123			return int(v)
 124		case string:
 125			if i, err := strconv.Atoi(v); err == nil {
 126				return i
 127			}
 128		}
 129	}
 130	return defaultValue
 131}
 132
 133// RequireInt returns an int argument by key, or an error if not found or not convertible to int
 134func (r CallToolRequest) RequireInt(key string) (int, error) {
 135	args := r.GetArguments()
 136	if val, ok := args[key]; ok {
 137		switch v := val.(type) {
 138		case int:
 139			return v, nil
 140		case float64:
 141			return int(v), nil
 142		case string:
 143			if i, err := strconv.Atoi(v); err == nil {
 144				return i, nil
 145			}
 146			return 0, fmt.Errorf("argument %q cannot be converted to int", key)
 147		default:
 148			return 0, fmt.Errorf("argument %q is not an int", key)
 149		}
 150	}
 151	return 0, fmt.Errorf("required argument %q not found", key)
 152}
 153
 154// GetFloat returns a float64 argument by key, or the default value if not found
 155func (r CallToolRequest) GetFloat(key string, defaultValue float64) float64 {
 156	args := r.GetArguments()
 157	if val, ok := args[key]; ok {
 158		switch v := val.(type) {
 159		case float64:
 160			return v
 161		case int:
 162			return float64(v)
 163		case string:
 164			if f, err := strconv.ParseFloat(v, 64); err == nil {
 165				return f
 166			}
 167		}
 168	}
 169	return defaultValue
 170}
 171
 172// RequireFloat returns a float64 argument by key, or an error if not found or not convertible to float64
 173func (r CallToolRequest) RequireFloat(key string) (float64, error) {
 174	args := r.GetArguments()
 175	if val, ok := args[key]; ok {
 176		switch v := val.(type) {
 177		case float64:
 178			return v, nil
 179		case int:
 180			return float64(v), nil
 181		case string:
 182			if f, err := strconv.ParseFloat(v, 64); err == nil {
 183				return f, nil
 184			}
 185			return 0, fmt.Errorf("argument %q cannot be converted to float64", key)
 186		default:
 187			return 0, fmt.Errorf("argument %q is not a float64", key)
 188		}
 189	}
 190	return 0, fmt.Errorf("required argument %q not found", key)
 191}
 192
 193// GetBool returns a bool argument by key, or the default value if not found
 194func (r CallToolRequest) GetBool(key string, defaultValue bool) bool {
 195	args := r.GetArguments()
 196	if val, ok := args[key]; ok {
 197		switch v := val.(type) {
 198		case bool:
 199			return v
 200		case string:
 201			if b, err := strconv.ParseBool(v); err == nil {
 202				return b
 203			}
 204		case int:
 205			return v != 0
 206		case float64:
 207			return v != 0
 208		}
 209	}
 210	return defaultValue
 211}
 212
 213// RequireBool returns a bool argument by key, or an error if not found or not convertible to bool
 214func (r CallToolRequest) RequireBool(key string) (bool, error) {
 215	args := r.GetArguments()
 216	if val, ok := args[key]; ok {
 217		switch v := val.(type) {
 218		case bool:
 219			return v, nil
 220		case string:
 221			if b, err := strconv.ParseBool(v); err == nil {
 222				return b, nil
 223			}
 224			return false, fmt.Errorf("argument %q cannot be converted to bool", key)
 225		case int:
 226			return v != 0, nil
 227		case float64:
 228			return v != 0, nil
 229		default:
 230			return false, fmt.Errorf("argument %q is not a bool", key)
 231		}
 232	}
 233	return false, fmt.Errorf("required argument %q not found", key)
 234}
 235
 236// GetStringSlice returns a string slice argument by key, or the default value if not found
 237func (r CallToolRequest) GetStringSlice(key string, defaultValue []string) []string {
 238	args := r.GetArguments()
 239	if val, ok := args[key]; ok {
 240		switch v := val.(type) {
 241		case []string:
 242			return v
 243		case []any:
 244			result := make([]string, 0, len(v))
 245			for _, item := range v {
 246				if str, ok := item.(string); ok {
 247					result = append(result, str)
 248				}
 249			}
 250			return result
 251		}
 252	}
 253	return defaultValue
 254}
 255
 256// RequireStringSlice returns a string slice argument by key, or an error if not found or not convertible to string slice
 257func (r CallToolRequest) RequireStringSlice(key string) ([]string, error) {
 258	args := r.GetArguments()
 259	if val, ok := args[key]; ok {
 260		switch v := val.(type) {
 261		case []string:
 262			return v, nil
 263		case []any:
 264			result := make([]string, 0, len(v))
 265			for i, item := range v {
 266				if str, ok := item.(string); ok {
 267					result = append(result, str)
 268				} else {
 269					return nil, fmt.Errorf("item %d in argument %q is not a string", i, key)
 270				}
 271			}
 272			return result, nil
 273		default:
 274			return nil, fmt.Errorf("argument %q is not a string slice", key)
 275		}
 276	}
 277	return nil, fmt.Errorf("required argument %q not found", key)
 278}
 279
 280// GetIntSlice returns an int slice argument by key, or the default value if not found
 281func (r CallToolRequest) GetIntSlice(key string, defaultValue []int) []int {
 282	args := r.GetArguments()
 283	if val, ok := args[key]; ok {
 284		switch v := val.(type) {
 285		case []int:
 286			return v
 287		case []any:
 288			result := make([]int, 0, len(v))
 289			for _, item := range v {
 290				switch num := item.(type) {
 291				case int:
 292					result = append(result, num)
 293				case float64:
 294					result = append(result, int(num))
 295				case string:
 296					if i, err := strconv.Atoi(num); err == nil {
 297						result = append(result, i)
 298					}
 299				}
 300			}
 301			return result
 302		}
 303	}
 304	return defaultValue
 305}
 306
 307// RequireIntSlice returns an int slice argument by key, or an error if not found or not convertible to int slice
 308func (r CallToolRequest) RequireIntSlice(key string) ([]int, error) {
 309	args := r.GetArguments()
 310	if val, ok := args[key]; ok {
 311		switch v := val.(type) {
 312		case []int:
 313			return v, nil
 314		case []any:
 315			result := make([]int, 0, len(v))
 316			for i, item := range v {
 317				switch num := item.(type) {
 318				case int:
 319					result = append(result, num)
 320				case float64:
 321					result = append(result, int(num))
 322				case string:
 323					if i, err := strconv.Atoi(num); err == nil {
 324						result = append(result, i)
 325					} else {
 326						return nil, fmt.Errorf("item %d in argument %q cannot be converted to int", i, key)
 327					}
 328				default:
 329					return nil, fmt.Errorf("item %d in argument %q is not an int", i, key)
 330				}
 331			}
 332			return result, nil
 333		default:
 334			return nil, fmt.Errorf("argument %q is not an int slice", key)
 335		}
 336	}
 337	return nil, fmt.Errorf("required argument %q not found", key)
 338}
 339
 340// GetFloatSlice returns a float64 slice argument by key, or the default value if not found
 341func (r CallToolRequest) GetFloatSlice(key string, defaultValue []float64) []float64 {
 342	args := r.GetArguments()
 343	if val, ok := args[key]; ok {
 344		switch v := val.(type) {
 345		case []float64:
 346			return v
 347		case []any:
 348			result := make([]float64, 0, len(v))
 349			for _, item := range v {
 350				switch num := item.(type) {
 351				case float64:
 352					result = append(result, num)
 353				case int:
 354					result = append(result, float64(num))
 355				case string:
 356					if f, err := strconv.ParseFloat(num, 64); err == nil {
 357						result = append(result, f)
 358					}
 359				}
 360			}
 361			return result
 362		}
 363	}
 364	return defaultValue
 365}
 366
 367// RequireFloatSlice returns a float64 slice argument by key, or an error if not found or not convertible to float64 slice
 368func (r CallToolRequest) RequireFloatSlice(key string) ([]float64, error) {
 369	args := r.GetArguments()
 370	if val, ok := args[key]; ok {
 371		switch v := val.(type) {
 372		case []float64:
 373			return v, nil
 374		case []any:
 375			result := make([]float64, 0, len(v))
 376			for i, item := range v {
 377				switch num := item.(type) {
 378				case float64:
 379					result = append(result, num)
 380				case int:
 381					result = append(result, float64(num))
 382				case string:
 383					if f, err := strconv.ParseFloat(num, 64); err == nil {
 384						result = append(result, f)
 385					} else {
 386						return nil, fmt.Errorf("item %d in argument %q cannot be converted to float64", i, key)
 387					}
 388				default:
 389					return nil, fmt.Errorf("item %d in argument %q is not a float64", i, key)
 390				}
 391			}
 392			return result, nil
 393		default:
 394			return nil, fmt.Errorf("argument %q is not a float64 slice", key)
 395		}
 396	}
 397	return nil, fmt.Errorf("required argument %q not found", key)
 398}
 399
 400// GetBoolSlice returns a bool slice argument by key, or the default value if not found
 401func (r CallToolRequest) GetBoolSlice(key string, defaultValue []bool) []bool {
 402	args := r.GetArguments()
 403	if val, ok := args[key]; ok {
 404		switch v := val.(type) {
 405		case []bool:
 406			return v
 407		case []any:
 408			result := make([]bool, 0, len(v))
 409			for _, item := range v {
 410				switch b := item.(type) {
 411				case bool:
 412					result = append(result, b)
 413				case string:
 414					if parsed, err := strconv.ParseBool(b); err == nil {
 415						result = append(result, parsed)
 416					}
 417				case int:
 418					result = append(result, b != 0)
 419				case float64:
 420					result = append(result, b != 0)
 421				}
 422			}
 423			return result
 424		}
 425	}
 426	return defaultValue
 427}
 428
 429// RequireBoolSlice returns a bool slice argument by key, or an error if not found or not convertible to bool slice
 430func (r CallToolRequest) RequireBoolSlice(key string) ([]bool, error) {
 431	args := r.GetArguments()
 432	if val, ok := args[key]; ok {
 433		switch v := val.(type) {
 434		case []bool:
 435			return v, nil
 436		case []any:
 437			result := make([]bool, 0, len(v))
 438			for i, item := range v {
 439				switch b := item.(type) {
 440				case bool:
 441					result = append(result, b)
 442				case string:
 443					if parsed, err := strconv.ParseBool(b); err == nil {
 444						result = append(result, parsed)
 445					} else {
 446						return nil, fmt.Errorf("item %d in argument %q cannot be converted to bool", i, key)
 447					}
 448				case int:
 449					result = append(result, b != 0)
 450				case float64:
 451					result = append(result, b != 0)
 452				default:
 453					return nil, fmt.Errorf("item %d in argument %q is not a bool", i, key)
 454				}
 455			}
 456			return result, nil
 457		default:
 458			return nil, fmt.Errorf("argument %q is not a bool slice", key)
 459		}
 460	}
 461	return nil, fmt.Errorf("required argument %q not found", key)
 462}
 463
 464// ToolListChangedNotification is an optional notification from the server to
 465// the client, informing it that the list of tools it offers has changed. This may
 466// be issued by servers without any previous subscription from the client.
 467type ToolListChangedNotification struct {
 468	Notification
 469}
 470
 471// Tool represents the definition for a tool the client can call.
 472type Tool struct {
 473	// The name of the tool.
 474	Name string `json:"name"`
 475	// A human-readable description of the tool.
 476	Description string `json:"description,omitempty"`
 477	// A JSON Schema object defining the expected parameters for the tool.
 478	InputSchema ToolInputSchema `json:"inputSchema"`
 479	// Alternative to InputSchema - allows arbitrary JSON Schema to be provided
 480	RawInputSchema json.RawMessage `json:"-"` // Hide this from JSON marshaling
 481	// Optional properties describing tool behavior
 482	Annotations ToolAnnotation `json:"annotations"`
 483}
 484
 485// GetName returns the name of the tool.
 486func (t Tool) GetName() string {
 487	return t.Name
 488}
 489
 490// MarshalJSON implements the json.Marshaler interface for Tool.
 491// It handles marshaling either InputSchema or RawInputSchema based on which is set.
 492func (t Tool) MarshalJSON() ([]byte, error) {
 493	// Create a map to build the JSON structure
 494	m := make(map[string]any, 3)
 495
 496	// Add the name and description
 497	m["name"] = t.Name
 498	if t.Description != "" {
 499		m["description"] = t.Description
 500	}
 501
 502	// Determine which schema to use
 503	if t.RawInputSchema != nil {
 504		if t.InputSchema.Type != "" {
 505			return nil, fmt.Errorf("tool %s has both InputSchema and RawInputSchema set: %w", t.Name, errToolSchemaConflict)
 506		}
 507		m["inputSchema"] = t.RawInputSchema
 508	} else {
 509		// Use the structured InputSchema
 510		m["inputSchema"] = t.InputSchema
 511	}
 512
 513	m["annotations"] = t.Annotations
 514
 515	return json.Marshal(m)
 516}
 517
 518type ToolInputSchema struct {
 519	Type       string         `json:"type"`
 520	Properties map[string]any `json:"properties,omitempty"`
 521	Required   []string       `json:"required,omitempty"`
 522}
 523
 524// MarshalJSON implements the json.Marshaler interface for ToolInputSchema.
 525func (tis ToolInputSchema) MarshalJSON() ([]byte, error) {
 526	m := make(map[string]any)
 527	m["type"] = tis.Type
 528
 529	// Marshal Properties to '{}' rather than `nil` when its length equals zero
 530	if tis.Properties != nil {
 531		m["properties"] = tis.Properties
 532	}
 533
 534	if len(tis.Required) > 0 {
 535		m["required"] = tis.Required
 536	}
 537
 538	return json.Marshal(m)
 539}
 540
 541type ToolAnnotation struct {
 542	// Human-readable title for the tool
 543	Title string `json:"title,omitempty"`
 544	// If true, the tool does not modify its environment
 545	ReadOnlyHint *bool `json:"readOnlyHint,omitempty"`
 546	// If true, the tool may perform destructive updates
 547	DestructiveHint *bool `json:"destructiveHint,omitempty"`
 548	// If true, repeated calls with same args have no additional effect
 549	IdempotentHint *bool `json:"idempotentHint,omitempty"`
 550	// If true, tool interacts with external entities
 551	OpenWorldHint *bool `json:"openWorldHint,omitempty"`
 552}
 553
 554// ToolOption is a function that configures a Tool.
 555// It provides a flexible way to set various properties of a Tool using the functional options pattern.
 556type ToolOption func(*Tool)
 557
 558// PropertyOption is a function that configures a property in a Tool's input schema.
 559// It allows for flexible configuration of JSON Schema properties using the functional options pattern.
 560type PropertyOption func(map[string]any)
 561
 562//
 563// Core Tool Functions
 564//
 565
 566// NewTool creates a new Tool with the given name and options.
 567// The tool will have an object-type input schema with configurable properties.
 568// Options are applied in order, allowing for flexible tool configuration.
 569func NewTool(name string, opts ...ToolOption) Tool {
 570	tool := Tool{
 571		Name: name,
 572		InputSchema: ToolInputSchema{
 573			Type:       "object",
 574			Properties: make(map[string]any),
 575			Required:   nil, // Will be omitted from JSON if empty
 576		},
 577		Annotations: ToolAnnotation{
 578			Title:           "",
 579			ReadOnlyHint:    ToBoolPtr(false),
 580			DestructiveHint: ToBoolPtr(true),
 581			IdempotentHint:  ToBoolPtr(false),
 582			OpenWorldHint:   ToBoolPtr(true),
 583		},
 584	}
 585
 586	for _, opt := range opts {
 587		opt(&tool)
 588	}
 589
 590	return tool
 591}
 592
 593// NewToolWithRawSchema creates a new Tool with the given name and a raw JSON
 594// Schema. This allows for arbitrary JSON Schema to be used for the tool's input
 595// schema.
 596//
 597// NOTE a [Tool] built in such a way is incompatible with the [ToolOption] and
 598// runtime errors will result from supplying a [ToolOption] to a [Tool] built
 599// with this function.
 600func NewToolWithRawSchema(name, description string, schema json.RawMessage) Tool {
 601	tool := Tool{
 602		Name:           name,
 603		Description:    description,
 604		RawInputSchema: schema,
 605	}
 606
 607	return tool
 608}
 609
 610// WithDescription adds a description to the Tool.
 611// The description should provide a clear, human-readable explanation of what the tool does.
 612func WithDescription(description string) ToolOption {
 613	return func(t *Tool) {
 614		t.Description = description
 615	}
 616}
 617
 618// WithToolAnnotation adds optional hints about the Tool.
 619func WithToolAnnotation(annotation ToolAnnotation) ToolOption {
 620	return func(t *Tool) {
 621		t.Annotations = annotation
 622	}
 623}
 624
 625// WithTitleAnnotation sets the Title field of the Tool's Annotations.
 626// It provides a human-readable title for the tool.
 627func WithTitleAnnotation(title string) ToolOption {
 628	return func(t *Tool) {
 629		t.Annotations.Title = title
 630	}
 631}
 632
 633// WithReadOnlyHintAnnotation sets the ReadOnlyHint field of the Tool's Annotations.
 634// If true, it indicates the tool does not modify its environment.
 635func WithReadOnlyHintAnnotation(value bool) ToolOption {
 636	return func(t *Tool) {
 637		t.Annotations.ReadOnlyHint = &value
 638	}
 639}
 640
 641// WithDestructiveHintAnnotation sets the DestructiveHint field of the Tool's Annotations.
 642// If true, it indicates the tool may perform destructive updates.
 643func WithDestructiveHintAnnotation(value bool) ToolOption {
 644	return func(t *Tool) {
 645		t.Annotations.DestructiveHint = &value
 646	}
 647}
 648
 649// WithIdempotentHintAnnotation sets the IdempotentHint field of the Tool's Annotations.
 650// If true, it indicates repeated calls with the same arguments have no additional effect.
 651func WithIdempotentHintAnnotation(value bool) ToolOption {
 652	return func(t *Tool) {
 653		t.Annotations.IdempotentHint = &value
 654	}
 655}
 656
 657// WithOpenWorldHintAnnotation sets the OpenWorldHint field of the Tool's Annotations.
 658// If true, it indicates the tool interacts with external entities.
 659func WithOpenWorldHintAnnotation(value bool) ToolOption {
 660	return func(t *Tool) {
 661		t.Annotations.OpenWorldHint = &value
 662	}
 663}
 664
 665//
 666// Common Property Options
 667//
 668
 669// Description adds a description to a property in the JSON Schema.
 670// The description should explain the purpose and expected values of the property.
 671func Description(desc string) PropertyOption {
 672	return func(schema map[string]any) {
 673		schema["description"] = desc
 674	}
 675}
 676
 677// Required marks a property as required in the tool's input schema.
 678// Required properties must be provided when using the tool.
 679func Required() PropertyOption {
 680	return func(schema map[string]any) {
 681		schema["required"] = true
 682	}
 683}
 684
 685// Title adds a display-friendly title to a property in the JSON Schema.
 686// This title can be used by UI components to show a more readable property name.
 687func Title(title string) PropertyOption {
 688	return func(schema map[string]any) {
 689		schema["title"] = title
 690	}
 691}
 692
 693//
 694// String Property Options
 695//
 696
 697// DefaultString sets the default value for a string property.
 698// This value will be used if the property is not explicitly provided.
 699func DefaultString(value string) PropertyOption {
 700	return func(schema map[string]any) {
 701		schema["default"] = value
 702	}
 703}
 704
 705// Enum specifies a list of allowed values for a string property.
 706// The property value must be one of the specified enum values.
 707func Enum(values ...string) PropertyOption {
 708	return func(schema map[string]any) {
 709		schema["enum"] = values
 710	}
 711}
 712
 713// MaxLength sets the maximum length for a string property.
 714// The string value must not exceed this length.
 715func MaxLength(max int) PropertyOption {
 716	return func(schema map[string]any) {
 717		schema["maxLength"] = max
 718	}
 719}
 720
 721// MinLength sets the minimum length for a string property.
 722// The string value must be at least this length.
 723func MinLength(min int) PropertyOption {
 724	return func(schema map[string]any) {
 725		schema["minLength"] = min
 726	}
 727}
 728
 729// Pattern sets a regex pattern that a string property must match.
 730// The string value must conform to the specified regular expression.
 731func Pattern(pattern string) PropertyOption {
 732	return func(schema map[string]any) {
 733		schema["pattern"] = pattern
 734	}
 735}
 736
 737//
 738// Number Property Options
 739//
 740
 741// DefaultNumber sets the default value for a number property.
 742// This value will be used if the property is not explicitly provided.
 743func DefaultNumber(value float64) PropertyOption {
 744	return func(schema map[string]any) {
 745		schema["default"] = value
 746	}
 747}
 748
 749// Max sets the maximum value for a number property.
 750// The number value must not exceed this maximum.
 751func Max(max float64) PropertyOption {
 752	return func(schema map[string]any) {
 753		schema["maximum"] = max
 754	}
 755}
 756
 757// Min sets the minimum value for a number property.
 758// The number value must not be less than this minimum.
 759func Min(min float64) PropertyOption {
 760	return func(schema map[string]any) {
 761		schema["minimum"] = min
 762	}
 763}
 764
 765// MultipleOf specifies that a number must be a multiple of the given value.
 766// The number value must be divisible by this value.
 767func MultipleOf(value float64) PropertyOption {
 768	return func(schema map[string]any) {
 769		schema["multipleOf"] = value
 770	}
 771}
 772
 773//
 774// Boolean Property Options
 775//
 776
 777// DefaultBool sets the default value for a boolean property.
 778// This value will be used if the property is not explicitly provided.
 779func DefaultBool(value bool) PropertyOption {
 780	return func(schema map[string]any) {
 781		schema["default"] = value
 782	}
 783}
 784
 785//
 786// Array Property Options
 787//
 788
 789// DefaultArray sets the default value for an array property.
 790// This value will be used if the property is not explicitly provided.
 791func DefaultArray[T any](value []T) PropertyOption {
 792	return func(schema map[string]any) {
 793		schema["default"] = value
 794	}
 795}
 796
 797//
 798// Property Type Helpers
 799//
 800
 801// WithBoolean adds a boolean property to the tool schema.
 802// It accepts property options to configure the boolean property's behavior and constraints.
 803func WithBoolean(name string, opts ...PropertyOption) ToolOption {
 804	return func(t *Tool) {
 805		schema := map[string]any{
 806			"type": "boolean",
 807		}
 808
 809		for _, opt := range opts {
 810			opt(schema)
 811		}
 812
 813		// Remove required from property schema and add to InputSchema.required
 814		if required, ok := schema["required"].(bool); ok && required {
 815			delete(schema, "required")
 816			t.InputSchema.Required = append(t.InputSchema.Required, name)
 817		}
 818
 819		t.InputSchema.Properties[name] = schema
 820	}
 821}
 822
 823// WithNumber adds a number property to the tool schema.
 824// It accepts property options to configure the number property's behavior and constraints.
 825func WithNumber(name string, opts ...PropertyOption) ToolOption {
 826	return func(t *Tool) {
 827		schema := map[string]any{
 828			"type": "number",
 829		}
 830
 831		for _, opt := range opts {
 832			opt(schema)
 833		}
 834
 835		// Remove required from property schema and add to InputSchema.required
 836		if required, ok := schema["required"].(bool); ok && required {
 837			delete(schema, "required")
 838			t.InputSchema.Required = append(t.InputSchema.Required, name)
 839		}
 840
 841		t.InputSchema.Properties[name] = schema
 842	}
 843}
 844
 845// WithString adds a string property to the tool schema.
 846// It accepts property options to configure the string property's behavior and constraints.
 847func WithString(name string, opts ...PropertyOption) ToolOption {
 848	return func(t *Tool) {
 849		schema := map[string]any{
 850			"type": "string",
 851		}
 852
 853		for _, opt := range opts {
 854			opt(schema)
 855		}
 856
 857		// Remove required from property schema and add to InputSchema.required
 858		if required, ok := schema["required"].(bool); ok && required {
 859			delete(schema, "required")
 860			t.InputSchema.Required = append(t.InputSchema.Required, name)
 861		}
 862
 863		t.InputSchema.Properties[name] = schema
 864	}
 865}
 866
 867// WithObject adds an object property to the tool schema.
 868// It accepts property options to configure the object property's behavior and constraints.
 869func WithObject(name string, opts ...PropertyOption) ToolOption {
 870	return func(t *Tool) {
 871		schema := map[string]any{
 872			"type":       "object",
 873			"properties": map[string]any{},
 874		}
 875
 876		for _, opt := range opts {
 877			opt(schema)
 878		}
 879
 880		// Remove required from property schema and add to InputSchema.required
 881		if required, ok := schema["required"].(bool); ok && required {
 882			delete(schema, "required")
 883			t.InputSchema.Required = append(t.InputSchema.Required, name)
 884		}
 885
 886		t.InputSchema.Properties[name] = schema
 887	}
 888}
 889
 890// WithArray adds an array property to the tool schema.
 891// It accepts property options to configure the array property's behavior and constraints.
 892func WithArray(name string, opts ...PropertyOption) ToolOption {
 893	return func(t *Tool) {
 894		schema := map[string]any{
 895			"type": "array",
 896		}
 897
 898		for _, opt := range opts {
 899			opt(schema)
 900		}
 901
 902		// Remove required from property schema and add to InputSchema.required
 903		if required, ok := schema["required"].(bool); ok && required {
 904			delete(schema, "required")
 905			t.InputSchema.Required = append(t.InputSchema.Required, name)
 906		}
 907
 908		t.InputSchema.Properties[name] = schema
 909	}
 910}
 911
 912// Properties defines the properties for an object schema
 913func Properties(props map[string]any) PropertyOption {
 914	return func(schema map[string]any) {
 915		schema["properties"] = props
 916	}
 917}
 918
 919// AdditionalProperties specifies whether additional properties are allowed in the object
 920// or defines a schema for additional properties
 921func AdditionalProperties(schema any) PropertyOption {
 922	return func(schemaMap map[string]any) {
 923		schemaMap["additionalProperties"] = schema
 924	}
 925}
 926
 927// MinProperties sets the minimum number of properties for an object
 928func MinProperties(min int) PropertyOption {
 929	return func(schema map[string]any) {
 930		schema["minProperties"] = min
 931	}
 932}
 933
 934// MaxProperties sets the maximum number of properties for an object
 935func MaxProperties(max int) PropertyOption {
 936	return func(schema map[string]any) {
 937		schema["maxProperties"] = max
 938	}
 939}
 940
 941// PropertyNames defines a schema for property names in an object
 942func PropertyNames(schema map[string]any) PropertyOption {
 943	return func(schemaMap map[string]any) {
 944		schemaMap["propertyNames"] = schema
 945	}
 946}
 947
 948// Items defines the schema for array items.
 949// Accepts any schema definition for maximum flexibility.
 950//
 951// Example:
 952//
 953//	Items(map[string]any{
 954//	    "type": "object",
 955//	    "properties": map[string]any{
 956//	        "name": map[string]any{"type": "string"},
 957//	        "age": map[string]any{"type": "number"},
 958//	    },
 959//	})
 960//
 961// For simple types, use ItemsString(), ItemsNumber(), ItemsBoolean() instead.
 962func Items(schema any) PropertyOption {
 963	return func(schemaMap map[string]any) {
 964		schemaMap["items"] = schema
 965	}
 966}
 967
 968// MinItems sets the minimum number of items for an array
 969func MinItems(min int) PropertyOption {
 970	return func(schema map[string]any) {
 971		schema["minItems"] = min
 972	}
 973}
 974
 975// MaxItems sets the maximum number of items for an array
 976func MaxItems(max int) PropertyOption {
 977	return func(schema map[string]any) {
 978		schema["maxItems"] = max
 979	}
 980}
 981
 982// UniqueItems specifies whether array items must be unique
 983func UniqueItems(unique bool) PropertyOption {
 984	return func(schema map[string]any) {
 985		schema["uniqueItems"] = unique
 986	}
 987}
 988
 989// WithStringItems configures an array's items to be of type string.
 990//
 991// Supported options: Description(), DefaultString(), Enum(), MaxLength(), MinLength(), Pattern()
 992// Note: Options like Required() are not valid for item schemas and will be ignored.
 993//
 994// Examples:
 995//
 996//	mcp.WithArray("tags", mcp.WithStringItems())
 997//	mcp.WithArray("colors", mcp.WithStringItems(mcp.Enum("red", "green", "blue")))
 998//	mcp.WithArray("names", mcp.WithStringItems(mcp.MinLength(1), mcp.MaxLength(50)))
 999//
1000// Limitations: Only supports simple string arrays. Use Items() for complex objects.
1001func WithStringItems(opts ...PropertyOption) PropertyOption {
1002	return func(schema map[string]any) {
1003		itemSchema := map[string]any{
1004			"type": "string",
1005		}
1006
1007		for _, opt := range opts {
1008			opt(itemSchema)
1009		}
1010
1011		schema["items"] = itemSchema
1012	}
1013}
1014
1015// WithStringEnumItems configures an array's items to be of type string with a specified enum.
1016// Example:
1017//
1018//	mcp.WithArray("priority", mcp.WithStringEnumItems([]string{"low", "medium", "high"}))
1019//
1020// Limitations: Only supports string enums. Use WithStringItems(Enum(...)) for more flexibility.
1021func WithStringEnumItems(values []string) PropertyOption {
1022	return func(schema map[string]any) {
1023		schema["items"] = map[string]any{
1024			"type": "string",
1025			"enum": values,
1026		}
1027	}
1028}
1029
1030// WithNumberItems configures an array's items to be of type number.
1031//
1032// Supported options: Description(), DefaultNumber(), Min(), Max(), MultipleOf()
1033// Note: Options like Required() are not valid for item schemas and will be ignored.
1034//
1035// Examples:
1036//
1037//	mcp.WithArray("scores", mcp.WithNumberItems(mcp.Min(0), mcp.Max(100)))
1038//	mcp.WithArray("prices", mcp.WithNumberItems(mcp.Min(0)))
1039//
1040// Limitations: Only supports simple number arrays. Use Items() for complex objects.
1041func WithNumberItems(opts ...PropertyOption) PropertyOption {
1042	return func(schema map[string]any) {
1043		itemSchema := map[string]any{
1044			"type": "number",
1045		}
1046
1047		for _, opt := range opts {
1048			opt(itemSchema)
1049		}
1050
1051		schema["items"] = itemSchema
1052	}
1053}
1054
1055// WithBooleanItems configures an array's items to be of type boolean.
1056//
1057// Supported options: Description(), DefaultBool()
1058// Note: Options like Required() are not valid for item schemas and will be ignored.
1059//
1060// Examples:
1061//
1062//	mcp.WithArray("flags", mcp.WithBooleanItems())
1063//	mcp.WithArray("permissions", mcp.WithBooleanItems(mcp.Description("User permissions")))
1064//
1065// Limitations: Only supports simple boolean arrays. Use Items() for complex objects.
1066func WithBooleanItems(opts ...PropertyOption) PropertyOption {
1067	return func(schema map[string]any) {
1068		itemSchema := map[string]any{
1069			"type": "boolean",
1070		}
1071
1072		for _, opt := range opts {
1073			opt(itemSchema)
1074		}
1075
1076		schema["items"] = itemSchema
1077	}
1078}