You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
All clients now take the server as a command plus its arguments, identify themselves the same way, report usage errors the same way and quit cleanly on Ctrl-C.
Launching: pass the command that starts the server and its arguments, as Go and Rust already did. TypeScript and Ruby no longer start .py servers with bare python3, which failed for the uv-managed Python server
Client info: every client sends mcp-client- / 1.0.0
Usage errors: printed to stderr, exit status 1
Ctrl-C: quits like quit and shuts the server down. Go and Rust read input on a separate thread; Python reads stdin's raw stream on a daemon thread, so exiting doesn't wait for a pending read
Tools: listed once at connect time in every client (Python listed them again on each query)
Python: import sys at the top, drop the unused pathlib import
.env.example: add the missing Rust one, empty Go placeholder
Tests: the mock server rejects clients that don't send the expected client info, the smoke test launches it as a command and checks the usage error
READMEs: run commands updated for the command-plus-args form
How Has This Been Tested?
Locally.
Breaking Changes
N/a
Types of changes
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)
All clients now take the server as a command plus its arguments, identify
themselves the same way, report usage errors the same way and quit cleanly
on Ctrl-C.
- Launching: pass the command that starts the server and its arguments,
as Go and Rust already did. TypeScript and Ruby no longer start .py
servers with bare python3, which failed for the uv-managed Python server
- Client info: every client sends mcp-client-<lang> / 1.0.0
- Usage errors: printed to stderr, exit status 1
- Ctrl-C: quits like `quit` and shuts the server down. Go and Rust read
input on a separate thread; Python reads stdin's raw stream on a daemon
thread, so exiting doesn't wait for a pending read
- Tools: listed once at connect time in every client (Python listed them
again on each query)
- Python: import sys at the top, drop the unused pathlib import
- .env.example: add the missing Rust one, empty Go placeholder
- Tests: the mock server rejects clients that don't send the expected
client info, the smoke test launches it as a command and checks the
usage error
- READMEs: run commands updated for the command-plus-args form
Closes#269
This PR addresses #269 by aligning the five tutorial clients around a common command-plus-arguments launch interface, client identity, and exit behavior.
Changes:
Standardizes server launching, client metadata, usage errors, and tool caching.
Adds interrupt handling and aligns API-key environment examples.
Updates launch documentation and smoke tests, including client-info validation.
File
Description
weather-server-typescript/README.md
Adds the explicit Node server command.
weather-server-ruby/README.md
Adds the explicit Ruby server command.
weather-server-python/README.md
Documents launching the server through uv.
weather-server-go/README.md
Uses go run . for the client.
tests/smoke-test.sh
Checks usage errors and updates server launch arguments.
tests/README.md
Updates the tool-loop test command.
tests/helpers/mock-mcp-server.ts
Validates client names and versions.
mcp-client-typescript/README.md
Documents command-plus-arguments launching.
mcp-client-typescript/index.ts
Updates launching, client identity, and usage errors.
mcp-client-rust/src/main.rs
Adds interrupt handling and background stdin reading.
mcp-client-rust/.env.example
Adds an empty API-key placeholder.
mcp-client-ruby/README.md
Documents command-plus-arguments launching.
mcp-client-ruby/client.rb
Aligns launching, identity, tool caching, and interrupt handling.
mcp-client-python/README.md
Updates Python and TypeScript server launch examples.
mcp-client-python/client.py
Aligns launching, identity, caching, and interruptible input.
mcp-client-go/README.md
Uses go run . in launch examples.
mcp-client-go/main.go
Adds signal cancellation and updates client metadata.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Python’s input thread can raise exceptions during Ctrl-C cleanup, and shared tests lack SIGINT shutdown coverage with stdin kept open at the prompt and during requests across all five clients.
No open issues 🎉
2 previously missed issues
In code that hasn't changed since last review
Guard read callbacks after cancellation or loop shutdown
mcp-client-python/client.py:230
Ctrl-C cancels the future awaited by read_line, but the daemon thread keeps reading. If stdin then supplies a line or reaches EOF during cleanup, these callbacks emit InvalidStateError; completing the read after the loop closes can also raise RuntimeError. Check line.done() inside the scheduled callback, and ignore completion when the loop has already closed.
Test SIGINT shutdown with open stdin across all clients
mcp-client-rust/src/main.rs:500
The shared tool-loop helper exits with quit and never sends SIGINT, leaving the new interrupt handling and pending-input shutdown behavior untested. Add shared scenarios for all five clients that send SIGINT at the prompt with stdin kept open and during a request, then verify timely exit with status 0 and mock-server shutdown. Keeping stdin open is necessary to catch the pending-read hang this change targets.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
A blocking Python API call prevents prompt Ctrl-C cancellation and cleanup, while test-helper exit handling and incomplete build checks can hang or prevent the new interrupt tests from running.
Use async Anthropic requests to allow Ctrl-C cancellation
mcp-client-python/client.py:274
On Python 3.11+, Ctrl-C cannot promptly stop an in-flight query because process_query() calls synchronous Anthropic.messages.create() on the event-loop thread. A hanging request blocks task cancellation and cleanup, so the new “during a request” interrupt test times out. Use AsyncAnthropic and await the Messages API call so cancellation can stop the request and shut down the MCP server.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because unpublished findings flag silent cancellation of in-flight queries on stdin closure and conflicting launch instructions.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Ctrl-C in an interactive terminal bypasses the process-only SIGINT listener, leaving in-flight API or tool requests running and delaying cleanup because readline’s SIGINT event is not handled.
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
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
Closes #269
All clients now take the server as a command plus its arguments, identify themselves the same way, report usage errors the same way and quit cleanly on Ctrl-C.
quitand shuts the server down. Go and Rust read input on a separate thread; Python reads stdin's raw stream on a daemon thread, so exiting doesn't wait for a pending readHow Has This Been Tested?
Locally.
Breaking Changes
N/a
Types of changes
Checklist
Additional context
Assisted by Claude Code