Require calling MCP::Client#connect before sending requests on stdio transport

## Motivation and Context

The MCP specification requires an `initialize` request followed by `notifications/initialized`
before any other interaction. Issue #334 makes that handshake explicit on the stdio client,
matching the Python SDK (`ClientSession.initialize()`) and TypeScript SDK (`Client.connect(transport)`).

The change was staged to avoid a hard break. Phase 1 (#336) added `MCP::Client::Stdio#connect`
while keeping an implicit-init compatibility shim on the first `send_request`.
Phase 2 (#338) emitted a deprecation warning when that shim ran. The warning has shipped since 0.16.0,
so this final phase removes the shim.

## How Has This Been Tested?

`send_request` now raises a `RuntimeError` when `connect` has not been called, and no longer
auto-starts the subprocess. `connect` is the sole entry point that spawns the process and
performs the handshake, which avoids leaving an orphaned subprocess behind.

The stdio test suite was reworked to call `connect` explicitly, a regression test was added for
the new guard, and `rake test` passes (1145 runs, 0 failures).

## Breaking Changes

Stdio clients that send requests without calling `MCP::Client#connect` now raise a `RuntimeError`
instead of silently initializing. Call `client.connect` before the first request.

Closes #334
This commit is contained in:
Koichi ITO 2026-06-20 17:10:46 +09:00
commit d6a368a54f

View file

@ -128,9 +128,7 @@ module MCP
@server_info
end
# Returns true once `connect` (or the implicit handshake on the first
# `send_request`) has completed. Returns false before the handshake
# and after `close`.
# Returns true once `connect` has completed the handshake. Returns false before the handshake and after `close`.
def connected?
@initialized
end
@ -140,11 +138,7 @@ module MCP
# write does not race ahead of the request write on the wire. The yield happens inside `@write_mutex`,
# so any subsequent `send_notification` write waits for the mutex and is guaranteed to land after the request.
def send_request(request:)
start unless @started
unless @initialized
warn("Calling `MCP::Client::Stdio#send_request` without calling `MCP::Client#connect` is deprecated. Use `MCP::Client#connect` before sending requests instead.", uplevel: 1)
connect
end
raise "MCP::Client#connect must be called before sending requests." unless @initialized
@write_mutex.synchronize do
write_message(request)