Skip to content

Make the five clients consistent (#269) - #289

Closed
a-akimov wants to merge 5 commits into
mainfrom
fix/client-polish
Closed

a-akimov wants to merge 5 commits into
mainfrom
fix/client-polish

Conversation

@a-akimov

@a-akimov a-akimov commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

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.

  • 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)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Assisted by Claude Code

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
Copilot AI balanced review requested due to automatic review settings November 6, 2026 18:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The Python input reader can hang on non-UTF-8 input, while Go startup cancellation can exit with an error and bypass session cleanup.

2 open issues
What changed in this PR

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.
mcp-client-go/​.env.example Empties the API-key placeholder.

Review effort: Balanced

Comment thread mcp-client-go/main.go Resolved
Comment thread mcp-client-python/client.py Resolved
Copilot AI balanced review requested due to automatic review settings November 6, 2026 21:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium severity 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.

Medium severity 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.

2 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 6, 2026 23:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

2 open issues
1 previously missed issue

In code that hasn't changed since last review

High severity 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.

Review effort: Balanced

Comment thread tests/helpers/interrupt-test.ts Resolved
Comment thread tests/smoke-test.sh
TEST_CLIENT="${PROJECT_ROOT}/tests/helpers/build/mcp-test-client.js"
MOCK_SERVER="${PROJECT_ROOT}/tests/helpers/build/mock-mcp-server.js"
TOOL_LOOP_TEST="${PROJECT_ROOT}/tests/helpers/build/tool-loop-test.js"
INTERRUPT_TEST="${PROJECT_ROOT}/tests/helpers/build/interrupt-test.js"
Copilot AI balanced review requested due to automatic review settings November 6, 2026 23:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

1 open issue
1 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 6, 2026 23:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

2 open issues

Review effort: Balanced

interrupt.abort();
rl.close();
};
process.once("SIGINT", onSigint);
@a-akimov

a-akimov commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Closing this one, keeping #290 instead

@a-akimov a-akimov closed this Nov 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Client polish and consistency

2 participants