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
949func Items(schema any) PropertyOption {
950	return func(schemaMap map[string]any) {
951		schemaMap["items"] = schema
952	}
953}
954
955// MinItems sets the minimum number of items for an array
956func MinItems(min int) PropertyOption {
957	return func(schema map[string]any) {
958		schema["minItems"] = min
959	}
960}
961
962// MaxItems sets the maximum number of items for an array
963func MaxItems(max int) PropertyOption {
964	return func(schema map[string]any) {
965		schema["maxItems"] = max
966	}
967}
968
969// UniqueItems specifies whether array items must be unique
970func UniqueItems(unique bool) PropertyOption {
971	return func(schema map[string]any) {
972		schema["uniqueItems"] = unique
973	}
974}