Conversation
…l-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.
There was a problem hiding this comment.
🟡 Changes recommended
Ruby drops required thinking blocks, and the smoke test can incorrectly accept missing is_error flags.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Standardizes multi-turn tool use across MCP clients and adds smoke-test coverage for tool loops.
Changes:
- Adds capped, repeated tool-call handling with consistent token limits.
- Preserves final response text and forwards tool errors.
- Adds scripted smoke tests for Python, TypeScript, Ruby, and Go.
File summaries
| File | Description |
|---|---|
tests/utils.sh |
Includes the new test helper in build checks. |
tests/smoke-test.sh |
Runs tool-loop tests for four clients. |
tests/README.md |
Documents tool-loop scenarios and usage. |
tests/helpers/tool-loop-test.ts |
Implements the fake API and assertions. |
mcp-client-typescript/index.ts |
Updates token limits and result handling. |
mcp-client-rust/src/main.rs |
Adds a capped multi-turn tool loop. |
mcp-client-ruby/client.rb |
Adds multi-turn execution and error forwarding. |
mcp-client-python/client.py |
Preserves the final capped response. |
mcp-client-go/main.go |
Adds a capped multi-turn tool loop. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
The Rust client still loses signed adaptive-thinking blocks before tool-result follow-ups, causing API rejection.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
tests/smoke-test.sh:161
- This comment is stale: the helper now sends three queries, not one, and two scenarios specifically exercise the exact-limit and over-limit behavior. Describing only the parallel-tool scenario obscures why the additional API calls are expected.
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
tools, so any request that needs a second tool call stalled. Go silently droppedtool_useblocks from that final response.tool_useblock, each time with a singletool_result. Parallel tool calls produced a malformed message sequence and an API 400.isErrorasis_error, so the model could not tell a failed tool call from a successful one. Rust cannot set the flag either (see below).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_resultof a turn in one user message, passtoolson every call, and stop afterMAX_TOOL_TURNS = 10.max_tokensis 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
genaicrate. Its tool-response type has nois_errorfield, so an error result is marked with anError:prefix in the text and the limitation is commented.New smoke test
tests/helpers/tool-loop-test.tsstarts a fake Anthropic Messages API on a loopback port, points a client at it throughANTHROPIC_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 (anisErrorresult), then a final answer, and asserts that tools are passed each time,max_tokensis 10000, alltool_results arrive in one message with matching ids, and the error is forwarded.smoke-test.shruns it for Python, TypeScript, Go, and Ruby.The Rust client is excluded for now:
genaireads no endpoint variable, and the client negotiates protocol2025-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.shpasses for every example.mainwas run through the new test. It fails 10 of 19 checks, on exactly the bugs listed above.golang:1.25andruby:3.4containers.Breaking Changes
None. The clients' command lines, output format, and no-key behavior are unchanged.
Types of changes
Checklist
Additional context
Two pre-existing issues surfaced while testing and are left for a follow-up:
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 to2025-11-25, so the client must request2026-07-28explicitly.AI assistance: the analysis, code changes, test helper, and this description were prepared partially manually, partially with Claude Code and reviewed by me.