mcp: add documentation for new protocol version (#1019)
Add documentation for new protocol version in the `internal/docs`
This commit is contained in:
parent
01847b35b3
commit
b9e6fc5a66
1 changed files with 719 additions and 36 deletions
169
docs/server.md
169
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue