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}