Commit graph

256 commits

Author SHA1 Message Date
Alex Akimov
470d1ba328
Fix the tool-use loop in the Go, Rust, and Ruby clients and add a tool-loop smoke test (#200)
Some checks failed
Smoke Tests / test (push) Has been cancelled
* Fix the tool-use loop in the Go, Rust, and Ruby clients and add a tool-loop smoke test

## Motivation and Context

Only the Python and TypeScript clients implemented a correct tool-use loop. The other three
had bugs that break ordinary multi-step requests:

- **Go, Rust, Ruby** did a single tool round and then called Claude once more *without*
  `tools`, so any request that needs a second tool call stalled. Go silently dropped
  `tool_use` blocks from that final response.
- **Ruby** called Claude once per `tool_use` block, each time with a single `tool_result`.
  Parallel tool calls produced a malformed message sequence and an API 400.
- **max_tokens** was 1000 in TypeScript and Ruby, 1024 in Go, and unset in Rust. Sonnet 5
  thinks adaptively, so 1000 tokens is regularly exhausted mid-thought. Python already used
  10000 with a comment explaining why.
- **Ruby** never forwarded MCP `isError` as `is_error`, so the model could not tell a failed
  tool call from a successful one. Rust cannot set the flag either (see below).
- **TypeScript** forwarded raw MCP content blocks to Anthropic. The two block shapes differ,
  so a non-text result would have failed.

The existing smoke test never noticed because it only checks that each client calls
`tools/list`. None of these bugs was reachable without an API key.

### What changed

All five clients now run the same loop: call tools until Claude answers without one, send
every `tool_result` of a turn in one user message, pass `tools` on every call, and stop
after `MAX_TOOL_TURNS = 10`. `max_tokens` is 10000 everywhere, with the same comment.
Python and TypeScript also keep the last response's text when the turn cap is hit; it used
to be dropped.

Rust keeps the `genai` crate. Its tool-response type has no `is_error` field, so an error
result is marked with an `Error:` prefix in the text and the limitation is commented.

### New smoke test

`tests/helpers/tool-loop-test.ts` starts a fake Anthropic Messages API on a loopback port,
points a client at it through `ANTHROPIC_BASE_URL`, pipes in one query, and checks every
request the client sends. It scripts two parallel tool calls, then a call to a tool the mock
server lacks (an `isError` result), then a final answer, and asserts that tools are passed
each time, `max_tokens` is 10000, all `tool_result`s arrive in one message with matching
ids, and the error is forwarded. `smoke-test.sh` runs it for Python, TypeScript, Go, and
Ruby.

The Rust client is excluded for now: `genai` reads no endpoint variable, and the client
negotiates protocol `2025-11-25`, under which the mock's array-rooted tool is refused at call
time. Both will be addressed in a follow-up that brings all examples to current SDK versions.

## How Has This Been Tested?

- `./tests/smoke-test.sh` passes for every example.
- The new tool-loop test passes all 19 checks for Python, TypeScript, Go, and Ruby.
- The Rust client was driven through the same scripted conversation with a temporary
  endpoint resolver (not committed) and passes.
- As a negative check, the Ruby client from `main` was run through the new test. It fails 10
  of 19 checks, on exactly the bugs listed above.
- Go and Ruby were built and run in `golang:1.25` and `ruby:3.4` containers.

## Breaking Changes

None. The clients' command lines, output format, and no-key behavior are unchanged.

## Types of changes

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [x] Documentation update

## Checklist

- [x] I have read the [MCP Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context

Two pre-existing issues surfaced while testing and are left for a follow-up:

- The Rust client (rmcp 3.1.2) negotiates `2025-11-25`. The TypeScript mock server and the
  Python weather server both reject its calls to array-rooted tools under that revision.
  Even current rmcp defaults to `2025-11-25`, so the client must request `2026-07-28`
  explicitly.
- The Go SDK's tool-result helper sends content as a list of text blocks rather than a
  string. The API accepts both, so it is unchanged.

AI assistance: the analysis, code changes, test helper, and this description were prepared
partially manually, partially with Claude Code and reviewed by me.

* Improvements

* Address comments
2026-09-19 18:25:02 -07:00
Alex Akimov
3844c5b45e
Add smoke tests for Go and Rust clients (#196)
* Add smoke tests for Go and Rush clients

* Add smoke tests for Go and Rush clients
2026-09-15 15:14:06 -07:00
Alex Akimov
ccb4eeef82
Upgrade Python libraries to mcp 2.2.0, and bump others as well (#197) 2026-09-15 15:13:25 -07:00
Alex Akimov
5eb3b647d0
Upgrade TypeScript libraries to newer versions (#198) 2026-09-15 15:12:34 -07:00
Alex Akimov
7ff523bc51
Merge pull request #194 from modelcontextprotocol/dependabot/uv/mcp-client-python/uv-d93ce086ae
Bump the uv group across 2 directories with 1 update
2026-09-09 08:34:18 +02:00
dependabot[bot]
802f71931d
Bump the uv group across 2 directories with 1 update
Bumps the uv group with 1 update in the /mcp-client-python directory: [httpx2](https://github.com/pydantic/httpx2).
Bumps the uv group with 1 update in the /weather-server-python directory: [httpx2](https://github.com/pydantic/httpx2).


Updates `httpx2` from 2.9.1 to 2.12.0
- [Release notes](https://github.com/pydantic/httpx2/releases)
- [Changelog](https://github.com/pydantic/httpx2/blob/main/src/httpx2/CHANGELOG.md)
- [Commits](https://github.com/pydantic/httpx2/compare/v2.9.1...v2.12.0)

Updates `httpx2` from 2.9.1 to 2.12.0
- [Release notes](https://github.com/pydantic/httpx2/releases)
- [Changelog](https://github.com/pydantic/httpx2/blob/main/src/httpx2/CHANGELOG.md)
- [Commits](https://github.com/pydantic/httpx2/compare/v2.9.1...v2.12.0)

---
updated-dependencies:
- dependency-name: httpx2
  dependency-version: 2.12.0
  dependency-type: indirect
  dependency-group: uv
- dependency-name: httpx2
  dependency-version: 2.12.0
  dependency-type: direct:production
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-08 22:58:47 +00:00
Alex Akimov
55f75f3aa9
Merge pull request #192 from olaservo/readme-followup-nits
Correct README claims that do not match the repo
2026-08-29 12:34:33 +02:00
olaservo
4d79997120 Correct README claims that do not match the repo
The License section named MIT. LICENSE is a transition file: new code
contributions are Apache-2.0, documentation excluding specifications is
CC-BY-4.0, and only earlier contributions without relicensing consent
stay MIT.

"Rust (stable)" understates the floor. rmcp, darling and time declare
rust-version 1.88, and cargo metadata resolves both crates to 1.88.

The Ruby aside said 3.3 also satisfies the gems. anthropic requires
>= 3.2.0 and mcp >= 2.7.0, so the gem floor was 3.2, and 3.2 reached EOL
on 2026-03-31. CI runs 3.4, which is what the READMEs now state.

The Go and Rust clients take a command plus arguments, not a path to a
server script.

Removed the commit-message convention, which is attributed to the MCP
contributing guidelines but does not appear in them. Added the
AI_POLICY.md link that CONTRIBUTING.md points to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdvHa5MzDw33PEz3DbTaJD
2026-08-28 10:51:08 -07:00
Alex Akimov
9395583c49
Improve all readmes (#188)
* Improve all readmes

* Address some of the comments
2026-08-27 20:32:29 -07:00
Alex Akimov
f26c52eab4
Address the code scanning report (#189)
https://github.com/modelcontextprotocol/quickstart-resources/security/code-scanning/3
2026-08-23 09:29:35 -07:00
Alex Akimov
0f4fc38f9c
Change the Sonnet model to 5 (#187) 2026-08-22 05:12:48 -07:00
Alex Akimov
9a0dba5f9a
Merge pull request #186 from modelcontextprotocol/dependabot/cargo/mcp-client-rust/cargo-26dd2c1603
Bump the cargo group across 2 directories with 3 updates
2026-08-21 18:13:24 +02:00
Alex Akimov
4c55f7dcfb
Merge pull request #184 from olaservo/structured-output-2026-07-28/ruby
Add structured content to the Ruby weather server and client
2026-08-21 18:05:05 +02:00
Alex Akimov
3196b11aae
Merge pull request #183 from olaservo/ruby-smoke-tests
Cover the Ruby examples in the smoke tests
2026-08-21 17:57:35 +02:00
olaservo
cfda589119 Update the tests README for Ruby server coverage
The README still said the Ruby weather server was uncovered and described
the resultType gap as unfixable from an example. Both stopped being true
when the server test landed.

Also move the no-API-key comment below the Ruby server test. It describes
the client tests, and sat where it read as describing the server one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:03:57 -07:00
olaservo
c8139806a7 Require mcp 1.2.0 in the Ruby weather server
Protocol revision 2026-07-28 requires a resultType on every result. The mcp
gem stamps it as of 1.2.0, so a ">= 1.1.0" floor allowed an install whose
responses a 2026-07-28 client rejects outright.

Observed against the test client on 1.1.0:

  Invalid result for tools/list: missing required resultType

On 1.2.0 the same server lists both tools and both structured-content
assertions pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:12:18 -07:00
olaservo
d9005ebe03 Cover the Ruby weather server in the smoke tests
The server was excluded because the mcp gem did not stamp the resultType
field that 2026-07-28 requires, so the test client rejected its responses.
mcp 1.2.0 stamps it. Gemfile.lock is gitignored, so a fresh bundle install
resolves the existing ">= 1.1.0" to 1.2.0 with no manifest change.

Observed against the built test client: on 1.1.0 the run fails with
"missing required resultType"; on 1.2.0 it connects and lists both tools.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:11:37 -07:00
olaservo
ecc1138647 Pin ANTHROPIC_API_KEY empty in the client smoke tests
The client tests drive the path where no key is set: each client prints a
notice and exits. They did not isolate the variable, so a key exported in
the environment or sitting in a local .env sent the client into its
interactive chat loop instead. Observed with a .env in mcp-client-ruby: the
run reached "Query:" and waited on stdin.

Empty rather than unset, because dotenv skips a name already present in ENV.
All three clients check `nil? || empty?`, so an empty value takes the early
exit.

CI never hit this, having no key set. It affects local runs, and a run fed
input rather than /dev/null would issue real API requests.

Applied to the Python and TypeScript client tests too; all three share the
pattern.

Also correct the Ruby version comment: 3.3 is supported until March 2027, so
3.4 is not the oldest supported series. The tested version is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:44:09 -07:00
dependabot[bot]
7ddcd63002
Bump the cargo group across 2 directories with 3 updates
Bumps the cargo group with 3 updates in the /mcp-client-rust directory: [openssl](https://github.com/rust-openssl/rust-openssl), [quinn-proto](https://github.com/quinn-rs/quinn) and [serde_with](https://github.com/jonasbb/serde_with).
Bumps the cargo group with 1 update in the /weather-server-rust directory: [openssl](https://github.com/rust-openssl/rust-openssl).


Updates `openssl` from 0.10.79 to 0.10.81
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.79...openssl-v0.10.81)

Updates `quinn-proto` from 0.11.14 to 0.11.17
- [Release notes](https://github.com/quinn-rs/quinn/releases)
- [Commits](https://github.com/quinn-rs/quinn/compare/quinn-proto-0.11.14...quinn-proto-0.11.17)

Updates `serde_with` from 3.15.0 to 3.22.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.15.0...v3.22.0)

Updates `openssl` from 0.10.79 to 0.10.81
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.79...openssl-v0.10.81)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.81
  dependency-type: indirect
  dependency-group: cargo
- dependency-name: quinn-proto
  dependency-version: 0.11.17
  dependency-type: indirect
  dependency-group: cargo
- dependency-name: serde_with
  dependency-version: 3.22.0
  dependency-type: indirect
  dependency-group: cargo
- dependency-name: openssl
  dependency-version: 0.10.81
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-19 00:09:25 +00:00
Alex Akimov
d06b6ac3a1
Merge pull request #167 from olaservo/structured-output-2026-07-28/rust
Add structured content to the Rust weather server and client and update to v3
2026-08-18 19:53:33 +02:00
Alex Akimov
6935039258
Merge pull request #166 from olaservo/structured-output-2026-07-28/go
Add structured content to the Go weather server and client and update to latest SDK and protocol version
2026-08-18 17:44:43 +02:00
Alex Akimov
7fbffb130b
Merge pull request #185 from olaservo/olaservo/dated-spec-links-python
Link the dated spec revision, not /draft
2026-08-17 22:21:06 +02:00
Ola Hungerford
43927228cc
Add structured content to the TypeScript weather server and client and update to v2 (#165)
* Add structured output to the TypeScript weather server and client

Both tools declare an outputSchema and return structuredContent alongside the
text.

get-alerts declares z.array(...), 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.

This costs older clients nothing. The server declares the array schema once
and never branches on protocol version: serveStdio serves both eras from one
factory, and the SDK projects the schema down to
{"type":"object","properties":{"result":...}} for a 2025-11-25 client, wrapping
the structured content to match. Verified against both eras.

Error paths throw: a tool declaring an outputSchema MUST return conforming
structured content, so a path with no data has to fail.

The client passes versionNegotiation {mode:"auto"}; the SDK default is
"legacy". The SDK validates every 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, structuredContent is used as data,
reporting how many items came back.

Moves to the 2.0 beta packages, where the 2026-07-28 support lives:
@modelcontextprotocol/sdk is replaced by @modelcontextprotocol/server and
@modelcontextprotocol/client. Model identifier moves to claude-sonnet-5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Update to @modelcontextprotocol/{server,client} 2.0.0

The 2.0 packages left beta on 2026-07-27, so the dependency ranges move off
2.0.0-beta.5.

Also from review: engines.node goes to 20, which is what the 2.0 packages
require, and 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>

* Query the active-alerts endpoint in the TypeScript server

get-alerts asked for /alerts?area=XX, which returns every alert NWS has on
file for the state including expired ones. The Python, Go and Rust servers
all ask for /alerts/active/area/XX.

Measured against the live API for TX: 372 alerts before, 8 after, and the
tools/call response drops from roughly 270KB to 5KB. The other four servers
return the same 8.

Pre-existing on main rather than introduced by the structured-output work,
but it is the one thing that stopped the examples being comparable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Match the shared alert and forecast shapes in the TypeScript server

get-alerts published status and headline where Python, Go, Rust and Ruby all
publish description and instructions, and get-forecast periods used the NWS
camelCase spelling with shortForecast instead of detailed_forecast. The
divergence predates this round, but declaring an outputSchema turns it from a
difference in prose formatting into a difference in the published contract,
which defeats the point of a five-language example.

All five now advertise:
  get_alerts   -> [{event, area, severity, description, instructions}]
  get_forecast -> {latitude, longitude, periods[{name, temperature,
                   temperature_unit, wind_speed, wind_direction,
                   detailed_forecast}]}

Also limits the forecast to the next 5 periods, which the other four already
did, and uses `??` on the alert fields because NWS sends explicit nulls
rather than omitting them.

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>

* Address review feedback on the TypeScript weather server

- Point the structured-content link at the dated 2026-07-28 spec rather
  than /draft, since the surrounding text names that revision.
- Model a missing temperature as null instead of 0. `ForecastPeriod.temperature`
  is optional upstream, so the old `?? 0` published an incorrect but
  valid-looking 0degF rather than admitting the gap. The output schema now
  declares `z.number().nullable()`, and formatPeriod renders "Temperature:
  Unknown" for the null case instead of "null degF".
- Fall back to "Unknown" for a missing wind direction, matching the
  neighbouring fields.
- Report out-of-band errors via serveStdio's onerror callback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Require Node 24 in the TypeScript examples

The client declared `>=20.0.0` and the server declared no floor at all,
while its lockfile root still recorded `>=16.0.0`. Node 20 reached end of
life on 2026-04-30, so it should not be what the examples ask people to
install.

Both packages now require `>=24.0.0`, the Active LTS, matching the Node 24
that ci.yml already provisions and the reasoning its comment already gives.
The SDK's own floor is `>=20`, but that is the oldest runtime the library
tolerates, not what an example should recommend. Lockfile roots regenerated;
no dependency versions move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 22:20:07 +02:00
olaservo
e83a860df2 Link the dated spec revision, not /draft
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.

Came in with #164. The same fix is on the open Go, Rust, and Ruby branches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 19:49:04 -07:00
olaservo
c99de72f48 Link the dated spec revision, not /draft
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>
2026-08-16 18:21:18 -07:00
olaservo
b625be7725 Link the dated spec revision, not /draft
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>
2026-08-16 18:21:17 -07:00
olaservo
1e6af8799a Link the dated spec revision, not /draft
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>
2026-08-16 18:21:17 -07:00
olaservo
f24bf97412 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>
2026-08-09 21:20:39 -07:00
olaservo
b8b3289475 Advertise only the capability this server actually has
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>
2026-08-09 21:20:39 -07:00
olaservo
d913f3a150 Add structured output to the Ruby weather server and client
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>
2026-08-09 21:20:39 -07:00
olaservo
75796e0c98 Cover the Ruby MCP client in the smoke tests
CI installed no Ruby toolchain and the suite ran no Ruby example, so
nothing under mcp-client-ruby or weather-server-ruby was exercised by a
green build. A dependency change there -- #177 removes the base64 gem
now that the anthropic gem declares it -- merges on the strength of a
check that never resolved its Gemfile.

The client is drivable without credentials: it connects before it looks
for ANTHROPIC_API_KEY and exits 0 when there is none, the same shape the
Python and TypeScript clients already rely on for their coverage.

The Ruby weather server stays uncovered. The mcp gem's server does not
emit the resultType field that protocol revision 2026-07-28 makes
mandatory, so the test client rejects its responses; that is a fix for
the gem rather than something an example can work around. The README
said the gem could not negotiate the revision at all, which stopped
being true at 1.1.0 -- it negotiates, then answers incompletely.

Ruby 3.4 rather than the 3.2.0 floor the anthropic gem declares: 3.2
reached end of life in March 2026, and a gem's floor says what it
tolerates, not what an example should tell people to run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:11:42 -07:00
Koichi ITO
12ef7bc7d6
Update Ruby MCP client example (#177)
Follow-up to #121.

This PR removes the `base64` dependency from the Gemfile, as it became a runtime dependency of
Claude SDK for Ruby v1.60.0+.
https://github.com/anthropics/anthropic-sdk-ruby/pull/161#issuecomment-5221400980
2026-08-09 20:52:33 -07:00
olaservo
ce055ae1fe Drop the hand-written list_tools now that rmcp emits cache hints
`#[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.
2026-08-09 17:52:24 -07:00
olaservo
506e1f70de 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>
2026-08-09 15:36:39 -07:00
olaservo
d6cf28629f Update to rmcp 3.1.0
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>
2026-08-09 15:36:38 -07:00
olaservo
65d1a92cf8 Add structured output to the Rust weather server and client
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>
2026-08-09 15:36:38 -07:00
olaservo
a0ce1f89be 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>
2026-08-09 15:36:33 -07:00
olaservo
8ec30b939c Stop advertising the deprecated logging capability
go-sdk defaults ServerCapabilities to {"logging":{}} when ServerOptions is
nil — "for historical reasons", as its own doc comment puts it. This server
does no logging, and logging is deprecated as of 2026-07-28 (SEP-2577), so an
example for that revision should not be advertising it.

Passing an empty Capabilities suppresses the default. The tools capability is
still inferred from the registered tools, so server/discover now reports
{"tools":{"listChanged":true}} rather than logging alongside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 15:36:33 -07:00
olaservo
88beed0069 Publish forecast periods in the shared shape
Forecast.Periods reused ForecastPeriod, the struct that decodes the NWS
response, so its camelCase json tags leaked into the declared outputSchema:
temperatureUnit and detailedForecast where the other four quickstarts publish
temperature_unit and detailed_forecast.

Splits out a Period output type, for the same reason Alert is already
separate from AlertProperties: the shape NWS sends and the shape the tool
publishes are different contracts and should not be one struct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 15:36:33 -07:00
olaservo
bf55c99d3f Update to go-sdk v1.7.0 and drop two committed binaries
v1.7.0 shipped on 2026-07-27, so the pin moves off v1.7.0-pre.3.

Removes mcp-client-go.exe and weather-server-go/weather.exe, which were
committed by accident in the previous commit — 14MB of build output that
should never have been in the tree. The tests branch adds the ignore rules
that keep them out.

Also from review: the server README claimed both tools set an OutputSchema.
Only get_alerts does; get_forecast relies on inference from the handler
return type, which is the idiomatic spelling and worth saying so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 15:36:33 -07:00
olaservo
84ffd2d7d2 Add structured output to the Go weather server and client
Both tools declare an OutputSchema and return StructuredContent alongside the
text.

get_alerts returns []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. The declared schema narrows the inferred ["null","array"] root to
"array": jsonschema-go infers the nullable form because a nil slice is valid
Go, but the handler always builds a slice.

Error paths return an error: a tool declaring an OutputSchema 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, using jsonschema-go - already an SDK dependency, so no new
one. Each channel goes to its stated reader: Content is forwarded to the
model, StructuredContent is used as data, reporting how many items came back.

The client no longer treats a missing .env as fatal and checks for
ANTHROPIC_API_KEY after connecting rather than before, matching the Python and
TypeScript clients, so the connection can be exercised without credentials.

Requires go-sdk v1.7.0-pre.3: non-object output schema support (SEP-2106)
landed after v1.6.1 and is in no stable release. Model identifier moves to
claude-sonnet-5; the second call in the tool loop used a hardcoded
ModelClaude3_7SonnetLatest while the first used the package variable, so both
now use the same one.

Note that the SDK 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>
2026-08-09 15:36:32 -07:00
Ola Hungerford
8e373f0f2e
Add structured content to the Python weather server and client and update to v2 (#164)
* 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>
2026-08-09 14:46:03 +02:00
Ola Hungerford
f587167452
Assert on structured output in the smoke tests and update to v2 (#163)
* 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>
2026-08-03 19:42:57 +02:00
Alex Akimov
7b7f81e4c8
Merge pull request #162 from modelcontextprotocol/dependabot/npm_and_yarn/mcp-client-typescript/npm_and_yarn-841e2b793f
Bump the npm_and_yarn group across 3 directories with 2 updates
2026-07-23 23:07:01 +02:00
dependabot[bot]
43e8923a2f
Bump the npm_and_yarn group across 3 directories with 2 updates
Bumps the npm_and_yarn group with 2 updates in the /mcp-client-typescript directory: [fast-uri](https://github.com/fastify/fast-uri) and [hono](https://github.com/honojs/hono).
Bumps the npm_and_yarn group with 2 updates in the /tests/helpers directory: [fast-uri](https://github.com/fastify/fast-uri) and [hono](https://github.com/honojs/hono).
Bumps the npm_and_yarn group with 2 updates in the /weather-server-typescript directory: [fast-uri](https://github.com/fastify/fast-uri) and [hono](https://github.com/honojs/hono).


Updates `fast-uri` from 3.1.2 to 3.1.4
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.4)

Updates `hono` from 4.12.25 to 4.12.31
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.25...v4.12.31)

Updates `fast-uri` from 3.1.2 to 3.1.4
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.4)

Updates `hono` from 4.12.25 to 4.12.31
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.25...v4.12.31)

Updates `fast-uri` from 3.1.2 to 3.1.4
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.4)

Updates `hono` from 4.12.25 to 4.12.31
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.25...v4.12.31)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: hono
  dependency-version: 4.12.31
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-uri
  dependency-version: 3.1.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: hono
  dependency-version: 4.12.31
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-uri
  dependency-version: 3.1.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: hono
  dependency-version: 4.12.31
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-23 15:14:04 +00:00
Alex Akimov
08d99ed34c
Merge pull request #161 from modelcontextprotocol/dependabot/npm_and_yarn/mcp-client-typescript/npm_and_yarn-7e3c182b7c
Bump the npm_and_yarn group across 3 directories with 1 update
2026-07-23 17:11:48 +02:00
dependabot[bot]
4cd6e3cfac
Bump the npm_and_yarn group across 3 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /mcp-client-typescript directory: [body-parser](https://github.com/expressjs/body-parser).
Bumps the npm_and_yarn group with 1 update in the /tests/helpers directory: [body-parser](https://github.com/expressjs/body-parser).
Bumps the npm_and_yarn group with 1 update in the /weather-server-typescript directory: [body-parser](https://github.com/expressjs/body-parser).


Updates `body-parser` from 2.2.2 to 2.3.0
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0)

Updates `body-parser` from 2.2.2 to 2.3.0
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0)

Updates `body-parser` from 2.2.2 to 2.3.0
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-version: 2.3.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: body-parser
  dependency-version: 2.3.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: body-parser
  dependency-version: 2.3.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 22:10:09 +00:00
Alex Akimov
206ddfe774
Merge pull request #160 from modelcontextprotocol/dependabot/uv/mcp-client-python/uv-9fe5b90e09
Bump the uv group across 2 directories with 1 update
2026-07-18 20:00:38 +02:00
dependabot[bot]
b14bf6ee71
Bump the uv group across 2 directories with 1 update
Bumps the uv group with 1 update in the /mcp-client-python directory: [mcp](https://github.com/modelcontextprotocol/python-sdk).
Bumps the uv group with 1 update in the /weather-server-python directory: [mcp](https://github.com/modelcontextprotocol/python-sdk).


Updates `mcp` from 1.27.2 to 1.28.1
- [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases)
- [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md)
- [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.27.2...v1.28.1)

Updates `mcp` from 1.27.2 to 1.28.1
- [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases)
- [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md)
- [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.27.2...v1.28.1)

---
updated-dependencies:
- dependency-name: mcp
  dependency-version: 1.28.1
  dependency-type: direct:production
  dependency-group: uv
- dependency-name: mcp
  dependency-version: 1.28.1
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-18 17:44:14 +00:00
Alex Akimov
1908637b47
Merge pull request #159 from modelcontextprotocol/dependabot/uv/mcp-client-python/uv-ca46f69960
Bump the uv group across 2 directories with 1 update
2026-07-18 19:42:04 +02:00