The surrounding sentence names 2026-07-28 as the revision that first allows
an array-rooted schema, but the link pointed at /draft, which moves on to
the next revision and stops matching the claim.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
The gem's default_capabilities advertises tools, prompts, resources and
logging. This server has only tools; logging is additionally deprecated as of
2026-07-28 (SEP-2577).
Passing capabilities: { tools: {} } makes server/discover honest. No
listChanged flag, because change notifications at this revision are delivered
over subscriptions/listen and this server does not serve it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mcp gem 1.1.0 (2026-08-01) is the first to negotiate 2026-07-28, so the
Ruby examples can finally show the same thing the other four do: get_alerts
declares an array-rooted output_schema and returns a bare top-level JSON
array, get_forecast returns an object. Declaring `type:` at the root is what
suppresses the SDK object-root default.
Failure paths return an error result rather than a bare text one, because a
tool declaring an output_schema MUST return conforming structured content.
No alerts is an empty array, not an error.
The client compiles each declared schema at connect time and validates every
non-error result. The SDK client does not do this itself, so the spec
client-side SHOULD costs real code here, as it does in Go and Rust.
NOT READY TO SHIP. The gem never emits the `resultType` field that the
2026-07-28 schema marks mandatory, and no user-level knob sets it, so a
spec-strict client rejects every response including tools/list. Verified
end-to-end against the Ruby client, which does not check the field; blocked
against the TypeScript SDK client. See the server README.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* 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>
* Assert on structured output in the smoke tests
The smoke tests connected to each server, listed its tools and stopped there,
so they could not catch a broken structured result.
mcp-test-client now calls every tool that declares an outputSchema and checks
the answer against it: the result must carry structuredContent, and a tool
with an array-rooted schema must return a top-level JSON array rather than an
object wrapping one. Tools with no output schema are left alone, so this is
not a new requirement on any example. The client negotiates with mode "auto",
so it exercises servers of either protocol era. Tool calls reach the live NWS
API; an error result is reported as a skip rather than a failure.
mock-mcp-server moves to the low-level Server and advertises one array-rooted
and one object-rooted tool, with the schemas written out literally. A client
that compiles every declared outputSchema at connect time fails against the
array-rooted one if it assumes output schemas are always objects.
utils.sh no longer swallows build output. A failed build left no binary behind
and the test reported a downstream spawn ENOENT instead of the compiler error,
which is what makes a broken build hard to diagnose from CI logs; #143 is the
current example. check_dependency returns instead of exiting, so a missing
toolchain fails its own test and the suite still prints its summary. Binaries
resolve through a helper that accounts for the .exe suffix.
The test helpers move to the 2.0 MCP SDK packages, where the 2026-07-28
support lives; the examples themselves are untouched. Those require Node 20 or
newer, so CI moves off Node 18 to Node 24, the Active LTS - Node 20 reached
end of life in April 2026.
The Go examples and the Rust client stay uncovered here. Both clients abort
when no .env file is present, so that coverage lands with those examples.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Update to the stable 2.0 SDK packages and address review feedback
@modelcontextprotocol/{client,server} 2.0.0 shipped on 2026-07-27, so the
helpers move off 2.0.0-beta.5.
Review fixes:
- strip the UTF-8 BOM that preceded smoke-test.sh's shebang
- ensure_built and ensure_helpers_built return rather than exit, so one
project failing to build no longer aborts the suite before the summary
- engines.node and the documented Node requirement go to 20, matching what
the 2.0 SDK packages actually require
- close the client on the failure path, so a failing test cannot leave the
spawned server running
- the README claimed Go and Rust client coverage that did not exist
Adds the Go weather server to the smoke test, with the Go toolchain CI was
missing. The Go and Rust clients stay uncovered: on main both abort without
a .env, so credential-free startup has to land in their own directories.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Apply the same Messages API tool-use fixes that #141 made to the Python
client (mcp-client-python/client.py) to mcp-client-typescript/index.ts:
- Reply to each tool_use with a proper tool_result block carrying
tool_use_id, and append the assistant's full content (preserving the
tool_use block) instead of sending the result as a plain user string.
- Restructure processQuery into a turn-level loop so parallel tool calls
in one response and chained tool calls across turns both work; pass
tools on every follow-up request.
- Add MAX_TOOL_TURNS = 10 cap with a "[Stopped after N tool-use turns]"
notice to prevent unbounded tool-use loops.
- Fix chatLoop to exit cleanly on EOF (Ctrl-D) and SIGINT (Ctrl-C) via
an AbortController wired to readline close, instead of hanging or
throwing on stdin EOF.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps the npm_and_yarn group with 1 update in the /mcp-client-typescript directory: [ip-address](https://github.com/beaugunderson/ip-address).
Bumps the npm_and_yarn group with 1 update in the /tests/helpers directory: [ip-address](https://github.com/beaugunderson/ip-address).
Bumps the npm_and_yarn group with 1 update in the /weather-server-typescript directory: [ip-address](https://github.com/beaugunderson/ip-address).
Updates `ip-address` from 10.1.0 to 10.2.0
- [Commits](https://github.com/beaugunderson/ip-address/commits)
Updates `ip-address` from 10.1.0 to 10.2.0
- [Commits](https://github.com/beaugunderson/ip-address/commits)
Updates `ip-address` from 10.1.0 to 10.2.0
- [Commits](https://github.com/beaugunderson/ip-address/commits)
---
updated-dependencies:
- dependency-name: ip-address
dependency-version: 10.2.0
dependency-type: indirect
dependency-group: npm_and_yarn
- dependency-name: ip-address
dependency-version: 10.2.0
dependency-type: indirect
dependency-group: npm_and_yarn
- dependency-name: ip-address
dependency-version: 10.2.0
dependency-type: indirect
dependency-group: npm_and_yarn
...
Signed-off-by: dependabot[bot] <support@github.com>
## Motivation and Context
Follow-up to https://github.com/modelcontextprotocol/ruby-sdk/pull/336
mcp gem 0.15.0 introduced an explicit `MCP::Client#connect` method for the stdio transport,
aligning with the Python and TypeScript SDKs which require explicit handshake calls regardless
of transport type:
- Python SDK: `ClientSession.initialize()`
https://github.com/modelcontextprotocol/python-sdk
- TypeScript SDK: `Client.connect(transport)`
https://github.com/modelcontextprotocol/typescript-sdk
This sample is updated to call `@mcp_client.connect` explicitly after creating the client,
demonstrating the recommended initialization pattern. The Gemfile constraint is bumped to `>= 0.15.0`
to require the new API.
## How Has This Been Tested?
Ran the sample against `weather-server-ruby` and confirmed the connection, tool listing,
and chat loop continue to work as expected.
## Breaking Changes
None. The change updates the sample to use the new explicit `connect` API,
which was added as a non-breaking addition in mcp gem 0.15.0.