* Add structured output to the Python weather server and client
Both tools declare an output schema - the return type annotation - and return
structured_content alongside the text.
get_alerts 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.
The detail worth reading the source for: Alerts is a RootModel[list[Alert]],
not a plain list[Alert]. A list is not a JSON object, so the SDK wraps it as
{"result": [...]} and advertises an object-rooted schema to match, with no
opt-out. A RootModel is a BaseModel, so it is taken as the schema exactly as
written, and a RootModel over a list serializes as the bare list.
Error paths raise: a tool declaring an output schema MUST return conforming
structured content, so a path with no data has to fail.
The client moves to the 2.0 Client API with mode="auto" and prints the
negotiated version on connect. call_tool already revalidates every non-error
result against the declared schema, so the client-side SHOULD needs no code.
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.
This clears the staleness modelcontextprotocol#3124 flags against this
repository - weather.py imported mcp.server.fastmcp, client.py held the
ClientSession version - and matches that PR's idioms: the short
"from mcp.server import MCPServer" spelling, httpx2 with httpx dropped from
the dependencies since httpx2>=2.5.0 is a hard dependency of mcp,
result.content narrowed to TextContent, and input() on a worker thread.
Requires mcp 2.0.0b2; pyproject.toml sets [tool.uv] prerelease = "allow" so
plain uv run and uv sync work without flags. Model identifier moves to
claude-sonnet-5.
Note that an array-rooted schema only works on a 2026-07-28 connection. This
SDK does not project it down for older clients; it raises server-side instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Update to mcp 2.0.0
The SDK left beta on 2026-07-28, so the pins move off 2.0.0b2 and both
projects drop `[tool.uv] prerelease = allow` — plain `uv run` works now.
Also from review: declare the imports rather than lean on transitive
resolution. weather.py imports httpx2 and client.py imports mcp_types, so
both are listed explicitly. The client README no longer implies every
structured result is counted — only array-rooted ones are.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Fix get_alerts failing when NWS sends a null field
`dict.get(key, default)` returns the default only when the key is absent. The
NWS API sends these fields as explicit nulls instead of omitting them, so
`props.get(instruction, ...)` passed None straight into a `str` field and
Pydantic rejected the whole result.
One alert in eight for TX is a Special Weather Statement with a null
instruction, which was enough to fail the entire call:
Error executing tool get_alerts: 1 validation error for Alert
instructions
Input should be a valid string [type=string_type, input_value=None]
The smoke test missed it twice over: it calls get_alerts with CA, whose
alerts happen to have no nulls today, and it reports an isError result as a
skip rather than a failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Say "structured content", not "structured output"
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>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit adds `tests/smoke-test.sh`, which performs a smoke test of
all clients and servers. The smoke test tests that the clients and
servers run and that list tools is called.
This commit also adds a GitHub Actions workflow to run the smoke test on
all pull requests and pushes to main.
For practicality, this commit modifies the TypeScript and Python clients
to lazily initialize the Anthropic client, thus allowing tests to skip
setting an API key. This change also provides a friendly error when
users forget to set an API key.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alex Akimov <alexeyakimov@gmail.com>