# 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.