diff --git a/README.md b/README.md index f9bf4eb..baeb3e7 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,12 @@ contains feature documentation, mapping the MCP spec to the packages above. The following table shows which versions of the Go SDK support which versions of the MCP specification: -| SDK Version | Latest MCP Spec | All Supported MCP Specs | -|-----------------|-------------------|----------------------------------------------------| -| v1.4.0+ | 2025-11-25\* | 2025-11-25\*, 2025-06-18, 2025-03-26, 2024-11-05 | -| v1.2.0 - v1.3.1 | 2025-11-25\*\* | 2025-11-25\*\*, 2025-06-18, 2025-03-26, 2024-11-05 | -| v1.0.0 - v1.1.0 | 2025-06-18 | 2025-06-18, 2025-03-26, 2024-11-05 | +| SDK Version | Latest MCP Spec | All Supported MCP Specs | +|-----------------|-------------------|------------------------------------------------------------------| +| v1.7.0+ | 2026-07-28 | 2026-07-28, 2025-11-25\*, 2025-06-18, 2025-03-26, 2024-11-05 | +| v1.4.0 - v1.6.1 | 2025-11-25\* | 2025-11-25\*, 2025-06-18, 2025-03-26, 2024-11-05 | +| v1.2.0 - v1.3.1 | 2025-11-25\*\* | 2025-11-25\*\*, 2025-06-18, 2025-03-26, 2024-11-05 | +| v1.0.0 - v1.1.0 | 2025-06-18 | 2025-06-18, 2025-03-26, 2024-11-05 | \* Client side OAuth has experimental support. diff --git a/docs/client.md b/docs/client.md index cb96ff9..b5d0032 100644 --- a/docs/client.md +++ b/docs/client.md @@ -4,9 +4,11 @@ 1. [Roots](#roots) 1. [Sampling](#sampling) 1. [Elicitation](#elicitation) +1. [Multi Round-Trip Requests (MRTR)](#multi-round-trip-requests-(mrtr)) 1. [Capabilities](#capabilities) 1. [Capability inference](#capability-inference) 1. [Explicit capabilities](#explicit-capabilities) + 1. [Extensions](#extensions) ## Roots @@ -34,6 +36,10 @@ client, a call to `AddRoot` or `RemoveRoots` will result in a [`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](protocol.md#multi-round-trip-requests-mrtr) +pattern. ```go func Example_roots() { @@ -102,6 +108,11 @@ 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](protocol.md#multi-round-trip-requests-mrtr) +pattern. + ```go func Example_sampling() { ctx := context.Background() @@ -154,6 +165,11 @@ 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](protocol.md#multi-round-trip-requests-mrtr) +pattern. + ```go func Example_elicitation() { ctx := context.Background() @@ -191,6 +207,37 @@ func Example_elicitation() { } ``` +## Multi Round-Trip Requests (MRTR) + +[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 `InputRequiredResult` values directly to the +caller and your code must fulfil the requests manually. + +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-mrtr). + ## Capabilities Client capabilities are advertised to servers during the initialization @@ -247,3 +294,11 @@ client := mcp.NewClient(impl, &mcp.ClientOptions{ }) ``` +### 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. + diff --git a/docs/protocol.md b/docs/protocol.md index f45d1df..5f043f2 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -2,6 +2,8 @@ # Support for the MCP base protocol 1. [Lifecycle](#lifecycle) + 1. [Discovery (`server/discover`)](#discovery-(server/discover)) + 1. [Per-request `_meta` keys](#per-request-meta-keys) 1. [Transports](#transports) 1. [Stdio Transport](#stdio-transport) 1. [Streamable Transport](#streamable-transport) @@ -20,20 +22,27 @@ 1. [Cancellation](#cancellation) 1. [Ping](#ping) 1. [Progress](#progress) + 1. [Error codes](#error-codes) ## Lifecycle The SDK provides an API for defining both MCP clients and servers, and connecting them over various transports. When a client and server are -connected, it creates a logical session, which follows the MCP spec's -[lifecycle](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle). +connected, it creates a logical session. -In this SDK, both a -[`Client`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Client) -and -[`Server`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server) -can handle multiple peers. Every time a new peer is connected, it creates a new -session. +The MCP specification defines two lifecycle models, and the SDK supports both +transparently based on the negotiated protocol version: + +- The **legacy `initialize` handshake** + ([lifecycle](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle)) + used by protocol versions through `2025-11-25`. +- A **stateless** model introduced in `2026-07-28` by + [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575), + in which there is no `initialize`/`notifications/initialized` handshake, and + each request carries its protocol version, client identity, and client + capabilities in `_meta`. + +In both models, the SDK exposes the same API: - A `Client` is a logical MCP client, configured with various [`ClientOptions`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions). @@ -49,11 +58,16 @@ session. [`Server.Connect`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.Connect), it creates a [`ServerSession`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession). - This session is not initialized until the client sends the - `notifications/initialized` message. Use `ServerOptions.InitializedHandler` - to listen for this event, or just use the session through various feature - handlers (such as a `ToolHandler`). Requests to the server are rejected until - the client has initialized the session. + +In the legacy model, the server session is not considered initialized until +the client sends the `notifications/initialized` message. Use +`ServerOptions.InitializedHandler` to listen for this event, or just use the +session through various feature handlers (such as a `ToolHandler`). Requests +to the server are rejected until the client has initialized the session. + +In the stateless model (`2026-07-28`+), there is no handshake. The server +processes the first request the moment it arrives, validates the per-request +`_meta` fields. Both `ClientSession` and `ServerSession` have a `Close` method to terminate the session, and a `Wait` method to await session termination by the peer. Typically, @@ -98,6 +112,41 @@ func Example_lifecycle() { } ``` +### Discovery (`server/discover`) + +Introduced in `2026-07-28` by +[SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575), +the `server/discover` RPC lets a client discover a server's supported +protocol versions, capabilities, and identity before issuing any other +request. Servers implementing `2026-07-28` MUST implement it. + +- **Server**: `Server.Connect` registers the + `server/discover` handler automatically; the response is computed from the + server's static configuration (capabilities, server info, instructions) and + the transport-filtered list of supported protocol versions. +- **Client**: `Client.Connect` calls `server/discover` first and + uses the result to negotiate a mutually supported version. If discovery + fails or the server does not support the latest version, the client falls back to the + legacy `initialize` handshake. + +### Per-request `_meta` keys + +When the negotiated protocol version is `2026-07-28` or later, every request +carries these keys inside its `_meta` map (constants live in +[`mcp/protocol.go`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#pkg-constants)): + +| Constant | Wire key | Type | +|---|---|---| +| `MetaKeyProtocolVersion` | `io.modelcontextprotocol/protocolVersion` | `string` | +| `MetaKeyClientInfo` | `io.modelcontextprotocol/clientInfo` | `*Implementation` | +| `MetaKeyClientCapabilities` | `io.modelcontextprotocol/clientCapabilities` | `*ClientCapabilities` | +| `MetaKeyLogLevel` | `io.modelcontextprotocol/logLevel` | `LoggingLevel` (deprecated by SEP-2577) | + +The client populates these keys automatically on every outgoing request. +Server-side handlers can read them +through `ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`, +and `ServerRequest[P].ClientCapabilities()`. + ## Transports A @@ -188,6 +237,39 @@ client, err := mcp.Connect(ctx, transport, &mcp.ClientOptions{...}) The `StreamableClientTransport` handles the HTTP requests and communicates with the server using the streamable transport protocol. +#### HTTP Headers + +[SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243) +standardises a small set of MCP-specific HTTP headers, sent and validated by +the SDK only for sessions negotiated on protocol version `2026-07-28` or +later. + +| Header | Direction | Purpose | +|---|---|---| +| `Mcp-Protocol-Version` | request | Mirrors `_meta.io.modelcontextprotocol/protocolVersion` from the body | +| `Mcp-Session-Id` | request/response | Logical session identifier (removed in stateless mode) | +| `Mcp-Method` | request | Mirrors the JSON-RPC `method` field; mismatch ⇒ `-32020` | +| `Mcp-Name` | request | Mirrors the request's principal name (`tools/call.params.name`, `prompts/get.params.name`, `resources/read.params.uri`); mismatch ⇒ `-32020` | +| `Mcp-Param-{Header}` | request | Per-tool parameter passthrough; see below | + +The header-name set is exhaustive: for protocol versions earlier than +`2026-07-28`, only `Mcp-Protocol-Version` and `Mcp-Session-Id` are recognised. + +**Body↔header mirroring.** When `Mcp-Method` or `Mcp-Name` is present but +does not match the JSON-RPC body, the server returns +[`CodeHeaderMismatch`](#error-codes) (`-32020`). The same applies to a +mismatch between `Mcp-Protocol-Version` and the body's +`_meta.io.modelcontextprotocol/protocolVersion`. + +**`Mcp-Param-*` passthrough.** A tool may annotate properties of its +`InputSchema` with `x-mcp-header: ""`. When a client invokes that +tool over HTTP, the SDK serializes the matching argument(s) as +`Mcp-Param-` request headers (using `=?base64?...?=` encoding for +non-ASCII values). On the server, the headers are validated against the body +and either accepted or rejected with `-32020`. Properties so annotated must +be typed `string`, `integer`, or `boolean`; tools that fail validation at +registration time are silently dropped from `tools/list`. + #### Resumability and Redelivery By default, the streamable server does not support [resumability or @@ -201,6 +283,13 @@ value. The SDK provides a `MemoryEventStore` for testing or simple use cases; for production use it is generally advisable to use a more sophisticated implementation. +> **Note**: [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575) +> removes SSE stream resumability (`Last-Event-ID`, SSE event IDs) for protocol +> version `2026-07-28`. The SDK preserves the `EventStore` and `Last-Event-ID` +> code paths for backward compatibility with `2025-11-25` and earlier; on +> `2026-07-28` sessions, a broken response stream loses the in-flight request +> and the client must re-issue it as a new request with a new ID. + #### Stateless Mode The streamable server supports a _stateless mode_ by setting @@ -219,6 +308,10 @@ to see the logical session > modelcontextprotocol/modelcontextprotocol#1372, or > modelcontextprotocol/modelcontextprotocol#1442 for potential refinements. +> **Required for `2026-07-28`**: the streamable HTTP transport accepts +> requests at protocol version `2026-07-28` **only** when `Stateless = true`. +> Requests at that version against a non-stateless handler are rejected. + _See [examples/server/distributed](https://github.com/modelcontextprotocol/go-sdk/blob/main/examples/server/distributed/main.go) for an example using stateless mode to implement a server distributed across multiple processes._ @@ -597,6 +690,13 @@ session if the ping fails, set or [`ServerOptions.KeepAlive`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.KeepAlive). +> **Note**: `ping` is removed from the protocol as of `2026-07-28` by +> [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575). +> The SDK preserves the API for backward compatibility with legacy clients; +> when both peers negotiate `2026-07-28`, the server returns +> `MethodNotFound` (`-32601`) for `ping` and `KeepAlive` should not be +> enabled. + ### Progress [Progress](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress) @@ -658,3 +758,22 @@ func Example_progress() { } ``` +### Error codes + +The SDK uses the standard JSON-RPC base codes (parse error `-32700`, +invalid request `-32600`, method not found `-32601`, invalid params +`-32602`, internal error `-32603`) plus the following MCP-specific codes: + +| Constant | Code | Meaning | +|---|---|---| +| [`CodeHeaderMismatch`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#CodeHeaderMismatch) | `-32020` | An MCP HTTP header (`Mcp-Method`, `Mcp-Name`, `Mcp-Protocol-Version`, or `Mcp-Param-*`) does not match the JSON-RPC body | +| [`CodeMissingRequiredClientCapabilities`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#CodeMissingRequiredClientCapabilities) | `-32021` | Server requires client capabilities the client did not declare | +| [`CodeUnsupportedProtocolVersion`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#CodeUnsupportedProtocolVersion) | `-32022` | Requested protocol version not supported. Data: `UnsupportedProtocolVersionData{Supported []string, Requested string}` | +| [`CodeResourceNotFound`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#CodeResourceNotFound) | `-32602` | Resource URI not found (variable; was `-32002` before SEP-2164) | + +The error code allocation policy defined in `2026-07-28` +([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)) +partitions the JSON-RPC server-error range: + +- `-32000` to `-32019`: implementation-defined; existing SDK usage is grandfathered. +- `-32020` to `-32099`: reserved for the MCP specification. diff --git a/docs/server.md b/docs/server.md index a24a58e..e7ee307 100644 --- a/docs/server.md +++ b/docs/server.md @@ -4,12 +4,17 @@ 1. [Prompts](#prompts) 1. [Resources](#resources) 1. [Tools](#tools) +1. [Multi Round-Trip Requests (MRTR)](#multi-round-trip-requests-(mrtr)) + 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 @@ -424,6 +429,162 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { } ``` +## Multi Round-Trip Requests (MRTR) + +[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": "Ada"}}, 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.** After paginating, the SDK sets `CacheScope = "public"` +and leaves `TTLMs = 0` (which the client cache treats as "immediately +stale"). A handler that wants its responses cached must set `TTLMs` +explicitly on the returned result. + +```go +mcp.AddTool(server, &mcp.Tool{Name: "expensive"}, func(...) (*mcp.CallToolResult, any, error) { + // ... +}) +// Override the default for tools/list: +server.AddSendingMiddleware(func(next mcp.MethodHandler) mcp.MethodHandler { + return func(ctx context.Context, method string, req mcp.Request) (mcp.Result, error) { + res, err := next(ctx, method, req) + if lr, ok := res.(*mcp.ListToolsResult); ok { + lr.TTLMs = 30_000 // 30s cache freshness hint + } + return res, err + } +}) +``` + ## Utilities ### Completion @@ -613,6 +774,14 @@ server := mcp.NewServer(impl, &mcp.ServerOptions{ **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 diff --git a/internal/docs/client.src.md b/internal/docs/client.src.md index 213078d..7caa081 100644 --- a/internal/docs/client.src.md +++ b/internal/docs/client.src.md @@ -28,6 +28,10 @@ client, a call to `AddRoot` or `RemoveRoots` will result in a [`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](protocol.md#multi-round-trip-requests-mrtr) +pattern. %include ../../mcp/client_example_test.go roots - @@ -51,6 +55,11 @@ 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](protocol.md#multi-round-trip-requests-mrtr) +pattern. + %include ../../mcp/client_example_test.go sampling - ## Elicitation @@ -68,8 +77,44 @@ 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](protocol.md#multi-round-trip-requests-mrtr) +pattern. + %include ../../mcp/client_example_test.go elicitation - +## Multi Round-Trip Requests (MRTR) + +[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 `InputRequiredResult` values directly to the +caller and your code must fulfil the requests manually. + +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-mrtr). + ## Capabilities Client capabilities are advertised to servers during the initialization @@ -126,3 +171,11 @@ client := mcp.NewClient(impl, &mcp.ClientOptions{ }) ``` +### 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. + diff --git a/internal/docs/protocol.src.md b/internal/docs/protocol.src.md index 4044a6f..5bdbac3 100644 --- a/internal/docs/protocol.src.md +++ b/internal/docs/protocol.src.md @@ -6,15 +6,21 @@ The SDK provides an API for defining both MCP clients and servers, and connecting them over various transports. When a client and server are -connected, it creates a logical session, which follows the MCP spec's -[lifecycle](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle). +connected, it creates a logical session. -In this SDK, both a -[`Client`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Client) -and -[`Server`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server) -can handle multiple peers. Every time a new peer is connected, it creates a new -session. +The MCP specification defines two lifecycle models, and the SDK supports both +transparently based on the negotiated protocol version: + +- The **legacy `initialize` handshake** + ([lifecycle](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle)) + used by protocol versions through `2025-11-25`. +- A **stateless** model introduced in `2026-07-28` by + [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575), + in which there is no `initialize`/`notifications/initialized` handshake, and + each request carries its protocol version, client identity, and client + capabilities in `_meta`. + +In both models, the SDK exposes the same API: - A `Client` is a logical MCP client, configured with various [`ClientOptions`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions). @@ -30,11 +36,16 @@ session. [`Server.Connect`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.Connect), it creates a [`ServerSession`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession). - This session is not initialized until the client sends the - `notifications/initialized` message. Use `ServerOptions.InitializedHandler` - to listen for this event, or just use the session through various feature - handlers (such as a `ToolHandler`). Requests to the server are rejected until - the client has initialized the session. + +In the legacy model, the server session is not considered initialized until +the client sends the `notifications/initialized` message. Use +`ServerOptions.InitializedHandler` to listen for this event, or just use the +session through various feature handlers (such as a `ToolHandler`). Requests +to the server are rejected until the client has initialized the session. + +In the stateless model (`2026-07-28`+), there is no handshake. The server +processes the first request the moment it arrives, validates the per-request +`_meta` fields. Both `ClientSession` and `ServerSession` have a `Close` method to terminate the session, and a `Wait` method to await session termination by the peer. Typically, @@ -42,6 +53,41 @@ it is the client's responsibility to end the session. %include ../../mcp/mcp_example_test.go lifecycle - +### Discovery (`server/discover`) + +Introduced in `2026-07-28` by +[SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575), +the `server/discover` RPC lets a client discover a server's supported +protocol versions, capabilities, and identity before issuing any other +request. Servers implementing `2026-07-28` MUST implement it. + +- **Server**: `Server.Connect` registers the + `server/discover` handler automatically; the response is computed from the + server's static configuration (capabilities, server info, instructions) and + the transport-filtered list of supported protocol versions. +- **Client**: `Client.Connect` calls `server/discover` first and + uses the result to negotiate a mutually supported version. If discovery + fails or the server does not support the latest version, the client falls back to the + legacy `initialize` handshake. + +### Per-request `_meta` keys + +When the negotiated protocol version is `2026-07-28` or later, every request +carries these keys inside its `_meta` map (constants live in +[`mcp/protocol.go`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#pkg-constants)): + +| Constant | Wire key | Type | +|---|---|---| +| `MetaKeyProtocolVersion` | `io.modelcontextprotocol/protocolVersion` | `string` | +| `MetaKeyClientInfo` | `io.modelcontextprotocol/clientInfo` | `*Implementation` | +| `MetaKeyClientCapabilities` | `io.modelcontextprotocol/clientCapabilities` | `*ClientCapabilities` | +| `MetaKeyLogLevel` | `io.modelcontextprotocol/logLevel` | `LoggingLevel` (deprecated by SEP-2577) | + +The client populates these keys automatically on every outgoing request. +Server-side handlers can read them +through `ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`, +and `ServerRequest[P].ClientCapabilities()`. + ## Transports A @@ -112,6 +158,39 @@ client, err := mcp.Connect(ctx, transport, &mcp.ClientOptions{...}) The `StreamableClientTransport` handles the HTTP requests and communicates with the server using the streamable transport protocol. +#### HTTP Headers + +[SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243) +standardises a small set of MCP-specific HTTP headers, sent and validated by +the SDK only for sessions negotiated on protocol version `2026-07-28` or +later. + +| Header | Direction | Purpose | +|---|---|---| +| `Mcp-Protocol-Version` | request | Mirrors `_meta.io.modelcontextprotocol/protocolVersion` from the body | +| `Mcp-Session-Id` | request/response | Logical session identifier (removed in stateless mode) | +| `Mcp-Method` | request | Mirrors the JSON-RPC `method` field; mismatch ⇒ `-32020` | +| `Mcp-Name` | request | Mirrors the request's principal name (`tools/call.params.name`, `prompts/get.params.name`, `resources/read.params.uri`); mismatch ⇒ `-32020` | +| `Mcp-Param-{Header}` | request | Per-tool parameter passthrough; see below | + +The header-name set is exhaustive: for protocol versions earlier than +`2026-07-28`, only `Mcp-Protocol-Version` and `Mcp-Session-Id` are recognised. + +**Body↔header mirroring.** When `Mcp-Method` or `Mcp-Name` is present but +does not match the JSON-RPC body, the server returns +[`CodeHeaderMismatch`](#error-codes) (`-32020`). The same applies to a +mismatch between `Mcp-Protocol-Version` and the body's +`_meta.io.modelcontextprotocol/protocolVersion`. + +**`Mcp-Param-*` passthrough.** A tool may annotate properties of its +`InputSchema` with `x-mcp-header: ""`. When a client invokes that +tool over HTTP, the SDK serializes the matching argument(s) as +`Mcp-Param-` request headers (using `=?base64?...?=` encoding for +non-ASCII values). On the server, the headers are validated against the body +and either accepted or rejected with `-32020`. Properties so annotated must +be typed `string`, `integer`, or `boolean`; tools that fail validation at +registration time are silently dropped from `tools/list`. + #### Resumability and Redelivery By default, the streamable server does not support [resumability or @@ -125,6 +204,13 @@ value. The SDK provides a `MemoryEventStore` for testing or simple use cases; for production use it is generally advisable to use a more sophisticated implementation. +> **Note**: [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575) +> removes SSE stream resumability (`Last-Event-ID`, SSE event IDs) for protocol +> version `2026-07-28`. The SDK preserves the `EventStore` and `Last-Event-ID` +> code paths for backward compatibility with `2025-11-25` and earlier; on +> `2026-07-28` sessions, a broken response stream loses the in-flight request +> and the client must re-issue it as a new request with a new ID. + #### Stateless Mode The streamable server supports a _stateless mode_ by setting @@ -143,6 +229,10 @@ to see the logical session > modelcontextprotocol/modelcontextprotocol#1372, or > modelcontextprotocol/modelcontextprotocol#1442 for potential refinements. +> **Required for `2026-07-28`**: the streamable HTTP transport accepts +> requests at protocol version `2026-07-28` **only** when `Stateless = true`. +> Requests at that version against a non-stateless handler are rejected. + _See [examples/server/distributed](https://github.com/modelcontextprotocol/go-sdk/blob/main/examples/server/distributed/main.go) for an example using stateless mode to implement a server distributed across multiple processes._ @@ -463,6 +553,13 @@ session if the ping fails, set or [`ServerOptions.KeepAlive`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.KeepAlive). +> **Note**: `ping` is removed from the protocol as of `2026-07-28` by +> [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575). +> The SDK preserves the API for backward compatibility with legacy clients; +> when both peers negotiate `2026-07-28`, the server returns +> `MethodNotFound` (`-32601`) for `ping` and `KeepAlive` should not be +> enabled. + ### Progress [Progress](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress) @@ -480,3 +577,22 @@ Issue #460 discusses some potential ergonomic improvements to this API. %include ../../mcp/mcp_example_test.go progress - +### Error codes + +The SDK uses the standard JSON-RPC base codes (parse error `-32700`, +invalid request `-32600`, method not found `-32601`, invalid params +`-32602`, internal error `-32603`) plus the following MCP-specific codes: + +| Constant | Code | Meaning | +|---|---|---| +| [`CodeHeaderMismatch`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#CodeHeaderMismatch) | `-32020` | An MCP HTTP header (`Mcp-Method`, `Mcp-Name`, `Mcp-Protocol-Version`, or `Mcp-Param-*`) does not match the JSON-RPC body | +| [`CodeMissingRequiredClientCapabilities`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#CodeMissingRequiredClientCapabilities) | `-32021` | Server requires client capabilities the client did not declare | +| [`CodeUnsupportedProtocolVersion`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#CodeUnsupportedProtocolVersion) | `-32022` | Requested protocol version not supported. Data: `UnsupportedProtocolVersionData{Supported []string, Requested string}` | +| [`CodeResourceNotFound`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#CodeResourceNotFound) | `-32602` | Resource URI not found (variable; was `-32002` before SEP-2164) | + +The error code allocation policy defined in `2026-07-28` +([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)) +partitions the JSON-RPC server-error range: + +- `-32000` to `-32019`: implementation-defined; existing SDK usage is grandfathered. +- `-32020` to `-32099`: reserved for the MCP specification. diff --git a/internal/docs/server.src.md b/internal/docs/server.src.md index 744015f..fa1599d 100644 --- a/internal/docs/server.src.md +++ b/internal/docs/server.src.md @@ -214,6 +214,80 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { } ``` +## Multi Round-Trip Requests (MRTR) + +[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). + +%include ../../mcp/server_example_test.go mrtr - + +## 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.** After paginating, the SDK sets `CacheScope = "public"` +and leaves `TTLMs = 0` (which the client cache treats as "immediately +stale"). A handler that wants its responses cached must set `TTLMs` +explicitly on the returned result. + +```go +mcp.AddTool(server, &mcp.Tool{Name: "expensive"}, func(...) (*mcp.CallToolResult, any, error) { + // ... +}) +// Override the default for tools/list: +server.AddSendingMiddleware(func(next mcp.MethodHandler) mcp.MethodHandler { + return func(ctx context.Context, method string, req mcp.Request) (mcp.Result, error) { + res, err := next(ctx, method, req) + if lr, ok := res.(*mcp.ListToolsResult); ok { + lr.TTLMs = 30_000 // 30s cache freshness hint + } + return res, err + } +}) +``` + ## Utilities ### Completion @@ -318,6 +392,14 @@ server := mcp.NewServer(impl, &mcp.ServerOptions{ **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 diff --git a/internal/readme/README.src.md b/internal/readme/README.src.md index 84126ad..ce1f6fc 100644 --- a/internal/readme/README.src.md +++ b/internal/readme/README.src.md @@ -33,11 +33,12 @@ contains feature documentation, mapping the MCP spec to the packages above. The following table shows which versions of the Go SDK support which versions of the MCP specification: -| SDK Version | Latest MCP Spec | All Supported MCP Specs | -|-----------------|-------------------|----------------------------------------------------| -| v1.4.0+ | 2025-11-25\* | 2025-11-25\*, 2025-06-18, 2025-03-26, 2024-11-05 | -| v1.2.0 - v1.3.1 | 2025-11-25\*\* | 2025-11-25\*\*, 2025-06-18, 2025-03-26, 2024-11-05 | -| v1.0.0 - v1.1.0 | 2025-06-18 | 2025-06-18, 2025-03-26, 2024-11-05 | +| SDK Version | Latest MCP Spec | All Supported MCP Specs | +|-----------------|-------------------|------------------------------------------------------------------| +| v1.7.0+ | 2026-07-28 | 2026-07-28, 2025-11-25\*, 2025-06-18, 2025-03-26, 2024-11-05 | +| v1.4.0 - v1.6.1 | 2025-11-25\* | 2025-11-25\*, 2025-06-18, 2025-03-26, 2024-11-05 | +| v1.2.0 - v1.3.1 | 2025-11-25\*\* | 2025-11-25\*\*, 2025-06-18, 2025-03-26, 2024-11-05 | +| v1.0.0 - v1.1.0 | 2025-06-18 | 2025-06-18, 2025-03-26, 2024-11-05 | \* Client side OAuth has experimental support. diff --git a/mcp/server_example_test.go b/mcp/server_example_test.go index 808be5a..58d4eee 100644 --- a/mcp/server_example_test.go +++ b/mcp/server_example_test.go @@ -13,6 +13,7 @@ import ( "log/slog" "sync/atomic" + "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -220,3 +221,89 @@ func Example_resources() { } // !-resources + +// !+mrtr + +// 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": "Ada"}}, 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) +} + +// !-mrtr