The "Schema defaults and enums" section of docs/client.md links SEP-1034 to https://modelcontextprotocol.io/seps/1034 and SEP-1330 to https://modelcontextprotocol.io/seps/1330, and both of those bare-number URLs are dead. modelcontextprotocol.io serves SEPs at number-plus-slug URLs, which is what every other SEP link in this repo already uses (seps/2575-stateless-mcp, seps/2577-deprecate-roots-sampling-and-logging, seps/2596-feature-lifecycle-and-deprecation-policy, seps/2207-oidc-refresh-token-guidance); these two were the only bare-number ones left. I took the replacement slugs from the site's own sitemap.xml, so the `1034--` double hyphen is what is published rather than a typo here. curl -sIL on the two old URLs returns 404 and 404, and on the two new ones 200 and 200. The edit is in internal/docs/client.src.md with docs/client.md regenerated by `go generate ./internal/docs`, and `go build ./... && go test ./...` pass. Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
548 lines
21 KiB
Markdown
548 lines
21 KiB
Markdown
<!-- Autogenerated by weave; DO NOT EDIT -->
|
|
# Support for MCP client features
|
|
|
|
1. [Roots](#roots)
|
|
1. [Roots list changed](#roots-list-changed)
|
|
1. [Sampling](#sampling)
|
|
1. [Elicitation](#elicitation)
|
|
1. [Schema defaults and enums](#schema-defaults-and-enums)
|
|
1. [Completing a URL elicitation](#completing-a-url-elicitation)
|
|
1. [Multi Round-Trip Requests](#multi-round-trip-requests)
|
|
1. [Capabilities](#capabilities)
|
|
1. [Capability inference](#capability-inference)
|
|
1. [Explicit capabilities](#explicit-capabilities)
|
|
1. [Extensions](#extensions)
|
|
|
|
## Roots
|
|
|
|
> **Note:** The roots 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 roots for compatibility. New code
|
|
> should pass paths via tool parameters, resource URIs, or configuration
|
|
> instead.
|
|
|
|
MCP allows clients to specify a set of filesystem
|
|
["roots"](https://modelcontextprotocol.io/specification/2025-06-18/client/roots).
|
|
The SDK supports this as follows:
|
|
|
|
**Client-side**: The SDK client always has the `roots.listChanged` capability.
|
|
To add roots to a client, use the
|
|
[`Client.AddRoots`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Client.AddRoots)
|
|
and
|
|
[`Client.RemoveRoots`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Client.RemoveRoots)
|
|
methods. If any servers are already [connected](protocol.md#lifecycle) to the
|
|
client, a call to `AddRoot` or `RemoveRoots` will result in a
|
|
`notifications/roots/list_changed` notification to each connected server.
|
|
|
|
**Server-side**: To query roots from the server, use the
|
|
[`ServerSession.ListRoots`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.ListRoots)
|
|
method. To receive notifications about root changes, set
|
|
[`ServerOptions.RootsListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.RootsListChangedHandler).
|
|
For protocol versions `2026-07-28` and later, `ListRoots` requests are
|
|
delivered via the
|
|
[Multi Round-Trip Requests](#multi-round-trip-requests)
|
|
pattern.
|
|
|
|
```go
|
|
func Example_roots() {
|
|
ctx := context.Background()
|
|
|
|
// Create a client with two roots.
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
|
|
c.AddRoots(&mcp.Root{URI: "file://a"}, &mcp.Root{URI: "file://b"})
|
|
|
|
// Create a server with a tool that requests roots via the multi round-trip
|
|
// pattern (SEP-2322): server-to-client requests are no longer sent as
|
|
// standalone JSON-RPC calls on protocol version >= 2026-07-28.
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
mcp.AddTool(s, &mcp.Tool{Name: "roots"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
|
|
if len(req.Params.InputResponses) == 0 {
|
|
return &mcp.CallToolResult{
|
|
InputRequests: mcp.InputRequestMap{"roots": &mcp.ListRootsParams{}},
|
|
}, nil, nil
|
|
}
|
|
rootList := req.Params.InputResponses["roots"].(*mcp.ListRootsResult)
|
|
var roots []string
|
|
for _, root := range rootList.Roots {
|
|
roots = append(roots, root.URI)
|
|
}
|
|
fmt.Println(roots)
|
|
return &mcp.CallToolResult{}, nil, nil
|
|
})
|
|
|
|
// Connect the server and client...
|
|
t1, t2 := mcp.NewInMemoryTransports()
|
|
serverSession, err := s.Connect(ctx, t1, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer serverSession.Close()
|
|
|
|
clientSession, err := c.Connect(ctx, t2, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer clientSession.Close()
|
|
|
|
// ...and call the tool. The client's multi round-trip driver fulfils the
|
|
// embedded roots/list request and retries the call.
|
|
if _, err := clientSession.CallTool(ctx, &mcp.CallToolParams{Name: "roots"}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// Output: [file://a file://b]
|
|
}
|
|
```
|
|
|
|
### Roots list changed
|
|
|
|
`Client.AddRoots` and `Client.RemoveRoots` notify every connected server that
|
|
the list changed. Servers observe this through
|
|
[`ServerOptions.RootsListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.RootsListChangedHandler);
|
|
as with the server-side list-changed notifications, it reports only that
|
|
something changed, so read the list back with `ServerSession.ListRoots`.
|
|
|
|
```go
|
|
func Example_rootsListChanged() {
|
|
ctx := context.Background()
|
|
|
|
changed := make(chan struct{}, 2)
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
|
|
RootsListChangedHandler: func(context.Context, *mcp.RootsListChangedRequest) {
|
|
changed <- struct{}{}
|
|
},
|
|
})
|
|
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
|
|
c.AddRoots(&mcp.Root{URI: "file:///project"})
|
|
|
|
t1, t2 := mcp.NewInMemoryTransports()
|
|
ss, err := s.Connect(ctx, t1, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer ss.Close()
|
|
|
|
// ListRoots is a server-initiated request, so this session negotiates a
|
|
// protocol version that still allows one.
|
|
cs, err := c.Connect(ctx, t2, &mcp.ClientSessionOptions{ProtocolVersion: "2025-11-25"})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
// Roots added after the client connects notify every connected server.
|
|
c.AddRoots(&mcp.Root{URI: "file:///scratch"})
|
|
<-changed
|
|
|
|
// The notification says only that the list changed, so read it back.
|
|
res, err := ss.ListRoots(ctx, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
for _, root := range res.Roots {
|
|
fmt.Println(root.URI)
|
|
}
|
|
|
|
c.RemoveRoots("file:///scratch")
|
|
<-changed
|
|
fmt.Println("roots changed again")
|
|
|
|
// Output:
|
|
// file:///project
|
|
// file:///scratch
|
|
// roots changed again
|
|
}
|
|
```
|
|
|
|
## Sampling
|
|
|
|
> **Note:** The sampling 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 sampling for compatibility. Servers
|
|
> that need LLM completions should call LLM provider APIs directly.
|
|
|
|
[Sampling](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling)
|
|
is a way for servers to leverage the client's AI capabilities. It is
|
|
implemented in the SDK as follows:
|
|
|
|
**Client-side**: To add the `sampling` capability to a client, set
|
|
[`ClientOptions.CreateMessageHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.CreateMessageHandler).
|
|
This function is invoked whenever the server requests sampling.
|
|
|
|
**Server-side**: To use sampling from the server, call
|
|
[`ServerSession.CreateMessage`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.CreateMessage).
|
|
|
|
For protocol versions `2026-07-28` and later, sampling requests are
|
|
delivered via the
|
|
[Multi Round-Trip Requests](#multi-round-trip-requests)
|
|
pattern.
|
|
|
|
```go
|
|
func Example_sampling() {
|
|
ctx := context.Background()
|
|
|
|
// Create a client with a sampling handler.
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
|
|
CreateMessageHandler: func(_ context.Context, req *mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
|
|
return &mcp.CreateMessageResult{
|
|
Content: &mcp.TextContent{
|
|
Text: "would have created a message",
|
|
},
|
|
}, nil
|
|
},
|
|
})
|
|
|
|
// Connect the server and client...
|
|
ct, st := mcp.NewInMemoryTransports()
|
|
// Create a server with a tool that requests sampling via the multi
|
|
// round-trip pattern (SEP-2322): server-to-client requests are no longer
|
|
// sent as standalone JSON-RPC calls on protocol version >= 2026-07-28.
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
mcp.AddTool(s, &mcp.Tool{Name: "sample"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
|
|
if len(req.Params.InputResponses) == 0 {
|
|
return &mcp.CallToolResult{
|
|
InputRequests: mcp.InputRequestMap{"msg": &mcp.CreateMessageParams{}},
|
|
}, nil, nil
|
|
}
|
|
msg := req.Params.InputResponses["msg"].(*mcp.CreateMessageWithToolsResult)
|
|
return &mcp.CallToolResult{Content: msg.Content}, nil, nil
|
|
})
|
|
session, err := s.Connect(ctx, st, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer session.Close()
|
|
|
|
clientSession, err := c.Connect(ctx, ct, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
res, err := clientSession.CallTool(ctx, &mcp.CallToolParams{Name: "sample"})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println(res.Content[0].(*mcp.TextContent).Text)
|
|
// Output: would have created a message
|
|
}
|
|
```
|
|
|
|
## Elicitation
|
|
|
|
[Elicitation](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation)
|
|
allows servers to request user inputs. It is implemented in the SDK as follows:
|
|
|
|
**Client-side**: To add the `elicitation` capability to a client, set
|
|
[`ClientOptions.ElicitationHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ElicitationHandler).
|
|
The elicitation handler must return a result that matches the requested schema;
|
|
otherwise, elicitation returns an error. If your handler supports [URL mode
|
|
elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-mode-elicitation-requests),
|
|
you must declare that capability explicitly (see [Capabilities](#capabilities))
|
|
|
|
**Server-side**: To use elicitation from the server, call
|
|
[`ServerSession.Elicit`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.Elicit).
|
|
|
|
For protocol versions `2026-07-28` and later, elicitation requests are
|
|
delivered via the
|
|
[Multi Round-Trip Requests](#multi-round-trip-requests)
|
|
pattern.
|
|
|
|
```go
|
|
func Example_elicitation() {
|
|
ctx := context.Background()
|
|
ct, st := mcp.NewInMemoryTransports()
|
|
|
|
// Create a server with a tool that requests elicitation via the multi
|
|
// round-trip pattern (SEP-2322): server-to-client requests are no longer
|
|
// sent as standalone JSON-RPC calls on protocol version >= 2026-07-28.
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
mcp.AddTool(s, &mcp.Tool{Name: "ask"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
|
|
if len(req.Params.InputResponses) == 0 {
|
|
return &mcp.CallToolResult{
|
|
InputRequests: mcp.InputRequestMap{"input": &mcp.ElicitParams{
|
|
Message: "This should fail",
|
|
RequestedSchema: &jsonschema.Schema{
|
|
Type: "object",
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"test": {Type: "string"},
|
|
},
|
|
},
|
|
}},
|
|
}, nil, nil
|
|
}
|
|
res := req.Params.InputResponses["input"].(*mcp.ElicitResult)
|
|
fmt.Println(res.Content["test"])
|
|
return &mcp.CallToolResult{}, nil, nil
|
|
})
|
|
ss, err := s.Connect(ctx, st, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer ss.Close()
|
|
|
|
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{"test": "value"}}, nil
|
|
},
|
|
})
|
|
clientSession, err := c.Connect(ctx, ct, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if _, err := clientSession.CallTool(ctx, &mcp.CallToolParams{Name: "ask"}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// Output: value
|
|
}
|
|
```
|
|
|
|
### Schema defaults and enums
|
|
|
|
`ElicitParams.RequestedSchema` is a flat schema of primitive fields, which the
|
|
client renders as a form. Two field keywords shape that form.
|
|
|
|
A `Default` ([SEP-1034](https://modelcontextprotocol.io/seps/1034--support-default-values-for-all-primitive-types-in)) prefills a
|
|
field. When the user accepts without supplying it, the SDK fills the field in
|
|
from the schema before the result reaches either side's caller — the client
|
|
does so after its elicitation handler returns, and `ServerSession.Elicit` does
|
|
so again on receipt. This is unconditional; there is no opt-in flag. Marking a
|
|
defaulted field `Required` defeats it: accepted content is validated against
|
|
the schema before defaults are applied, so an answer that omits the field is
|
|
rejected rather than defaulted.
|
|
|
|
An `Enum` ([SEP-1330](https://modelcontextprotocol.io/seps/1330-elicitation-enum-schema-improvements-and-standards)) restricts a
|
|
field to a fixed set of values, which the client renders as a choice. Enums
|
|
are supported only on `"string"` fields; declaring one on another type is
|
|
rejected. To label the choices, set the legacy `enumNames` keyword through
|
|
`Schema.Extra`, with exactly one name per enum value — a mismatched length is
|
|
rejected.
|
|
|
|
```go
|
|
func Example_elicitationSchema() {
|
|
ctx := context.Background()
|
|
ct, st := mcp.NewInMemoryTransports()
|
|
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
mcp.AddTool(s, &mcp.Tool{Name: "export_report"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
|
|
if len(req.Params.InputResponses) == 0 {
|
|
return &mcp.CallToolResult{
|
|
InputRequests: mcp.InputRequestMap{"format": &mcp.ElicitParams{
|
|
Message: "Export quarterly-sales as which format?",
|
|
RequestedSchema: &jsonschema.Schema{
|
|
Type: "object",
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"format": {
|
|
Type: "string",
|
|
Title: "Format",
|
|
Enum: []any{"pdf", "csv"},
|
|
Default: json.RawMessage(`"pdf"`),
|
|
Extra: map[string]any{"enumNames": []any{"PDF document", "CSV spreadsheet"}},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
}, nil, nil
|
|
}
|
|
res := req.Params.InputResponses["format"].(*mcp.ElicitResult)
|
|
return &mcp.CallToolResult{
|
|
Content: []mcp.Content{&mcp.TextContent{Text: "Exported as " + res.Content["format"].(string)}},
|
|
}, nil, nil
|
|
})
|
|
if _, err := s.Connect(ctx, st, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// The user accepts without filling anything in.
|
|
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{}}, nil
|
|
},
|
|
})
|
|
cs, err := c.Connect(ctx, ct, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
res, err := cs.CallTool(ctx, &mcp.CallToolParams{Name: "export_report"})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println(res.Content[0].(*mcp.TextContent).Text)
|
|
// Output: Exported as pdf
|
|
}
|
|
```
|
|
|
|
### Completing a URL elicitation
|
|
|
|
In URL mode the user finishes out of band, in a browser, so nothing in the
|
|
elicitation result tells the client when they are done. The server signals that
|
|
with
|
|
[`ServerSession.NotifyElicitationComplete`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.NotifyElicitationComplete),
|
|
passing the same `ElicitationID` the request carried; the client observes it
|
|
through
|
|
[`ClientOptions.ElicitationCompleteHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ElicitationCompleteHandler).
|
|
Send it from whatever endpoint the hosted flow redirects back to.
|
|
|
|
The notification matters most when a handler rejects a request with
|
|
[`URLElicitationRequiredError`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#URLElicitationRequiredError):
|
|
the client parks the original request until a notification names that
|
|
`ElicitationID`, and then retries it automatically. Until one arrives, the
|
|
client waits.
|
|
|
|
```go
|
|
func Example_elicitationComplete() {
|
|
ctx := context.Background()
|
|
ct, st := mcp.NewInMemoryTransports()
|
|
|
|
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
|
ss, err := s.Connect(ctx, st, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer ss.Close()
|
|
|
|
done := make(chan struct{})
|
|
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
|
|
Capabilities: &mcp.ClientCapabilities{
|
|
Elicitation: &mcp.ElicitationCapabilities{URL: &mcp.URLElicitationCapabilities{}},
|
|
},
|
|
ElicitationHandler: func(_ context.Context, req *mcp.ElicitRequest) (*mcp.ElicitResult, error) {
|
|
fmt.Println("opening", req.Params.URL)
|
|
return &mcp.ElicitResult{Action: "accept"}, nil
|
|
},
|
|
ElicitationCompleteHandler: func(_ context.Context, req *mcp.ElicitationCompleteNotificationRequest) {
|
|
fmt.Println("flow finished:", req.Params.ElicitationID)
|
|
close(done)
|
|
},
|
|
})
|
|
cs, err := c.Connect(ctx, ct, &mcp.ClientSessionOptions{ProtocolVersion: "2025-11-25"})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer cs.Close()
|
|
|
|
const elicitationID = "connect-calendar-1"
|
|
if _, err := ss.Elicit(ctx, &mcp.ElicitParams{
|
|
Message: "Grant calendar access",
|
|
URL: "https://calendar.example.com/consent?state=" + elicitationID,
|
|
ElicitationID: elicitationID,
|
|
}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// The hosted page redirects back to the server, whose callback endpoint
|
|
// signals that the user is done.
|
|
if err := ss.NotifyElicitationComplete(ctx, &mcp.ElicitationCompleteParams{ElicitationID: elicitationID}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
<-done
|
|
|
|
// Output:
|
|
// opening https://calendar.example.com/consent?state=connect-calendar-1
|
|
// flow finished: connect-calendar-1
|
|
}
|
|
```
|
|
|
|
## Multi Round-Trip Requests
|
|
|
|
[SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)
|
|
introduces the MRTR pattern: server-to-client requests for sampling,
|
|
elicitation, and roots are no longer issued as fresh JSON-RPC requests but
|
|
are carried inside the in-flight reply of a `tools/call`, `prompts/get`, or
|
|
`resources/read`. The client must respond by retrying the original request
|
|
with the produced responses.
|
|
|
|
The SDK installs `clientMultiRoundTripMiddleware` for every client by
|
|
default. The middleware:
|
|
|
|
1. Inspects each `tools/call`/`prompts/get`/`resources/read` reply.
|
|
2. If the result's `NeedsInput()` is true, fans out the `InputRequests` map
|
|
concurrently, calling the configured handler for each (`elicit`,
|
|
`createMessage`/`createMessageWithTools`, or `listRoots`).
|
|
3. Threads the server-supplied opaque `RequestState` back unchanged.
|
|
4. Retries the original request with the responses set, repeating until the
|
|
result no longer needs input.
|
|
|
|
The middleware is enabled by default. To opt out, set
|
|
[`ClientOptions.MultiRoundTrip.Disabled = true`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#MultiRoundTripOptions);
|
|
the client will then surface input-required results directly to the caller
|
|
(the returned `CallToolResult`, `GetPromptResult`, or `ReadResourceResult`
|
|
will report `NeedsInput() == true` and expose the server's `InputRequests`
|
|
and opaque `RequestState`). Your code must fulfil each request and re-issue
|
|
the original call with `InputResponses` set and `RequestState` echoed
|
|
back.
|
|
|
|
For legacy (`<= 2025-11-25`) servers, the SDK transparently sends server
|
|
requests on the legacy server-initiated channel; the MRTR machinery is a
|
|
no-op in that direction. For legacy clients talking to MRTR-style servers,
|
|
the server SDK applies the inverse compatibility shim — see the
|
|
[server-side documentation](server.md#multi-round-trip-requests).
|
|
|
|
## Capabilities
|
|
|
|
Client capabilities are advertised to servers during the initialization
|
|
handshake. [By default](rough_edges.md), the SDK advertises the `logging`
|
|
capability. Additional capabilities are automatically added when server
|
|
features are added (e.g. via `AddTool`), or when handlers are set in the
|
|
`ServerOptions` struct (e.g., setting `CompletionHandler` adds the
|
|
`completions` capability), or may be configured explicitly.
|
|
|
|
### Capability inference
|
|
|
|
When handlers are set on `ClientOptions` (e.g., `CreateMessageHandler` or
|
|
`ElicitationHandler`), the corresponding capability is automatically added if
|
|
not already present, with a default configuration.
|
|
|
|
For elicitation, if the handler is set but no `Capabilities.Elicitation` is
|
|
specified, the client defaults to form elicitation. To enable URL elicitation
|
|
or both modes, [configure `Capabilities.Elicitation`
|
|
explicitly](#explicit-capabilities).
|
|
|
|
See the [`ClientCapabilities`
|
|
documentation](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientCapabilities)
|
|
for further details on inference.
|
|
|
|
### Explicit capabilities
|
|
|
|
To explicitly declare capabilities, or to override the [default inferred
|
|
capability](#capability-inference), set
|
|
[`ClientOptions.Capabilities`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.Capabilities).
|
|
This sets the initial client capabilities, before any capabilities are added
|
|
based on configured handlers. If a capability is already present in
|
|
`Capabilities`, adding a handler will not change its configuration.
|
|
|
|
This allows you to:
|
|
|
|
- **Disable default capabilities**: Pass an empty `&ClientCapabilities{}` to
|
|
disable all defaults, including roots.
|
|
- **Disable listChanged notifications**: Set `ListChanged: false` on a
|
|
capability to prevent the client from sending list-changed notifications
|
|
when roots are added or removed.
|
|
- **Configure elicitation modes**: Specify which elicitation modes (form, URL)
|
|
the client supports.
|
|
|
|
```go
|
|
// Configure elicitation modes and disable roots.
|
|
client := mcp.NewClient(impl, &mcp.ClientOptions{
|
|
Capabilities: &mcp.ClientCapabilities{
|
|
Elicitation: &mcp.ElicitationCapabilities{
|
|
Form: &mcp.FormElicitationCapabilities{},
|
|
URL: &mcp.URLElicitationCapabilities{},
|
|
},
|
|
},
|
|
ElicitationHandler: handler,
|
|
})
|
|
```
|
|
|
|
### Extensions
|
|
|
|
[SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)
|
|
adds an `extensions` map to `ClientCapabilities` and `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.
|
|
|