Skip to content

Commit 5985a7c

Browse files
authored
docs: document tool support (#498)
Document tool support, and tweak the wording for prompts. For #442
1 parent 28753c9 commit 5985a7c

File tree

7 files changed

+358
-56
lines changed

7 files changed

+358
-56
lines changed

docs/client.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ func Example_roots() {
5656
if _, err := s.Connect(ctx, t1, nil); err != nil {
5757
log.Fatal(err)
5858
}
59-
if _, err := c.Connect(ctx, t2, nil); err != nil {
59+
60+
clientSession, err := c.Connect(ctx, t2, nil)
61+
if err != nil {
6062
log.Fatal(err)
6163
}
64+
defer clientSession.Close()
6265

6366
// ...and add a root. The server is notified about the change.
6467
c.AddRoots(&mcp.Root{URI: "file://b"})

docs/server.md

Lines changed: 191 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,32 @@
1111

1212
## Prompts
1313

14-
**Server-side**:
15-
MCP servers can provide LLM prompt templates (called simply _prompts_) to clients.
16-
Every prompt has a required name which identifies it, and a set of named arguments, which are strings.
17-
Construct a prompt with a name and descriptions of its arguments.
18-
Associated with each prompt is a handler that expands the template given values for its arguments.
19-
Use [`Server.AddPrompt`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddPrompt)
20-
to add a prompt along with its handler.
21-
If `AddPrompt` is called before a server is connected, the server will have the `prompts` capability.
22-
If all prompts are to be added after connection, set [`ServerOptions.HasPrompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.HasPrompts)
23-
to advertise the capability.
24-
25-
**Client-side**:
26-
To list the server's prompts, call
27-
Call [`ClientSession.Prompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Prompts) to get an iterator.
28-
If needed, you can use the lower-level
29-
[`ClientSession.ListPrompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ListPrompts) to list the server's prompts.
30-
Call [`ClientSession.GetPrompt`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.GetPrompt) to retrieve a prompt by name, providing
31-
arguments for expansion.
32-
Set [`ClientOptions.PromptListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.PromptListChangedHandler) to be notified of changes in the list of prompts.
14+
MCP servers can provide LLM prompt templates (called simply
15+
[_prompts_](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts))
16+
to clients. Every prompt has a required name which identifies it, and a set of
17+
named arguments, which are strings.
18+
19+
**Client-side**: To list the server's prompts, use the
20+
[`ClientSession.Prompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Prompts)
21+
iterator, or the lower-level
22+
[`ClientSession.ListPrompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ListPrompts)
23+
(see [pagination](#pagination) below). Set
24+
[`ClientOptions.PromptListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.PromptListChangedHandler)
25+
to be notified of changes in the list of prompts.
26+
27+
Call
28+
[`ClientSession.GetPrompt`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.GetPrompt)
29+
to retrieve a prompt by name, providing arguments for expansion.
30+
31+
**Server-side**: Use
32+
[`Server.AddPrompt`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddPrompt)
33+
to add a prompt to the server along with its handler.
34+
The server will have the `prompts` capability if any prompt is added before the
35+
server is connected to a client, or if
36+
[`ServerOptions.HasPrompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.HasPrompts)
37+
is explicitly set. When a prompt is added, any clients already connected to the
38+
server will be notified via a `notifications/prompts/list_changed`
39+
notification.
3340

3441
```go
3542
func Example_prompts() {
@@ -73,6 +80,7 @@ func Example_prompts() {
7380
if err != nil {
7481
log.Fatal(err)
7582
}
83+
defer cs.Close()
7684

7785
// List the prompts.
7886
for p, err := range cs.Prompts(ctx, nil) {
@@ -105,7 +113,170 @@ func Example_prompts() {
105113

106114
## Tools
107115

108-
<!-- TODO -->
116+
MCP servers can provide
117+
[tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)
118+
to allow clients to interact with external systems or functionality. Tools are
119+
effectively remote function calls, and the Go SDK provides mechanisms to bind
120+
them to ordinary Go functions.
121+
122+
**Client-side**: To list the server's tools, use the
123+
[`ClientSession.Tools`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Tools)
124+
iterator, or the lower-level
125+
[`ClientSession.ListTools`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ListTools)
126+
(see [pagination](#pagination)). Set
127+
[`ClientOptions.ToolListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ToolListChangedHandler)
128+
to be notified of changes in the list of tools.
129+
130+
To call a tool, use
131+
[`ClientSession.CallTool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.CallTool)
132+
with `CallToolParams` holding the name and arguments of the tool to call.
133+
134+
```go
135+
res, err := session.CallTool(ctx, &mcp.CallToolParams{
136+
Name: "my_tool",
137+
Arguments: map[string]any{"name": "user"},
138+
})
139+
```
140+
141+
Arguments may be any value that can be marshaled to JSON.
142+
143+
**Server-side**: the basic API for adding a tool is symmetrical with the API
144+
for prompts or resources:
145+
[`Server.AddTool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddTool)
146+
adds a
147+
[`Tool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Tool) to
148+
the server along with its
149+
[`ToolHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ToolHandler)
150+
to handle it. The server will have the `tools` capability if any tool is added
151+
before the server is connected to a client, or if
152+
[`ServerOptions.HasTools`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.HasPrompts)
153+
is explicitly set. When a tool is added, any clients already connected to the
154+
server will be notified via a `notifications/tools/list_changed` notification.
155+
156+
However, the `Server.AddTool` API leaves it to the user to implement the tool
157+
handler correctly according to the spec, providing very little out of the box.
158+
In order to implement a tool, the user must do all of the following:
159+
160+
- Provide a tool input and output schema.
161+
- Validate the tool arguments against its input schema.
162+
- Unmarshal the input schema into a Go value
163+
- Execute the tool logic.
164+
- Marshal the tool's structured output (if any) to JSON, and store it in the
165+
result's `StructuredOutput` field as well as the unstructured `Content` field.
166+
- Validate that output JSON against the tool's output schema.
167+
- If any tool errors occurred, pack them into the unstructured content and set
168+
`IsError` to `true.`
169+
170+
For this reason, the SDK provides a generic
171+
[`AddTool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#AddTool)
172+
function that handles this for you. It can bind a tool to any function with the
173+
following shape:
174+
175+
```go
176+
func(_ context.Context, request *CallToolRequest, input In) (result *CallToolResult, output Out, _ error)
177+
```
178+
179+
This is like a `ToolHandler`, but with an extra arbitrary `In` input parameter,
180+
and `Out` output parameter.
181+
182+
Such a function can then be bound to the server using `AddTool`:
183+
184+
```go
185+
mcp.AddTool(server, &mcp.Tool{Name: "my_tool"}, handler)
186+
```
187+
188+
This does the following automatically:
189+
190+
- If `Tool.InputSchema` or `Tool.OutputSchema` are unset, the input and output
191+
schemas are inferred from the `In` type, which must be a struct or map.
192+
Optional `jsonschema` struct tags provide argument descriptions.
193+
- Tool arguments are validated against the input schema.
194+
- Tool arguments are marshaled into the `In` value.
195+
- Tool output (the `Out` value) is marshaled into the result's
196+
`StructuredOutput`, as well as the unstructured `Content`.
197+
- Output is validated against the tool's output schema.
198+
- If an ordinary error is returned, it is stored int the `CallToolResult` and
199+
`IsError` is set to `true`.
200+
201+
In fact, under ordinary circumstances, the user can ignore `CallToolRequest`
202+
and `CallToolResult`.
203+
204+
For a more realistic example, consider a tool that retrieves the weather:
205+
206+
```go
207+
type WeatherInput struct {
208+
Location Location `json:"location" jsonschema:"user location"`
209+
Days int `json:"days" jsonschema:"number of days to forecast"`
210+
}
211+
212+
type WeatherOutput struct {
213+
Summary string `json:"summary" jsonschema:"a summary of the weather forecast"`
214+
Confidence Probability `json:"confidence" jsonschema:"confidence, between 0 and 1"`
215+
AsOf time.Time `json:"asOf" jsonschema:"the time the weather was computed"`
216+
DailyForecast []Forecast `json:"dailyForecast" jsonschema:"the daily forecast"`
217+
Source string `json:"source,omitempty" jsonschema:"the organization providing the weather forecast"`
218+
}
219+
220+
func WeatherTool(ctx context.Context, req *mcp.CallToolRequest, in WeatherInput) (*mcp.CallToolResult, WeatherOutput, error) {
221+
perfectWeather := WeatherOutput{
222+
Summary: "perfect",
223+
Confidence: 1.0,
224+
AsOf: time.Now(),
225+
}
226+
for range in.Days {
227+
perfectWeather.DailyForecast = append(perfectWeather.DailyForecast, Forecast{
228+
Forecast: "another perfect day",
229+
Type: Sunny,
230+
Rain: 0.0,
231+
High: 72.0,
232+
Low: 72.0,
233+
})
234+
}
235+
return nil, perfectWeather, nil
236+
}
237+
```
238+
239+
In this case, we want to customize part of the inferred schema, though we can
240+
still infer the rest. Since we want to control the inference ourselves, we set
241+
the `Tool.InputSchema` explicitly:
242+
243+
```go
244+
// Distinguished Go types allow custom schemas to be reused during inference.
245+
customSchemas := map[any]*jsonschema.Schema{
246+
Probability(0): {Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0)},
247+
WeatherType(""): {Type: "string", Enum: []any{Sunny, PartlyCloudy, Cloudy, Rainy, Snowy}},
248+
}
249+
opts := &jsonschema.ForOptions{TypeSchemas: customSchemas}
250+
in, err := jsonschema.For[WeatherInput](opts)
251+
if err != nil {
252+
log.Fatal(err)
253+
}
254+
255+
// Furthermore, we can tweak the inferred schema, in this case limiting
256+
// forecasts to 0-10 days.
257+
daysSchema := in.Properties["days"]
258+
daysSchema.Minimum = jsonschema.Ptr(0.0)
259+
daysSchema.Maximum = jsonschema.Ptr(10.0)
260+
261+
// Output schema inference can reuse our custom schemas from input inference.
262+
out, err := jsonschema.For[WeatherOutput](opts)
263+
if err != nil {
264+
log.Fatal(err)
265+
}
266+
267+
// Now add our tool to a server. Since we've customized the schemas, we need
268+
// to override the default schema inference.
269+
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
270+
mcp.AddTool(server, &mcp.Tool{
271+
Name: "weather",
272+
InputSchema: in,
273+
OutputSchema: out,
274+
}, WeatherTool)
275+
```
276+
277+
_See [mcp/tool_example_test.go](../mcp/tool_example_test.go) for the full
278+
example, or [examples/server/toolschemas](examples/server/toolschemas/main.go)
279+
for more examples of customizing tool schemas._
109280

110281
## Utilities
111282

docs/troubleshooting.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,21 @@ func ExampleLoggingTransport() {
2929
ctx := context.Background()
3030
t1, t2 := mcp.NewInMemoryTransports()
3131
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
32-
if _, err := server.Connect(ctx, t1, nil); err != nil {
32+
serverSession, err := server.Connect(ctx, t1, nil)
33+
if err != nil {
3334
log.Fatal(err)
3435
}
36+
defer serverSession.Wait()
3537

3638
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
3739
var b bytes.Buffer
3840
logTransport := &mcp.LoggingTransport{Transport: t2, Writer: &b}
39-
if _, err := client.Connect(ctx, logTransport, nil); err != nil {
41+
clientSession, err := client.Connect(ctx, logTransport, nil)
42+
if err != nil {
4043
log.Fatal(err)
4144
}
45+
defer clientSession.Close()
46+
4247
// Sort for stability: reads are concurrent to writes.
4348
for _, line := range slices.Sorted(strings.SplitSeq(b.String(), "\n")) {
4449
fmt.Println(line)

0 commit comments

Comments
 (0)