tools.go

  1package mcp
  2
  3import (
  4	"encoding/json"
  5	"errors"
  6	"fmt"
  7)
  8
  9var errToolSchemaConflict = errors.New("provide either InputSchema or RawInputSchema, not both")
 10
 11// ListToolsRequest is sent from the client to request a list of tools the
 12// server has.
 13type ListToolsRequest struct {
 14	PaginatedRequest
 15}
 16
 17// ListToolsResult is the server's response to a tools/list request from the
 18// client.
 19type ListToolsResult struct {
 20	PaginatedResult
 21	Tools []Tool `json:"tools"`
 22}
 23
 24// CallToolResult is the server's response to a tool call.
 25//
 26// Any errors that originate from the tool SHOULD be reported inside the result
 27// object, with `isError` set to true, _not_ as an MCP protocol-level error
 28// response. Otherwise, the LLM would not be able to see that an error occurred
 29// and self-correct.
 30//
 31// However, any errors in _finding_ the tool, an error indicating that the
 32// server does not support tool calls, or any other exceptional conditions,
 33// should be reported as an MCP error response.
 34type CallToolResult struct {
 35	Result
 36	Content []Content `json:"content"` // Can be TextContent, ImageContent, or      EmbeddedResource
 37	// Whether the tool call ended in an error.
 38	//
 39	// If not set, this is assumed to be false (the call was successful).
 40	IsError bool `json:"isError,omitempty"`
 41}
 42
 43// CallToolRequest is used by the client to invoke a tool provided by the server.
 44type CallToolRequest struct {
 45	Request
 46	Params struct {
 47		Name      string                 `json:"name"`
 48		Arguments map[string]interface{} `json:"arguments,omitempty"`
 49		Meta      *struct {
 50			// If specified, the caller is requesting out-of-band progress
 51			// notifications for this request (as represented by
 52			// notifications/progress). The value of this parameter is an
 53			// opaque token that will be attached to any subsequent
 54			// notifications. The receiver is not obligated to provide these
 55			// notifications.
 56			ProgressToken ProgressToken `json:"progressToken,omitempty"`
 57		} `json:"_meta,omitempty"`
 58	} `json:"params"`
 59}
 60
 61// ToolListChangedNotification is an optional notification from the server to
 62// the client, informing it that the list of tools it offers has changed. This may
 63// be issued by servers without any previous subscription from the client.
 64type ToolListChangedNotification struct {
 65	Notification
 66}
 67
 68// Tool represents the definition for a tool the client can call.
 69type Tool struct {
 70	// The name of the tool.
 71	Name string `json:"name"`
 72	// A human-readable description of the tool.
 73	Description string `json:"description,omitempty"`
 74	// A JSON Schema object defining the expected parameters for the tool.
 75	InputSchema ToolInputSchema `json:"inputSchema"`
 76	// Alternative to InputSchema - allows arbitrary JSON Schema to be provided
 77	RawInputSchema json.RawMessage `json:"-"` // Hide this from JSON marshaling
 78}
 79
 80// MarshalJSON implements the json.Marshaler interface for Tool.
 81// It handles marshaling either InputSchema or RawInputSchema based on which is set.
 82func (t Tool) MarshalJSON() ([]byte, error) {
 83	// Create a map to build the JSON structure
 84	m := make(map[string]interface{}, 3)
 85
 86	// Add the name and description
 87	m["name"] = t.Name
 88	if t.Description != "" {
 89		m["description"] = t.Description
 90	}
 91
 92	// Determine which schema to use
 93	if t.RawInputSchema != nil {
 94		if t.InputSchema.Type != "" {
 95			return nil, fmt.Errorf("tool %s has both InputSchema and RawInputSchema set: %w", t.Name, errToolSchemaConflict)
 96		}
 97		m["inputSchema"] = t.RawInputSchema
 98	} else {
 99		// Use the structured InputSchema
100		m["inputSchema"] = t.InputSchema
101	}
102
103	return json.Marshal(m)
104}
105
106type ToolInputSchema struct {
107	Type       string                 `json:"type"`
108	Properties map[string]interface{} `json:"properties"`
109	Required   []string               `json:"required,omitempty"`
110}
111
112// ToolOption is a function that configures a Tool.
113// It provides a flexible way to set various properties of a Tool using the functional options pattern.
114type ToolOption func(*Tool)
115
116// PropertyOption is a function that configures a property in a Tool's input schema.
117// It allows for flexible configuration of JSON Schema properties using the functional options pattern.
118type PropertyOption func(map[string]interface{})
119
120//
121// Core Tool Functions
122//
123
124// NewTool creates a new Tool with the given name and options.
125// The tool will have an object-type input schema with configurable properties.
126// Options are applied in order, allowing for flexible tool configuration.
127func NewTool(name string, opts ...ToolOption) Tool {
128	tool := Tool{
129		Name: name,
130		InputSchema: ToolInputSchema{
131			Type:       "object",
132			Properties: make(map[string]interface{}),
133			Required:   nil, // Will be omitted from JSON if empty
134		},
135	}
136
137	for _, opt := range opts {
138		opt(&tool)
139	}
140
141	return tool
142}
143
144// NewToolWithRawSchema creates a new Tool with the given name and a raw JSON
145// Schema. This allows for arbitrary JSON Schema to be used for the tool's input
146// schema.
147//
148// NOTE a [Tool] built in such a way is incompatible with the [ToolOption] and
149// runtime errors will result from supplying a [ToolOption] to a [Tool] built
150// with this function.
151func NewToolWithRawSchema(name, description string, schema json.RawMessage) Tool {
152	tool := Tool{
153		Name:           name,
154		Description:    description,
155		RawInputSchema: schema,
156	}
157
158	return tool
159}
160
161// WithDescription adds a description to the Tool.
162// The description should provide a clear, human-readable explanation of what the tool does.
163func WithDescription(description string) ToolOption {
164	return func(t *Tool) {
165		t.Description = description
166	}
167}
168
169//
170// Common Property Options
171//
172
173// Description adds a description to a property in the JSON Schema.
174// The description should explain the purpose and expected values of the property.
175func Description(desc string) PropertyOption {
176	return func(schema map[string]interface{}) {
177		schema["description"] = desc
178	}
179}
180
181// Required marks a property as required in the tool's input schema.
182// Required properties must be provided when using the tool.
183func Required() PropertyOption {
184	return func(schema map[string]interface{}) {
185		schema["required"] = true
186	}
187}
188
189// Title adds a display-friendly title to a property in the JSON Schema.
190// This title can be used by UI components to show a more readable property name.
191func Title(title string) PropertyOption {
192	return func(schema map[string]interface{}) {
193		schema["title"] = title
194	}
195}
196
197//
198// String Property Options
199//
200
201// DefaultString sets the default value for a string property.
202// This value will be used if the property is not explicitly provided.
203func DefaultString(value string) PropertyOption {
204	return func(schema map[string]interface{}) {
205		schema["default"] = value
206	}
207}
208
209// Enum specifies a list of allowed values for a string property.
210// The property value must be one of the specified enum values.
211func Enum(values ...string) PropertyOption {
212	return func(schema map[string]interface{}) {
213		schema["enum"] = values
214	}
215}
216
217// MaxLength sets the maximum length for a string property.
218// The string value must not exceed this length.
219func MaxLength(max int) PropertyOption {
220	return func(schema map[string]interface{}) {
221		schema["maxLength"] = max
222	}
223}
224
225// MinLength sets the minimum length for a string property.
226// The string value must be at least this length.
227func MinLength(min int) PropertyOption {
228	return func(schema map[string]interface{}) {
229		schema["minLength"] = min
230	}
231}
232
233// Pattern sets a regex pattern that a string property must match.
234// The string value must conform to the specified regular expression.
235func Pattern(pattern string) PropertyOption {
236	return func(schema map[string]interface{}) {
237		schema["pattern"] = pattern
238	}
239}
240
241//
242// Number Property Options
243//
244
245// DefaultNumber sets the default value for a number property.
246// This value will be used if the property is not explicitly provided.
247func DefaultNumber(value float64) PropertyOption {
248	return func(schema map[string]interface{}) {
249		schema["default"] = value
250	}
251}
252
253// Max sets the maximum value for a number property.
254// The number value must not exceed this maximum.
255func Max(max float64) PropertyOption {
256	return func(schema map[string]interface{}) {
257		schema["maximum"] = max
258	}
259}
260
261// Min sets the minimum value for a number property.
262// The number value must not be less than this minimum.
263func Min(min float64) PropertyOption {
264	return func(schema map[string]interface{}) {
265		schema["minimum"] = min
266	}
267}
268
269// MultipleOf specifies that a number must be a multiple of the given value.
270// The number value must be divisible by this value.
271func MultipleOf(value float64) PropertyOption {
272	return func(schema map[string]interface{}) {
273		schema["multipleOf"] = value
274	}
275}
276
277//
278// Boolean Property Options
279//
280
281// DefaultBool sets the default value for a boolean property.
282// This value will be used if the property is not explicitly provided.
283func DefaultBool(value bool) PropertyOption {
284	return func(schema map[string]interface{}) {
285		schema["default"] = value
286	}
287}
288
289//
290// Property Type Helpers
291//
292
293// WithBoolean adds a boolean property to the tool schema.
294// It accepts property options to configure the boolean property's behavior and constraints.
295func WithBoolean(name string, opts ...PropertyOption) ToolOption {
296	return func(t *Tool) {
297		schema := map[string]interface{}{
298			"type": "boolean",
299		}
300
301		for _, opt := range opts {
302			opt(schema)
303		}
304
305		// Remove required from property schema and add to InputSchema.required
306		if required, ok := schema["required"].(bool); ok && required {
307			delete(schema, "required")
308			if t.InputSchema.Required == nil {
309				t.InputSchema.Required = []string{name}
310			} else {
311				t.InputSchema.Required = append(t.InputSchema.Required, name)
312			}
313		}
314
315		t.InputSchema.Properties[name] = schema
316	}
317}
318
319// WithNumber adds a number property to the tool schema.
320// It accepts property options to configure the number property's behavior and constraints.
321func WithNumber(name string, opts ...PropertyOption) ToolOption {
322	return func(t *Tool) {
323		schema := map[string]interface{}{
324			"type": "number",
325		}
326
327		for _, opt := range opts {
328			opt(schema)
329		}
330
331		// Remove required from property schema and add to InputSchema.required
332		if required, ok := schema["required"].(bool); ok && required {
333			delete(schema, "required")
334			if t.InputSchema.Required == nil {
335				t.InputSchema.Required = []string{name}
336			} else {
337				t.InputSchema.Required = append(t.InputSchema.Required, name)
338			}
339		}
340
341		t.InputSchema.Properties[name] = schema
342	}
343}
344
345// WithString adds a string property to the tool schema.
346// It accepts property options to configure the string property's behavior and constraints.
347func WithString(name string, opts ...PropertyOption) ToolOption {
348	return func(t *Tool) {
349		schema := map[string]interface{}{
350			"type": "string",
351		}
352
353		for _, opt := range opts {
354			opt(schema)
355		}
356
357		// Remove required from property schema and add to InputSchema.required
358		if required, ok := schema["required"].(bool); ok && required {
359			delete(schema, "required")
360			if t.InputSchema.Required == nil {
361				t.InputSchema.Required = []string{name}
362			} else {
363				t.InputSchema.Required = append(t.InputSchema.Required, name)
364			}
365		}
366
367		t.InputSchema.Properties[name] = schema
368	}
369}
370
371// WithObject adds an object property to the tool schema.
372// It accepts property options to configure the object property's behavior and constraints.
373func WithObject(name string, opts ...PropertyOption) ToolOption {
374	return func(t *Tool) {
375		schema := map[string]interface{}{
376			"type":       "object",
377			"properties": map[string]interface{}{},
378		}
379
380		for _, opt := range opts {
381			opt(schema)
382		}
383
384		// Remove required from property schema and add to InputSchema.required
385		if required, ok := schema["required"].(bool); ok && required {
386			delete(schema, "required")
387			if t.InputSchema.Required == nil {
388				t.InputSchema.Required = []string{name}
389			} else {
390				t.InputSchema.Required = append(t.InputSchema.Required, name)
391			}
392		}
393
394		t.InputSchema.Properties[name] = schema
395	}
396}
397
398// WithArray adds an array property to the tool schema.
399// It accepts property options to configure the array property's behavior and constraints.
400func WithArray(name string, opts ...PropertyOption) ToolOption {
401	return func(t *Tool) {
402		schema := map[string]interface{}{
403			"type": "array",
404		}
405
406		for _, opt := range opts {
407			opt(schema)
408		}
409
410		// Remove required from property schema and add to InputSchema.required
411		if required, ok := schema["required"].(bool); ok && required {
412			delete(schema, "required")
413			if t.InputSchema.Required == nil {
414				t.InputSchema.Required = []string{name}
415			} else {
416				t.InputSchema.Required = append(t.InputSchema.Required, name)
417			}
418		}
419
420		t.InputSchema.Properties[name] = schema
421	}
422}
423
424// Properties defines the properties for an object schema
425func Properties(props map[string]interface{}) PropertyOption {
426	return func(schema map[string]interface{}) {
427		schema["properties"] = props
428	}
429}
430
431// AdditionalProperties specifies whether additional properties are allowed in the object
432// or defines a schema for additional properties
433func AdditionalProperties(schema interface{}) PropertyOption {
434	return func(schemaMap map[string]interface{}) {
435		schemaMap["additionalProperties"] = schema
436	}
437}
438
439// MinProperties sets the minimum number of properties for an object
440func MinProperties(min int) PropertyOption {
441	return func(schema map[string]interface{}) {
442		schema["minProperties"] = min
443	}
444}
445
446// MaxProperties sets the maximum number of properties for an object
447func MaxProperties(max int) PropertyOption {
448	return func(schema map[string]interface{}) {
449		schema["maxProperties"] = max
450	}
451}
452
453// PropertyNames defines a schema for property names in an object
454func PropertyNames(schema map[string]interface{}) PropertyOption {
455	return func(schemaMap map[string]interface{}) {
456		schemaMap["propertyNames"] = schema
457	}
458}
459
460// Items defines the schema for array items
461func Items(schema interface{}) PropertyOption {
462	return func(schemaMap map[string]interface{}) {
463		schemaMap["items"] = schema
464	}
465}
466
467// MinItems sets the minimum number of items for an array
468func MinItems(min int) PropertyOption {
469	return func(schema map[string]interface{}) {
470		schema["minItems"] = min
471	}
472}
473
474// MaxItems sets the maximum number of items for an array
475func MaxItems(max int) PropertyOption {
476	return func(schema map[string]interface{}) {
477		schema["maxItems"] = max
478	}
479}
480
481// UniqueItems specifies whether array items must be unique
482func UniqueItems(unique bool) PropertyOption {
483	return func(schema map[string]interface{}) {
484		schema["uniqueItems"] = unique
485	}
486}