go-sdk/docs/client.md
Tony Dzi (Anton Dziatkovskii) 64e454e35c
docs: fix four dead links to the Multi Round-Trip Requests section (#1159)
### What was broken

Four links in `docs/client.md` point at a section that does not exist.

Three of them sit right where a reader needs them — at the end of the
**Roots**, **Sampling**
and **Elicitation** sections, on the sentence explaining that from
protocol version
`2026-07-28` these requests are delivered via the Multi Round-Trip
Requests pattern:

```
[Multi Round-Trip Requests](protocol.md#multi-round-trip-requests-mrtr)
```

`protocol.md` has no MRTR section. Following the link drops you at the
top of a page that
never mentions the pattern — while the explanation you were promised is
one screen further
down the *same* page you were already on (`client.md` → *Multi
Round-Trip Requests*).

The fourth one is in that MRTR section itself, sending you to the server
side:

```
[server-side documentation](server.md#multi-round-trip-requests-mrtr).
```

`server.md` does have the section, but its heading is `## Multi
Round-Trip Requests`, so the
slug is `#multi-round-trip-requests` — the trailing `-mrtr` is stale.

### How I checked

Against the published site, not just the source:

```
$ curl -sL https://go.sdk.modelcontextprotocol.io/protocol | grep -o 'id="[^"]*round[^"]*"'
            # (no output)
$ curl -sL https://go.sdk.modelcontextprotocol.io/client  | grep -o 'id="[^"]*round[^"]*"'
id="multi-round-trip-requests"
$ curl -sL https://go.sdk.modelcontextprotocol.io/server  | grep -o 'id="[^"]*round[^"]*"'
id="multi-round-trip-requests"
```

So `/protocol` carries no anchor containing `round` at all, and both
live targets use the
plain slug.

`git log -S` says the links arrived with b9e6fc5 ("mcp: add
documentation for new protocol
version", #1019, 2026-06-23) and that `protocol.src.md` has never
contained a
`Multi Round-Trip Requests` heading — the section landed in
`client.src.md` and
`server.src.md` instead, and these four references were never repointed.

I also ran a small link/anchor checker over `docs/`: 4 bad anchors
before, 0 after. (Two
"broken links" it also reports in `server.md` are its own false
positives — Go generics
inside a fence, `jsonschema.For[WeatherInput](opts)`, read as markdown
link syntax. Not
touched.)

### What it is now

The three per-feature links become same-page references, matching how
`client.md` already
links its own sections (`(see [Capabilities](#capabilities))`):

```
[Multi Round-Trip Requests](#multi-round-trip-requests)
```

and the cross-page one drops the stale suffix:

```
[server-side documentation](server.md#multi-round-trip-requests).
```

Change is in `internal/docs/client.src.md`; `docs/client.md` regenerated
with
`go generate ./...`, both committed together. Locally `go generate
./...` leaves the tree
clean afterwards, which is what `docs-check` asserts. Docs-only, four
link targets, no prose
and no code paths touched.

---
Assisted-by: Claude Opus 5 — this PR was drafted and verified by Anton's
AI cofounder running
on his account; every command and output above is from a real run on
this machine, not a
reconstruction.

Co-authored-by: tonydzi <dzyatkovskiy.a@gmail.com>
2026-08-13 10:49:56 +02:00

14 KiB

Support for MCP client features

  1. Roots
  2. Sampling
  3. Elicitation
  4. Multi Round-Trip Requests
  5. Capabilities
    1. Capability inference
    2. Explicit capabilities
    3. Extensions

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:

  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; 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: 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.
// 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.