`#[tool_handler]` generated `ttl_ms: None, cache_scope: None`, but both are
required on a paginated result at 2026-07-28, so a strict client rejected
tools/list before it could reach a tool call. The server worked around that by
hand-writing list_tools purely to set the two fields.
rust-sdk#1120 fixed the macro, released in rmcp 3.1.1: it now emits the fields
when the negotiated revision expects them and omits them otherwise. Both
lockfiles move to 3.1.2 and the override goes away, so the server is back to the
plain `#[tool_handler]` path the SDK documents.
Verified on the raw wire: after a 2026-07-28 handshake, tools/list carries
`ttlMs: 0` and `cacheScope: "public"` with no code setting them, and the smoke
test's strict client still validates both tools' structured content.
The spec never uses the phrase "structured output". It defines two separate
things under Tool Result: "Structured Content" (the `structuredContent` field)
and "Output Schema" (the `outputSchema` field). Collapsing them into
"structured output" conflates the two.
It is also actively confusing here. In LLM tooling "structured output" means
constrained decoding — making the *model* emit conforming JSON. These clients
call a model API, so a reader could reasonably take the phrase to mean the
tool constrains the model's response, which is the opposite of what is going
on: the tool describes the shape of its own result.
Headings and prose now say "structured content". References to real
identifiers are left alone: the Python SDK's own docs page is called
Structured Output and lives at docs/servers/structured-output.md, and its
decorator parameter is `structured_output`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rmcp left beta on 2026-07-28 and reached 3.1.0 on 2026-07-31, so the pin
moves off 3.0.0-beta.2.
get_info now sets server_info. ServerInfo::default() reports rmcp own crate
name and version, so the server was identifying itself as rmcp 3.1.0
rather than weather 1.0.0 as the other three quickstarts do.
The hand-written list_tools stays: rmcp-macros 3.1.0 still generates
ttl_ms: None, cache_scope: None, which a strict client rejects.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both tools declare an output_schema and return structured_content alongside
the text.
get_alerts declares Vec<Alert>, so it answers with a top-level JSON array
rather than an array nested in an object, which protocol revision 2026-07-28
is the first to allow. "No alerts" is simply []. get_forecast returns an
object, for contrast.
Error paths return a tool-level error: a tool declaring an output_schema MUST
return conforming structured content, so a path with no data has to fail.
The client compiles every declared outputSchema at connect time and validates
results against it. rmcp does not do this; its docs point at the jsonschema
crate, which is the one new dependency here. Each channel goes to its stated
reader: content is forwarded to the model, structured_content is used as data,
reporting how many items came back. A missing .env is no longer fatal, and the
API key is checked after connecting rather than before.
This supersedes #143, which bumped rmcp 0.3 -> 1.4 and failed CI because the
0.3 #[tool] macro API does not survive the jump. The rewrite was required
either way, so the bump is folded in and taken to 3.0.0-beta.2, the first
version with the 2026-07-28 model.
list_tools is hand-written. #[tool_handler] generates one hardcoding
ttl_ms: None, cache_scope: None, but both are required on a paginated result
at 2026-07-28 (SEP-2549) and a strict client rejects the response outright.
get_info does not pin ProtocolVersion::V_2026_07_28 either: rmcp's server
never implements server/discover, so pinning would overstate support.
Model identifier moves to claude-sonnet-5, replacing claude-sonnet-4-20250514,
which is past end of life.
Note that rmcp sends an array-rooted schema as written on every connection
rather than projecting it down, so an older client rejects the tool list.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>