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}