ruby-sdk/examples/stdio_client.rb
Koichi ITO 21eef8df9a Add MCP::Client::Stdio transport
## Motivation and Context

The `MCP::Client` currently only supports HTTP transport (`MCP::Client::HTTP`).
To achieve parity with other MCP SDKs (Python, TypeScript, C#) which provide
stdio client transports for spawning and communicating with server subprocesses,
the Ruby SDK needs a stdio client transport.
https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio

This enables building MCP clients that launch server processes and communicate
via stdin/stdout using newline-delimited JSON-RPC 2.0 messages.

## How Has This Been Tested?

Added tests for process spawning, protocol initialization handshake,
notification handling, error handling, and transport close lifecycle.

## Additional context

Enabled the `Lint/IncompatibleIoSelectWithFiberScheduler` cop in `.rubocop.yml`
to detect accidental use of `IO.select` instead of `IO#wait_readable`.
2026-03-19 12:41:27 +09:00

58 lines
1.8 KiB
Ruby

# frozen_string_literal: true
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
require "mcp"
require "json"
# Simple stdio client example that connects to the stdio_server.rb example
# Usage: ruby examples/stdio_client.rb
server_script = File.expand_path("stdio_server.rb", __dir__)
transport = MCP::Client::Stdio.new(command: "ruby", args: [server_script])
client = MCP::Client.new(transport: transport)
begin
# List available tools
puts "=== Listing tools ==="
tools = client.tools
tools.each do |tool|
puts " Tool: #{tool.name} - #{tool.description}"
end
# Call the example_tool (adds two numbers)
puts "\n=== Calling tool: example_tool ==="
tool = tools.find { |t| t.name == "example_tool" }
response = client.call_tool(tool: tool, arguments: { a: 5, b: 3 })
puts " Response: #{JSON.pretty_generate(response.dig("result", "content"))}"
# Call the echo tool
puts "\n=== Calling tool: echo ==="
tool = tools.find { |t| t.name == "echo" }
response = client.call_tool(tool: tool, arguments: { message: "Hello from stdio client!" })
puts " Response: #{JSON.pretty_generate(response.dig("result", "content"))}"
# List prompts
puts "\n=== Listing prompts ==="
prompts = client.prompts
prompts.each do |prompt|
puts " Prompt: #{prompt["name"]} - #{prompt["description"]}"
end
# List resources
puts "\n=== Listing resources ==="
resources = client.resources
resources.each do |resource|
puts " Resource: #{resource["name"]} (#{resource["uri"]})"
end
# Read a resource
puts "\n=== Reading resource: https://test_resource.invalid ==="
contents = client.read_resource(uri: "https://test_resource.invalid")
puts " Response: #{JSON.pretty_generate(contents)}"
rescue => e
puts "Error: #{e.message}"
puts e.backtrace.first(5).join("\n")
ensure
transport.close
end