This commit is contained in:
parent
3a6f2996cd
commit
837ef904f8
3 changed files with 1757 additions and 567 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue