Support for MCP client features¶
Roots¶
Note: The roots feature is deprecated as of protocol version 2026-07-28 (SEP-2577). 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". 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
and
Client.RemoveRoots
methods. If any servers are already connected 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
method. To receive notifications about root changes, set
ServerOptions.RootsListChangedHandler.
For protocol versions 2026-07-28 and later, ListRoots requests are
delivered via the
Multi Round-Trip Requests
pattern.
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]
}
Sampling¶
Note: The sampling feature is deprecated as of protocol version 2026-07-28 (SEP-2577). 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 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.
This function is invoked whenever the server requests sampling.
Server-side: To use sampling from the server, call
ServerSession.CreateMessage.
For protocol versions 2026-07-28 and later, sampling requests are
delivered via the
Multi Round-Trip Requests
pattern.
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 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.
The elicitation handler must return a result that matches the requested schema;
otherwise, elicitation returns an error. If your handler supports URL mode
elicitation,
you must declare that capability explicitly (see Capabilities)
Server-side: To use elicitation from the server, call
ServerSession.Elicit.
For protocol versions 2026-07-28 and later, elicitation requests are
delivered via the
Multi Round-Trip Requests
pattern.
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
}
Multi Round-Trip Requests¶
SEP-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:
- Inspects each
tools/call/prompts/get/resources/readreply. - If the result's
NeedsInput()is true, fans out theInputRequestsmap concurrently, calling the configured handler for each (elicit,createMessage/createMessageWithTools, orlistRoots). - Threads the server-supplied opaque
RequestStateback unchanged. - 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;
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.
Capabilities¶
Client capabilities are advertised to servers during the initialization
handshake. By default, 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.
See the ClientCapabilities
documentation
for further details on inference.
Explicit capabilities¶
To explicitly declare capabilities, or to override the default inferred
capability, set
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: falseon 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.
// 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
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.