Align with spec #3002: optional clientInfo, serverInfo in result _meta (#3143)

This commit is contained in:
Max 2026-07-23 12:00:36 +01:00 • committed by GitHub
commit 837ef904f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 1757 additions and 567 deletions

View file

@ -133,14 +133,21 @@ or veto a tool call:
```
* `params` is the validated `CallToolRequestParams`: you get `params.name` and
`params.arguments` without touching raw JSON.
* `call_next(ctx)` runs the rest of the chain. Return its result unchanged (observe),
return something else (replace), or raise an `MCPError` (refuse).
`params.arguments` without touching raw JSON. It is also what decides which
tool call runs: passing a rewritten context through `call_next` changes what
the handler observes on `ctx`, not the tool invocation. Wire-level request
rewriting belongs to [Middleware](middleware.md).
* `call_next(ctx)` runs the rest of the chain and returns the handler's result.
Return it unchanged (observe), return something else (replace), or raise an
`MCPError` (refuse). Whatever you return is serialized like any handler
result, including the 2026-era `serverInfo` identity stamp, so a
short-circuiting interceptor never produces an anonymous or off-schema
response.
* With several extensions, interceptors nest in registration order: the first
extension in `extensions=[...]` is outermost.
* The default implementation is a pass-through, and a server whose extensions never
override this hook installs **no** middleware at all. You don't pay for what
you don't use.
override this hook keeps the bare `tools/call` handler untouched. You don't
pay for what you don't use.
The hook wraps `tools/call` and nothing else. For every-message concerns, use
[Middleware](middleware.md). That is what it is for.

View file

@ -102,10 +102,13 @@ Call it and the result carries both representations:
"content": [{"type": "text", "text": "Found 3 books matching 'dune'."}],
"structuredContent": {"matches": 3, "query": "dune"},
"isError": false,
"resultType": "complete"
"resultType": "complete",
"_meta": {"io.modelcontextprotocol/serverInfo": {"name": "Bookshop", "version": "2.0.0"}}
}
```
The `_meta` block is the server's identity stamp: the SDK adds it to every 2026-era result, with the `version` from the constructor (a server that sets none reports an empty string). A server that must not identify itself can strip the key with a middleware, which owns the results it returns.
The server never compares the two fields. This SDK's `Client` does: return `structured_content` that doesn't satisfy the `output_schema` you declared and `call_tool` raises a `RuntimeError` that starts with `Invalid structured content returned by tool search_books` and goes on to quote the `jsonschema` failure. Promising a schema is cheap; keeping it is on you. The whole ladder of return types and schemas is in **[Structured Output](../servers/structured-output.md)**.
## `_meta`: for the application, not the model

View file

@ -63,6 +63,10 @@ In increasing order of how much you should hesitate:
`initialize`: the result the client gets back is built from your rewritten params, but the
server commits its connection state from the original wire params. The two sides can finish
the handshake disagreeing about what they negotiated.
* **Answer.** Return a result without calling `call_next(ctx)` and it goes to the client as
your response. `call_next` hands you the finished wire form, and the pipeline never patches
what you return, so the whole envelope is yours: on a 2026-era connection that includes the
`serverInfo` `_meta` stamp, which the SDK adds to handler results but not to yours.
!!! check
`initialize` is one of the things middleware wraps, and it is the *only* hook you get