1228 lines
47 KiB
Markdown
1228 lines
47 KiB
Markdown
<!-- Autogenerated by weave; DO NOT EDIT -->
|
|
# Support for MCP server features
|
|
|
|
1. [Prompts](#prompts)
|
|
1. [Prompt message content](#prompt-message-content)
|
|
1. [Resources](#resources)
|
|
1. [Binary resources](#binary-resources)
|
|
1. [Resource subscriptions](#resource-subscriptions)
|
|
1. [Tools](#tools)
|
|
1. [Tool result content](#tool-result-content)
|
|
1. [List changed notifications](#list-changed-notifications)
|
|
1. [Multi Round-Trip Requests](#multi-round-trip-requests)
|
|
1. [Talking to legacy clients](#talking-to-legacy-clients)
|
|
1. [Example](#example)
|
|
1. [Cacheable list results](#cacheable-list-results)
|
|
1. [Utilities](#utilities)
|
|
1. [Completion](#completion)
|
|
1. [Logging](#logging)
|
|
1. [Capabilities](#capabilities)
|
|
1. [Capability inference](#capability-inference)
|
|
1. [Explicit capabilities](#explicit-capabilities)
|
|
1. [Extensions](#extensions)
|
|
1. [Pagination](#pagination)
|
|
|
|
## Prompts
|
|
|
|
MCP servers can provide LLM prompt templates (called simply
|
|
[_prompts_](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts))
|
|
to clients. Every prompt has a required name which identifies it, and a set of
|
|
named arguments, which are strings.
|
|
|
|
**Client-side**: To list the server's prompts, use the
|
|
[`ClientSession.Prompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Prompts)
|
|
iterator, or the lower-level
|
|
[`ClientSession.ListPrompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ListPrompts)
|
|
(see [pagination](#pagination) below). 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.
|
|
|
|
Call
|
|
[`ClientSession.GetPrompt`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.GetPrompt)
|
|
to retrieve a prompt by name, providing arguments for expansion.
|
|
|
|
**Server-side**: Use
|
|
[`Server.AddPrompt`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddPrompt)
|
|
to add a prompt to the server along with its handler.
|
|
The server will have the `prompts` capability if any prompt is added before the
|
|
server is connected to a client, or if
|
|
[`ServerOptions.HasPrompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.HasPrompts)
|
|
is explicitly set. When a prompt is added, any clients already connected to the
|
|
server will be notified via a `notifications/prompts/list_changed`
|
|
notification.
|
|
|
|
```go
|
|
func Example_prompts() {
|
|
ctx := context.Background()
|
|
|
|
promptHandler := func(ctx context.Context, req *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
|
|
return &mcp.GetPromptResult{
|
|
Description: "Hi prompt",
|
|
Messages: []*mcp.PromptMessage{
|
|
{
|
|
Role: "user",
|
|
Content: &mcp.TextContent{Text: "Say hi to " + req.Params.Arguments["name"]},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// Create a server with a single prompt.
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
prompt := &mcp.Prompt{
|
|
Name: "greet",
|
|
Arguments: []*mcp.PromptArgument{
|
|
{
|
|
Name: "name",
|
|
Description: "the name of the person to greet",
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
s.AddPrompt(prompt, promptHandler)
|
|
|
|
// Create a client.
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
|
|
|
|
// Connect the server and client.
|
|
t1, t2 := mcp.NewInMemoryTransports()
|
|
if _, err := s.Connect(ctx, t1, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cs, err := c.Connect(ctx, t2, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
// List the prompts.
|
|
for p, err := range cs.Prompts(ctx, nil) {
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println(p.Name)
|
|
}
|
|
|
|
// Get the prompt.
|
|
res, err := cs.GetPrompt(ctx, &mcp.GetPromptParams{
|
|
Name: "greet",
|
|
Arguments: map[string]string{"name": "Pat"},
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
for _, msg := range res.Messages {
|
|
fmt.Println(msg.Role, msg.Content.(*mcp.TextContent).Text)
|
|
}
|
|
// Output:
|
|
// greet
|
|
// user Say hi to Pat
|
|
}
|
|
```
|
|
|
|
### Prompt message content
|
|
|
|
A
|
|
[`PromptMessage`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#PromptMessage)
|
|
has two fields: a `Role`, either `"user"` or `"assistant"`, and a single
|
|
`Content`. That is the same interface a tool result uses (see [Tool result
|
|
content](#tool-result-content)), so a prompt message can hold an image, audio,
|
|
or a resource's contents as readily as text.
|
|
|
|
Media on its own says nothing about what the model should do with it, so send a
|
|
text message alongside it. Embedding a resource puts its contents directly in
|
|
the message, sparing the client a separate `resources/read`. Note that
|
|
`ResourceContents.URI` is not checked against the resources the server has
|
|
registered: it records where the contents came from, so use the URI of a real
|
|
resource when there is one, as in the example below.
|
|
|
|
```go
|
|
func Example_promptContent() {
|
|
ctx := context.Background()
|
|
|
|
const styleGuide = "- Prefer clarity over cleverness.\n"
|
|
screenshotPNG := []byte("\x89PNG\r\n\x1a\n")
|
|
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
|
|
s.AddResource(&mcp.Resource{URI: "doc://style-guide", MIMEType: "text/markdown"},
|
|
func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
|
|
return &mcp.ReadResourceResult{
|
|
Contents: []*mcp.ResourceContents{{
|
|
URI: "doc://style-guide",
|
|
MIMEType: "text/markdown",
|
|
Text: styleGuide,
|
|
}},
|
|
}, nil
|
|
})
|
|
|
|
s.AddPrompt(&mcp.Prompt{Name: "review_screenshot"},
|
|
func(context.Context, *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
|
|
return &mcp.GetPromptResult{
|
|
Messages: []*mcp.PromptMessage{
|
|
{Role: "user", Content: &mcp.EmbeddedResource{
|
|
Resource: &mcp.ResourceContents{
|
|
URI: "doc://style-guide",
|
|
MIMEType: "text/markdown",
|
|
Text: styleGuide,
|
|
},
|
|
}},
|
|
{Role: "user", Content: &mcp.ImageContent{Data: screenshotPNG, MIMEType: "image/png"}},
|
|
{Role: "user", Content: &mcp.TextContent{Text: "Review the screenshot against the style guide."}},
|
|
},
|
|
}, nil
|
|
})
|
|
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
|
|
t1, t2 := mcp.NewInMemoryTransports()
|
|
if _, err := s.Connect(ctx, t1, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cs, err := c.Connect(ctx, t2, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
res, err := cs.GetPrompt(ctx, &mcp.GetPromptParams{Name: "review_screenshot"})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
for _, msg := range res.Messages {
|
|
switch content := msg.Content.(type) {
|
|
case *mcp.TextContent:
|
|
fmt.Println(msg.Role, "text:", content.Text)
|
|
case *mcp.ImageContent:
|
|
fmt.Printf("%s image: %s, %d bytes\n", msg.Role, content.MIMEType, len(content.Data))
|
|
case *mcp.EmbeddedResource:
|
|
fmt.Printf("%s embedded resource: %s (%s)\n", msg.Role, content.Resource.URI, content.Resource.MIMEType)
|
|
}
|
|
}
|
|
// Output:
|
|
// user embedded resource: doc://style-guide (text/markdown)
|
|
// user image: image/png, 8 bytes
|
|
// user text: Review the screenshot against the style guide.
|
|
}
|
|
```
|
|
|
|
## Resources
|
|
|
|
In MCP terms, a _resource_ is some data referenced by a URI.
|
|
MCP servers can serve resources to clients.
|
|
They can register resources individually, or register a _resource template_
|
|
that uses a URI pattern to describe a collection of resources.
|
|
|
|
|
|
**Client-side**:
|
|
Call [`ClientSession.ReadResource`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ReadResource)
|
|
to read a resource.
|
|
The SDK ensures that a read succeeds only if the URI matches a registered resource exactly,
|
|
or matches the URI pattern of a resource template.
|
|
|
|
To list a server's resources and resource templates, use the
|
|
[`ClientSession.Resources`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Resources)
|
|
and
|
|
[`ClientSession.ResourceTemplates`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ResourceTemplates)
|
|
iterators, or the lower-level `ListXXX` calls (see [pagination](#pagination)).
|
|
Set
|
|
[`ClientOptions.ResourceListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ResourceListChangedHandler)
|
|
to be notified of changes in the lists of resources or resource templates.
|
|
|
|
Clients can be notified when the contents of a resource changes by subscribing to the resource's URI.
|
|
Call
|
|
[`ClientSession.Subscribe`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Subscribe)
|
|
to subscribe to a resource
|
|
and
|
|
[`ClientSession.Unsubscribe`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Unsubscribe)
|
|
to unsubscribe.
|
|
Set
|
|
[`ClientOptions.ResourceUpdatedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ResourceUpdatedHandler)
|
|
to be notified of changes to subscribed resources. On `2026-07-28` and
|
|
later sessions the SDK delivers these notifications over a
|
|
`subscriptions/listen` stream instead of the legacy `resources/subscribe`
|
|
RPC; see [Subscriptions
|
|
(`subscriptions/listen`)](protocol.md#subscriptions)
|
|
for the wire-level details.
|
|
|
|
**Server-side**:
|
|
Use
|
|
[`Server.AddResource`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddResource)
|
|
or
|
|
[`Server.AddResourceTemplate`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddResourceTemplate)
|
|
to add a resource or resource template to the server along with its handler.
|
|
A
|
|
[`ResourceHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ResourceHandler)
|
|
maps a URI to the contents of a resource, which can include text, binary data,
|
|
or both.
|
|
If `AddResource` or `AddResourceTemplate` is called before a server is connected, the server will have the
|
|
`resources` capability.
|
|
The server will have the `resources` capability if any resource or resource template is added before the
|
|
server is connected to a client, or if
|
|
[`ServerOptions.HasResources`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.HasResources)
|
|
is explicitly set. When a prompt is added, any clients already connected to the
|
|
server will be notified via a `notifications/resources/list_changed`
|
|
notification.
|
|
|
|
|
|
```go
|
|
func Example_resources() {
|
|
ctx := context.Background()
|
|
|
|
resources := map[string]string{
|
|
"file:///a": "a",
|
|
"file:///dir/x": "x",
|
|
"file:///dir/y": "y",
|
|
}
|
|
|
|
handler := func(_ context.Context, req *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
|
|
uri := req.Params.URI
|
|
c, ok := resources[uri]
|
|
if !ok {
|
|
return nil, mcp.ResourceNotFoundError(uri)
|
|
}
|
|
return &mcp.ReadResourceResult{
|
|
Contents: []*mcp.ResourceContents{{URI: uri, Text: c}},
|
|
}, nil
|
|
}
|
|
|
|
// Create a server with a single resource.
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
s.AddResource(&mcp.Resource{URI: "file:///a"}, handler)
|
|
s.AddResourceTemplate(&mcp.ResourceTemplate{URITemplate: "file:///dir/{f}"}, handler)
|
|
|
|
// Create a client.
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
|
|
|
|
// Connect the server and client.
|
|
t1, t2 := mcp.NewInMemoryTransports()
|
|
if _, err := s.Connect(ctx, t1, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cs, err := c.Connect(ctx, t2, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
// List resources and resource templates.
|
|
for r, err := range cs.Resources(ctx, nil) {
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println(r.URI)
|
|
}
|
|
for r, err := range cs.ResourceTemplates(ctx, nil) {
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println(r.URITemplate)
|
|
}
|
|
|
|
// Read resources.
|
|
for _, path := range []string{"a", "dir/x", "b"} {
|
|
res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: "file:///" + path})
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
} else {
|
|
fmt.Println(res.Contents[0].Text)
|
|
}
|
|
}
|
|
// Output:
|
|
// file:///a
|
|
// file:///dir/{f}
|
|
// a
|
|
// x
|
|
// calling "resources/read": Resource not found
|
|
}
|
|
```
|
|
|
|
### Binary resources
|
|
|
|
A
|
|
[`ResourceContents`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ResourceContents)
|
|
carries its data in either `Text` or `Blob`. Binary data goes in `Blob` as
|
|
plain bytes — the SDK base64-encodes it on the wire — and leaves `Text` empty,
|
|
so a client tells the two apart by which field is populated.
|
|
|
|
```go
|
|
func Example_binaryResource() {
|
|
ctx := context.Background()
|
|
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
s.AddResource(&mcp.Resource{URI: "file:///logo.png", MIMEType: "image/png"},
|
|
func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
|
|
return &mcp.ReadResourceResult{
|
|
Contents: []*mcp.ResourceContents{{
|
|
URI: "file:///logo.png",
|
|
MIMEType: "image/png",
|
|
Blob: []byte("\x89PNG\r\n\x1a\n"),
|
|
}},
|
|
}, nil
|
|
})
|
|
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
|
|
t1, t2 := mcp.NewInMemoryTransports()
|
|
if _, err := s.Connect(ctx, t1, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cs, err := c.Connect(ctx, t2, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: "file:///logo.png"})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
contents := res.Contents[0]
|
|
fmt.Printf("%s: %d bytes of %s, text is %q\n",
|
|
contents.URI, len(contents.Blob), contents.MIMEType, contents.Text)
|
|
|
|
// Output:
|
|
// file:///logo.png: 8 bytes of image/png, text is ""
|
|
}
|
|
```
|
|
|
|
### Resource subscriptions
|
|
|
|
A client interested in changes to one resource subscribes to its URI with
|
|
[`ClientSession.Subscribe`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Subscribe)
|
|
and stops with
|
|
[`ClientSession.Unsubscribe`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Unsubscribe).
|
|
Set
|
|
[`ServerOptions.SubscribeHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.SubscribeHandler)
|
|
and
|
|
[`UnsubscribeHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.UnsubscribeHandler)
|
|
to track who is listening; setting either one gives the server the
|
|
`resources.subscribe` capability.
|
|
|
|
Publish a change with
|
|
[`Server.ResourceUpdated`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.ResourceUpdated),
|
|
which notifies every session subscribed to that URI. The notification carries
|
|
only the URI, not the new contents, so a client that wants them reads the
|
|
resource again.
|
|
|
|
```go
|
|
func Example_resourceSubscription() {
|
|
ctx := context.Background()
|
|
|
|
config := "theme=light\n"
|
|
|
|
updated := make(chan string, 1)
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
|
|
// The notification carries only the URI, so read the resource again to
|
|
// see what it now holds.
|
|
ResourceUpdatedHandler: func(context.Context, *mcp.ResourceUpdatedNotificationRequest) {
|
|
updated <- "config://app"
|
|
},
|
|
})
|
|
|
|
subscribed := make(chan string, 2)
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
|
|
SubscribeHandler: func(_ context.Context, req *mcp.SubscribeRequest) error {
|
|
subscribed <- "subscribed to " + req.Params.URI
|
|
return nil
|
|
},
|
|
UnsubscribeHandler: func(_ context.Context, req *mcp.UnsubscribeRequest) error {
|
|
subscribed <- "unsubscribed from " + req.Params.URI
|
|
return nil
|
|
},
|
|
})
|
|
s.AddResource(&mcp.Resource{URI: "config://app"},
|
|
func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
|
|
return &mcp.ReadResourceResult{
|
|
Contents: []*mcp.ResourceContents{{URI: "config://app", Text: config}},
|
|
}, nil
|
|
})
|
|
|
|
t1, t2 := mcp.NewInMemoryTransports()
|
|
if _, err := s.Connect(ctx, t1, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cs, err := c.Connect(ctx, t2, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
if err := cs.Subscribe(ctx, &mcp.SubscribeParams{URI: "config://app"}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println(<-subscribed)
|
|
|
|
config = "theme=dark\n"
|
|
if err := s.ResourceUpdated(ctx, &mcp.ResourceUpdatedNotificationParams{URI: "config://app"}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
uri := <-updated
|
|
|
|
res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: uri})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Print("re-read: ", res.Contents[0].Text)
|
|
|
|
if err := cs.Unsubscribe(ctx, &mcp.UnsubscribeParams{URI: "config://app"}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println(<-subscribed)
|
|
|
|
// Output:
|
|
// subscribed to config://app
|
|
// re-read: theme=dark
|
|
// unsubscribed from config://app
|
|
}
|
|
```
|
|
|
|
## Tools
|
|
|
|
MCP servers can provide
|
|
[tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)
|
|
to allow clients to interact with external systems or functionality. Tools are
|
|
effectively remote function calls, and the Go SDK provides mechanisms to bind
|
|
them to ordinary Go functions.
|
|
|
|
**Client-side**: To list the server's tools, use the
|
|
[`ClientSession.Tools`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Tools)
|
|
iterator, or the lower-level
|
|
[`ClientSession.ListTools`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ListTools)
|
|
(see [pagination](#pagination)). Set
|
|
[`ClientOptions.ToolListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ToolListChangedHandler)
|
|
to be notified of changes in the list of tools.
|
|
|
|
To call a tool, use
|
|
[`ClientSession.CallTool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.CallTool)
|
|
with `CallToolParams` holding the name and arguments of the tool to call.
|
|
|
|
```go
|
|
res, err := session.CallTool(ctx, &mcp.CallToolParams{
|
|
Name: "my_tool",
|
|
Arguments: map[string]any{"name": "user"},
|
|
})
|
|
```
|
|
|
|
Arguments may be any value that can be marshaled to JSON.
|
|
|
|
**Server-side**: the basic API for adding a tool is symmetrical with the API
|
|
for prompts or resources:
|
|
[`Server.AddTool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddTool)
|
|
adds a
|
|
[`Tool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Tool) to
|
|
the server along with its
|
|
[`ToolHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ToolHandler)
|
|
to handle it. The server will have the `tools` capability if any tool is added
|
|
before the server is connected to a client, or if
|
|
[`ServerOptions.HasTools`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.HasPrompts)
|
|
is explicitly set. When a tool is added, any clients already connected to the
|
|
server will be notified via a `notifications/tools/list_changed` notification.
|
|
|
|
However, the `Server.AddTool` API leaves it to the user to implement the tool
|
|
handler correctly according to the spec, providing very little out of the box.
|
|
In order to implement a tool, the user must do all of the following:
|
|
|
|
- Provide a tool input and output schema.
|
|
- Validate the tool arguments against its input schema.
|
|
- Unmarshal the input schema into a Go value
|
|
- Execute the tool logic.
|
|
- Marshal the tool's structured output (if any) to JSON, and store it in the
|
|
result's `StructuredContent` field as well as the unstructured `Content` field.
|
|
- Validate that output JSON against the tool's output schema.
|
|
- If any tool errors occurred, pack them into the unstructured content and set
|
|
`IsError` to `true.`
|
|
|
|
For this reason, the SDK provides a generic
|
|
[`AddTool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#AddTool)
|
|
function that handles this for you. It can bind a tool to any function with the
|
|
following shape:
|
|
|
|
```go
|
|
func(_ context.Context, request *CallToolRequest, input In) (result *CallToolResult, output Out, _ error)
|
|
```
|
|
|
|
This is like a `ToolHandler`, but with an extra arbitrary `In` input parameter,
|
|
and `Out` output parameter.
|
|
|
|
Such a function can then be bound to the server using `AddTool`:
|
|
|
|
```go
|
|
mcp.AddTool(server, &mcp.Tool{Name: "my_tool"}, handler)
|
|
```
|
|
|
|
This does the following automatically:
|
|
|
|
- If `Tool.InputSchema` is unset, the input schema is inferred from the `In`
|
|
type, which must be a struct or map.
|
|
- If `Tool.OutputSchema` is unset and the `Out` type is not `any`, the output
|
|
schema is inferred from the `Out` type. Per SEP-2106, `Out` may be any Go
|
|
type whose inferred schema is a valid JSON Schema (struct, map, slice,
|
|
primitive, etc.).
|
|
- Optional `jsonschema` struct tags provide argument and output descriptions.
|
|
- Tool arguments are validated against the input schema.
|
|
- Tool arguments are marshaled into the `In` value.
|
|
- Tool output (the `Out` value) is marshaled into the result's
|
|
`StructuredContent`, as well as the unstructured `Content`.
|
|
- Output is validated against the tool's output schema.
|
|
- If an ordinary error is returned, it is stored int the `CallToolResult` and
|
|
`IsError` is set to `true`.
|
|
|
|
In fact, under ordinary circumstances, the user can ignore `CallToolRequest`
|
|
and `CallToolResult`.
|
|
|
|
For a more realistic example, consider a tool that retrieves the weather:
|
|
|
|
```go
|
|
type WeatherInput struct {
|
|
Location Location `json:"location" jsonschema:"user location"`
|
|
Days int `json:"days" jsonschema:"number of days to forecast"`
|
|
}
|
|
|
|
type WeatherOutput struct {
|
|
Summary string `json:"summary" jsonschema:"a summary of the weather forecast"`
|
|
Confidence Probability `json:"confidence" jsonschema:"confidence, between 0 and 1"`
|
|
AsOf time.Time `json:"asOf" jsonschema:"the time the weather was computed"`
|
|
DailyForecast []Forecast `json:"dailyForecast" jsonschema:"the daily forecast"`
|
|
Source string `json:"source,omitempty" jsonschema:"the organization providing the weather forecast"`
|
|
}
|
|
|
|
func WeatherTool(ctx context.Context, req *mcp.CallToolRequest, in WeatherInput) (*mcp.CallToolResult, WeatherOutput, error) {
|
|
perfectWeather := WeatherOutput{
|
|
Summary: "perfect",
|
|
Confidence: 1.0,
|
|
AsOf: time.Now(),
|
|
}
|
|
for range in.Days {
|
|
perfectWeather.DailyForecast = append(perfectWeather.DailyForecast, Forecast{
|
|
Forecast: "another perfect day",
|
|
Type: Sunny,
|
|
Rain: 0.0,
|
|
High: 72.0,
|
|
Low: 72.0,
|
|
})
|
|
}
|
|
return nil, perfectWeather, nil
|
|
}
|
|
```
|
|
|
|
In this case, we want to customize part of the inferred schema, though we can
|
|
still infer the rest. Since we want to control the inference ourselves, we set
|
|
the `Tool.InputSchema` explicitly:
|
|
|
|
```go
|
|
// Distinguished Go types allow custom schemas to be reused during inference.
|
|
customSchemas := map[reflect.Type]*jsonschema.Schema{
|
|
reflect.TypeFor[Probability](): {Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0)},
|
|
reflect.TypeFor[WeatherType](): {Type: "string", Enum: []any{Sunny, PartlyCloudy, Cloudy, Rainy, Snowy}},
|
|
}
|
|
opts := &jsonschema.ForOptions{TypeSchemas: customSchemas}
|
|
in, err := jsonschema.For[WeatherInput](opts)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Furthermore, we can tweak the inferred schema, in this case limiting
|
|
// forecasts to 0-10 days.
|
|
daysSchema := in.Properties["days"]
|
|
daysSchema.Minimum = jsonschema.Ptr(0.0)
|
|
daysSchema.Maximum = jsonschema.Ptr(10.0)
|
|
|
|
// Output schema inference can reuse our custom schemas from input inference.
|
|
out, err := jsonschema.For[WeatherOutput](opts)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Now add our tool to a server. Since we've customized the schemas, we need
|
|
// to override the default schema inference.
|
|
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "weather",
|
|
InputSchema: in,
|
|
OutputSchema: out,
|
|
}, WeatherTool)
|
|
```
|
|
|
|
_See [mcp/tool_example_test.go](https://github.com/modelcontextprotocol/go-sdk/blob/main/mcp/tool_example_test.go) for the full
|
|
example, or [examples/server/toolschemas](https://github.com/modelcontextprotocol/go-sdk/blob/main/examples/server/toolschemas/main.go)
|
|
for more examples of customizing tool schemas._
|
|
|
|
### Tool result content
|
|
|
|
Alongside its structured output, a tool result carries a list of
|
|
[`Content`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Content)
|
|
blocks in `CallToolResult.Content`, and may mix as many kinds as it needs:
|
|
|
|
| Type | Carries |
|
|
| --- | --- |
|
|
| [`TextContent`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#TextContent) | plain text |
|
|
| [`ImageContent`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ImageContent) | image data, with a MIME type |
|
|
| [`AudioContent`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#AudioContent) | audio data, with a MIME type |
|
|
| [`EmbeddedResource`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#EmbeddedResource) | the contents of a resource, inline |
|
|
| [`ResourceLink`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ResourceLink) | a reference to a resource the client can read separately |
|
|
|
|
`ImageContent.Data` and `AudioContent.Data` are plain `[]byte`; the SDK
|
|
base64-encodes them on the wire, so handlers never encode by hand, and base64
|
|
carried over from another MCP SDK must be decoded before it is assigned. An
|
|
`EmbeddedResource` wraps a
|
|
[`ResourceContents`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ResourceContents),
|
|
which holds either `Text` or, for binary data, `Blob`. Prefer a `ResourceLink`
|
|
when the client may not need the contents, since an embedded resource is
|
|
transferred whether it is used or not.
|
|
|
|
When a handler bound with `AddTool` leaves `Content` unset, the SDK fills it
|
|
with the JSON encoding of the output value; set `Content` explicitly, as below,
|
|
to return anything else. `StructuredContent` is still populated from the output
|
|
value either way.
|
|
|
|
```go
|
|
func ExampleAddTool_contentTypes() {
|
|
chartPNG := []byte("\x89PNG\r\n\x1a\n")
|
|
summaryWAV := []byte("RIFF....WAVE")
|
|
|
|
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
mcp.AddTool(server, &mcp.Tool{Name: "report"}, func(context.Context, *mcp.CallToolRequest, struct{}) (*mcp.CallToolResult, any, error) {
|
|
return &mcp.CallToolResult{
|
|
Content: []mcp.Content{
|
|
&mcp.TextContent{Text: "Q3 revenue by region."},
|
|
&mcp.ImageContent{Data: chartPNG, MIMEType: "image/png"},
|
|
&mcp.AudioContent{Data: summaryWAV, MIMEType: "audio/wav"},
|
|
// An embedded resource inlines the contents, so the client
|
|
// needs no follow-up resources/read. Binary contents go in
|
|
// ResourceContents.Blob instead of Text.
|
|
&mcp.EmbeddedResource{
|
|
Resource: &mcp.ResourceContents{
|
|
URI: "file:///reports/q3.csv",
|
|
MIMEType: "text/csv",
|
|
Text: "region,revenue\nEMEA,42\n",
|
|
},
|
|
},
|
|
// A resource link only points at a resource. Prefer it when the
|
|
// client may not need the contents, since an embedded resource
|
|
// is transferred either way.
|
|
&mcp.ResourceLink{URI: "file:///reports/q3.pdf", MIMEType: "application/pdf"},
|
|
},
|
|
}, nil, nil
|
|
})
|
|
|
|
ctx := context.Background()
|
|
session, err := connect(ctx, server)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer session.Close()
|
|
|
|
res, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "report"})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Clients type-switch over the content they receive.
|
|
for _, content := range res.Content {
|
|
switch c := content.(type) {
|
|
case *mcp.TextContent:
|
|
fmt.Println("text:", c.Text)
|
|
case *mcp.ImageContent:
|
|
fmt.Printf("image: %s, %d bytes\n", c.MIMEType, len(c.Data))
|
|
case *mcp.AudioContent:
|
|
fmt.Printf("audio: %s, %d bytes\n", c.MIMEType, len(c.Data))
|
|
case *mcp.EmbeddedResource:
|
|
fmt.Printf("embedded resource: %s (%s)\n", c.Resource.URI, c.Resource.MIMEType)
|
|
case *mcp.ResourceLink:
|
|
fmt.Printf("resource link: %s (%s)\n", c.URI, c.MIMEType)
|
|
}
|
|
}
|
|
// Output:
|
|
// text: Q3 revenue by region.
|
|
// image: image/png, 8 bytes
|
|
// audio: audio/wav, 12 bytes
|
|
// embedded resource: file:///reports/q3.csv (text/csv)
|
|
// resource link: file:///reports/q3.pdf (application/pdf)
|
|
}
|
|
```
|
|
|
|
`ToolUseContent` and `ToolResultContent` also implement `Content`, but are only
|
|
valid in sampling messages, not in tool results.
|
|
|
|
**Stateless server deployments:** Some deployments create a new
|
|
[`Server`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server)
|
|
for each incoming request, re-registering tools every time. To avoid repeated
|
|
schema generation, create a
|
|
[`SchemaCache`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#SchemaCache)
|
|
and share it across server instances:
|
|
|
|
```go
|
|
var schemaCache = mcp.NewSchemaCache() // create once at startup
|
|
|
|
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
|
s := mcp.NewServer(impl, &mcp.ServerOptions{SchemaCache: schemaCache})
|
|
mcp.AddTool(s, myTool, myHandler)
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## List changed notifications
|
|
|
|
Adding or removing a feature on a connected server sends the matching
|
|
`notifications/*/list_changed` to every client, which dispatches it to
|
|
[`ClientOptions.ToolListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ToolListChangedHandler),
|
|
[`PromptListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.PromptListChangedHandler),
|
|
or
|
|
[`ResourceListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ResourceListChangedHandler).
|
|
A notification reports only that the list changed, so a client that needs the
|
|
new contents lists them again.
|
|
|
|
These notifications ride on capabilities, and capabilities are inferred from
|
|
what is registered before [`Server.Connect`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.Connect)
|
|
(see [Capabilities](#capabilities)). A server that registers its features
|
|
afterwards, as the example below does, has to declare
|
|
`ServerOptions.HasTools`, `HasPrompts`, or `HasResources` itself, or the
|
|
client is never told.
|
|
|
|
```go
|
|
func Example_listChanged() {
|
|
ctx := context.Background()
|
|
|
|
changed := make(chan string, 3)
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
|
|
ToolListChangedHandler: func(context.Context, *mcp.ToolListChangedRequest) {
|
|
changed <- "tools"
|
|
},
|
|
PromptListChangedHandler: func(context.Context, *mcp.PromptListChangedRequest) {
|
|
changed <- "prompts"
|
|
},
|
|
ResourceListChangedHandler: func(context.Context, *mcp.ResourceListChangedRequest) {
|
|
changed <- "resources"
|
|
},
|
|
})
|
|
|
|
// Nothing is registered before Connect, so the capabilities that carry these
|
|
// notifications have to be declared explicitly.
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
|
|
HasTools: true,
|
|
HasPrompts: true,
|
|
HasResources: true,
|
|
})
|
|
|
|
t1, t2 := mcp.NewInMemoryTransports()
|
|
if _, err := s.Connect(ctx, t1, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cs, err := c.Connect(ctx, t2, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
mcp.AddTool(s, &mcp.Tool{Name: "greet"}, func(context.Context, *mcp.CallToolRequest, struct{}) (*mcp.CallToolResult, any, error) {
|
|
return &mcp.CallToolResult{}, nil, nil
|
|
})
|
|
fmt.Println("changed:", <-changed)
|
|
|
|
s.AddPrompt(&mcp.Prompt{Name: "review"}, func(context.Context, *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
|
|
return &mcp.GetPromptResult{}, nil
|
|
})
|
|
fmt.Println("changed:", <-changed)
|
|
|
|
s.AddResource(&mcp.Resource{URI: "file:///a"}, func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
|
|
return &mcp.ReadResourceResult{}, nil
|
|
})
|
|
fmt.Println("changed:", <-changed)
|
|
|
|
// Removing a feature notifies the client just as adding one does.
|
|
s.RemoveTools("greet")
|
|
fmt.Println("changed:", <-changed)
|
|
|
|
// Output:
|
|
// changed: tools
|
|
// changed: prompts
|
|
// changed: resources
|
|
// changed: tools
|
|
}
|
|
```
|
|
|
|
## Multi Round-Trip Requests
|
|
|
|
[SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)
|
|
defines a new pattern for server-to-client requests (sampling, elicitation,
|
|
roots). Instead of issuing a fresh JSON-RPC request mid-flight, the server
|
|
returns an `InputRequiredResult` from its in-flight handler — the
|
|
`InputRequests` field of `CallToolResult`, `GetPromptResult`, or
|
|
`ReadResourceResult` carries the requests for additional information. The
|
|
client responds by retrying the original call with `InputResponses` set.
|
|
|
|
The SDK supports this pattern from both sides without requiring callers to
|
|
choose:
|
|
|
|
- **Returning input requests from a handler.** Set `InputRequests` on the
|
|
result you return.
|
|
Leave `Content` / `StructuredContent` empty — returning
|
|
both at once is a server bug and the SDK returns `-32603 InternalError`.
|
|
- **Calling the legacy APIs.** `ServerSession.Elicit`,
|
|
`ServerSession.CreateMessage(WithTools)`, and `ServerSession.ListRoots`
|
|
remain available and work for both new and legacy clients.
|
|
|
|
### Talking to legacy clients
|
|
|
|
The server installs `serverMultiRoundTripMiddleware` automatically. For
|
|
clients on a protocol version earlier than `2026-07-28`, the middleware
|
|
intercepts any `InputRequiredResult` your handler returns, fulfils each
|
|
input request itself by calling the legacy server-initiated APIs
|
|
(`Elicit`, `CreateMessage`, `ListRoots`), and re-invokes your handler
|
|
exactly once with the responses already populated. This means a handler
|
|
written in the MRTR style works against both old and new clients without
|
|
code changes.
|
|
|
|
### Example
|
|
|
|
The following example shows a "greet" tool whose handler asks the user
|
|
for their name via elicitation before producing the final greeting. The
|
|
handler runs twice — once to issue the elicitation, once to consume the
|
|
response — but the client call site sees a single `CallTool` returning the
|
|
final result, because the SDK's MRTR middleware handles the round trip on
|
|
either side of the wire (depending on the negotiated protocol version).
|
|
|
|
```go
|
|
// Example_mrtr demonstrates the [Multi Round-Trip Requests] pattern
|
|
// (SEP-2322). A tool handler signals "I need more information from the user"
|
|
// by returning a [mcp.CallToolResult] whose `InputRequests` field carries the
|
|
// requests for the additional information. Each request can be an
|
|
// [mcp.ElicitParams] (ask the user a question),
|
|
// [mcp.CreateMessageParams] (sample from the client's LLM), or
|
|
// [mcp.ListRootsParams] (list the client's roots).
|
|
//
|
|
// On protocol version `2026-07-28` and later, the client's
|
|
// `clientMultiRoundTripMiddleware` fulfils each input request from the
|
|
// configured handler and retries the original call transparently. On earlier
|
|
// protocol versions, the server's `serverMultiRoundTripMiddleware` performs
|
|
// the equivalent dance from the server side by calling the legacy
|
|
// server-to-client API (`ServerSession.Elicit`, `CreateMessage`, or
|
|
// `ListRoots`) and re-invoking the handler with the response. Either way,
|
|
// the user-facing client call site sees only the final result.
|
|
//
|
|
// [Multi Round-Trip Requests]: https://modelcontextprotocol.io/specification/draft/basic/patterns#multi-round-trip-requests
|
|
func Example_mrtr() {
|
|
ctx := context.Background()
|
|
|
|
// Server: a "greet" tool that asks the user for their name before
|
|
// returning a greeting. The handler runs twice per logical call:
|
|
// once to issue the elicitation, once to consume the response.
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
mcp.AddTool(s, &mcp.Tool{Name: "greet"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
|
|
if len(req.Params.InputResponses) == 0 {
|
|
// First call: ask the user for their name. The map key
|
|
// ("user_name") is a label the handler chooses; the client must
|
|
// echo it back in InputResponses. The wire-level method name
|
|
// ("elicitation/create") is derived from the value's Go type.
|
|
return &mcp.CallToolResult{
|
|
InputRequests: mcp.InputRequestMap{
|
|
"user_name": &mcp.ElicitParams{
|
|
Message: "What's your name?",
|
|
RequestedSchema: &jsonschema.Schema{
|
|
Type: "object",
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"name": {Type: "string"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// RequestState is an opaque token the client echoes back on
|
|
// the retry, letting the handler resume its work without
|
|
// per-session storage.
|
|
RequestState: "step=1",
|
|
}, nil, nil
|
|
}
|
|
// Retry: read the elicitation response and produce the final greeting.
|
|
name := req.Params.InputResponses["user_name"].(*mcp.ElicitResult).Content["name"].(string)
|
|
return &mcp.CallToolResult{
|
|
Content: []mcp.Content{&mcp.TextContent{Text: "Hello " + name}},
|
|
}, nil, nil
|
|
})
|
|
|
|
// Client: declares an elicitation handler. The SDK's MRTR middleware
|
|
// uses it to fulfil any input request the tool handler returns.
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
|
|
ElicitationHandler: func(_ context.Context, _ *mcp.ElicitRequest) (*mcp.ElicitResult, error) {
|
|
return &mcp.ElicitResult{Action: "accept", Content: map[string]any{"name": "MCP Go"}}, nil
|
|
},
|
|
})
|
|
|
|
ct, st := mcp.NewInMemoryTransports()
|
|
if _, err := s.Connect(ctx, st, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cs, err := c.Connect(ctx, ct, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
// Single call site. The MRTR round trip is invisible to the caller.
|
|
res, err := cs.CallTool(ctx, &mcp.CallToolParams{Name: "greet"})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println(res.Content[0].(*mcp.TextContent).Text)
|
|
}
|
|
```
|
|
|
|
## Cacheable list results
|
|
|
|
[SEP-2549](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549)
|
|
adds `ttlMs` and `cacheScope` fields to the results of `tools/list`,
|
|
`prompts/list`, `resources/list`, `resources/templates/list`, and
|
|
`resources/read`. Both fields complement existing list
|
|
results: clients use `ttlMs` as a freshness hint to reduce polling,
|
|
and `cacheScope` (`"public"` or `"private"`) controls whether shared
|
|
intermediaries may cache the response.
|
|
|
|
**Server-side defaults.** The SDK fills in `CacheScope = "public"` whenever a
|
|
result leaves it empty, since the field is required on the wire. Everything
|
|
else is left as produced: a `ResourceHandler` that sets `TTLMs` or `CacheScope`
|
|
on its `ReadResourceResult` keeps those values. The list results and
|
|
`server/discover` are built by the SDK itself, so they start out with
|
|
`TTLMs = 0`, which a client cache treats as "immediately stale".
|
|
|
|
**Setting a policy.** `ServerOptions.SetCacheable` decides the fields for every
|
|
result that carries them. It runs after the corresponding handler, receives
|
|
what that handler produced, and may keep, adjust, or replace it. The request is
|
|
passed in so that policy can vary by method, session, or authorization context.
|
|
|
|
```go
|
|
server := mcp.NewServer(impl, &mcp.ServerOptions{
|
|
SetCacheable: func(_ context.Context, req mcp.Request, c *mcp.Cacheable) {
|
|
// A 30s freshness hint, except where the handler chose its own.
|
|
if c.TTLMs == 0 {
|
|
c.TTLMs = 30_000
|
|
}
|
|
},
|
|
})
|
|
```
|
|
|
|
`SetCacheable` may run with the server's lock held, so it must not call back
|
|
into the `Server`: adding or removing a feature, or ranging over
|
|
`Server.Sessions`, deadlocks.
|
|
|
|
## Utilities
|
|
|
|
### Completion
|
|
|
|
To support the
|
|
[completion](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion)
|
|
capability, the server needs a completion handler.
|
|
|
|
**Client-side**: completion is called using the
|
|
[`ClientSession.Complete`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Complete)
|
|
method.
|
|
|
|
**Server-side**: completion is enabled by setting
|
|
[`ServerOptions.CompletionHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.CompletionHandler).
|
|
If this field is set to a non-nil value, the server will advertise the
|
|
`completions` server capability, and use this handler to respond to completion
|
|
requests.
|
|
|
|
```go
|
|
myCompletionHandler := func(_ context.Context, req *mcp.CompleteRequest) (*mcp.CompleteResult, error) {
|
|
// In a real application, you'd implement actual completion logic here.
|
|
// For this example, we return a fixed set of suggestions.
|
|
var suggestions []string
|
|
switch req.Params.Ref.Type {
|
|
case "ref/prompt":
|
|
suggestions = []string{"suggestion1", "suggestion2", "suggestion3"}
|
|
case "ref/resource":
|
|
suggestions = []string{"suggestion4", "suggestion5", "suggestion6"}
|
|
default:
|
|
return nil, fmt.Errorf("unrecognized content type %s", req.Params.Ref.Type)
|
|
}
|
|
|
|
return &mcp.CompleteResult{
|
|
Completion: mcp.CompletionResultDetails{
|
|
HasMore: false,
|
|
Total: len(suggestions),
|
|
Values: suggestions,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// Create the MCP Server instance and assign the handler.
|
|
// No server running, just showing the configuration.
|
|
_ = mcp.NewServer(&mcp.Implementation{Name: "server"}, &mcp.ServerOptions{
|
|
CompletionHandler: myCompletionHandler,
|
|
})
|
|
```
|
|
|
|
### Logging
|
|
|
|
> **Note:** The logging feature is deprecated as of protocol version
|
|
> 2026-07-28
|
|
> ([SEP-2577](https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging)).
|
|
> It remains fully functional during the deprecation window (at least twelve
|
|
> months). The SDK continues to support logging for compatibility. Servers
|
|
> should migrate to stderr logging (for STDIO transports) or OpenTelemetry.
|
|
|
|
MCP servers can send logging messages to MCP clients.
|
|
(This form of logging is distinct from server-side logging, where the
|
|
server produces logs that remain server-side, for use by server maintainers.)
|
|
|
|
**Server-side**:
|
|
The minimum log level is part of the server state.
|
|
For stateful sessions, there is no default log level: no log messages will be sent
|
|
until the client calls `SetLevel` (see below).
|
|
For stateful sessions, the level defaults to "info".
|
|
|
|
[`ServerSession.Log`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.Log) is the low-level way for servers to log to clients.
|
|
It sends a logging notification to the client if the level of the message
|
|
is at least the minimum log level.
|
|
|
|
For a simpler API, use [`NewLoggingHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#NewLoggingHandler) to obtain a [`slog.Handler`](https://pkg.go.dev/log/slog#Handler).
|
|
By setting [`LoggingHandlerOptions.MinInterval`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#LoggingHandlerOptions.MinInterval), the handler can be rate-limited
|
|
to avoid spamming clients with too many messages.
|
|
|
|
Servers always report the logging capability.
|
|
|
|
|
|
**Client-side**:
|
|
Set [`ClientOptions.LoggingMessageHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.LoggingMessageHandler) to receive log messages.
|
|
|
|
Call [`ClientSession.SetLevel`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.SetLevel) to change the log level for a session.
|
|
|
|
```go
|
|
func Example_logging() {
|
|
ctx := context.Background()
|
|
|
|
// Create a server.
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
|
|
// Create a client that displays log messages.
|
|
done := make(chan struct{}) // solely for the example
|
|
var nmsgs atomic.Int32
|
|
c := mcp.NewClient(
|
|
&mcp.Implementation{Name: "client", Version: "v0.0.1"},
|
|
&mcp.ClientOptions{
|
|
LoggingMessageHandler: func(_ context.Context, r *mcp.LoggingMessageRequest) {
|
|
m := r.Params.Data.(map[string]any)
|
|
fmt.Println(m["msg"], m["value"])
|
|
if nmsgs.Add(1) == 2 { // number depends on logger calls below
|
|
close(done)
|
|
}
|
|
},
|
|
})
|
|
|
|
// Connect the server and client.
|
|
t1, t2 := mcp.NewInMemoryTransports()
|
|
ss, err := s.Connect(ctx, t1, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer ss.Close()
|
|
cs, err := c.Connect(ctx, t2, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
// Set the minimum log level to "info".
|
|
if err := cs.SetLoggingLevel(ctx, &mcp.SetLoggingLevelParams{Level: "info"}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Get a slog.Logger for the server session.
|
|
logger := slog.New(mcp.NewLoggingHandler(ss, nil))
|
|
|
|
// Log some things.
|
|
logger.Info("info shows up", "value", 1)
|
|
logger.Debug("debug doesn't show up", "value", 2)
|
|
logger.Warn("warn shows up", "value", 3)
|
|
|
|
// Wait for them to arrive on the client.
|
|
// In a real application, the log messages would appear asynchronously
|
|
// while other work was happening.
|
|
<-done
|
|
|
|
// Output:
|
|
// info shows up 1
|
|
// warn shows up 3
|
|
}
|
|
```
|
|
|
|
## Capabilities
|
|
|
|
Server capabilities are advertised to clients during the initialization
|
|
handshake. By default, the SDK advertises only the `logging` capability.
|
|
Additional capabilities are automatically added when features are registered
|
|
(e.g., adding a tool adds the `tools` capability).
|
|
|
|
### Capability inference
|
|
|
|
When features such as tools, prompts, or resources are added to the server
|
|
(e.g., via `Server.AddTool`), their capability is automatically inferred, with
|
|
default value `{listChanged:true}`. Similarly, if the
|
|
`ServerOptions.SubscribeHandler` or `ServerOptions.CompletionHandler` are set,
|
|
the corresponding capability is added.
|
|
|
|
### Explicit capabilities
|
|
|
|
To explicitly declare capabilities, or to override the [default inferred
|
|
capability](#capability-inference), set
|
|
[`ServerOptions.Capabilities`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.Capabilities).
|
|
This sets the default server capabilities, before any capabilities are added
|
|
based on configured handlers. If a capability is already present as a field in
|
|
`Capabilities`, adding a feature or handler will not change its configuration.
|
|
|
|
This allows you to:
|
|
|
|
- **Disable default capabilities**: Pass an empty `&ServerCapabilities{}` to
|
|
disable all defaults, including logging.
|
|
- **Disable listChanged notifications**: Set `ListChanged: false` on a
|
|
capability to prevent the server from sending list-changed notifications
|
|
when features are added or removed.
|
|
- **Pre-declare capabilities**: Declare capabilities before features are
|
|
registered, useful for servers that load features dynamically.
|
|
|
|
```go
|
|
// Disable listChanged notifications for tools
|
|
server := mcp.NewServer(impl, &mcp.ServerOptions{
|
|
Capabilities: &mcp.ServerCapabilities{
|
|
Logging: &mcp.LoggingCapabilities{},
|
|
Tools: &mcp.ToolCapabilities{ListChanged: false},
|
|
},
|
|
})
|
|
```
|
|
|
|
**Deprecated**: The `HasPrompts`, `HasResources`, and `HasTools` fields on
|
|
`ServerOptions` are deprecated. Use `Capabilities` instead.
|
|
|
|
### Extensions
|
|
|
|
[SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)
|
|
adds an `extensions` map to `ServerCapabilities` so that optional
|
|
capabilities outside the core protocol can be declared on the wire. Keys
|
|
are namespaced as `"{vendor-prefix}/{extension-name}"`; values are
|
|
per-extension settings objects.
|
|
|
|
### Pagination
|
|
|
|
Server-side feature lists may be
|
|
[paginated](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/pagination),
|
|
using cursors. The SDK supports this by default.
|
|
|
|
**Client-side**: The `ClientSession` provides methods returning
|
|
[iterators](https://go.dev/blog/range-functions) for each feature type.
|
|
These iterators are an `iter.Seq2[Feature, error]`, where the error value
|
|
indicates whether page retrieval failed.
|
|
|
|
- [`ClientSession.Prompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Prompts)
|
|
iterates prompts.
|
|
- [`ClientSession.Resource`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Resource)
|
|
iterates resources.
|
|
- [`ClientSession.ResourceTemplates`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ResourceTemplates)
|
|
iterates resource templates.
|
|
- [`ClientSession.Tools`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Tools)
|
|
iterates tools.
|
|
|
|
The `ClientSession` also exposes `ListXXX` methods for fine-grained control
|
|
over pagination.
|
|
|
|
**Server-side**: pagination is on by default, so in general nothing is required
|
|
server-side. However, you may use
|
|
[`ServerOptions.PageSize`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.PageSize)
|
|
to customize the page size.
|