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`.
This commit is contained in:
parent
8823bd34ef
commit
21eef8df9a
2 changed files with 1079 additions and 4 deletions
|
|
@ -15,7 +15,26 @@ $ ruby examples/stdio_server.rb
|
|||
{"jsonrpc":"2.0","id":0,"method":"tools/list"}
|
||||
```
|
||||
|
||||
### 2. HTTP Server (`http_server.rb`)
|
||||
### 2. STDIO Client (`stdio_client.rb`)
|
||||
|
||||
A client that connects to the STDIO server using the `MCP::Client::Stdio` transport.
|
||||
This demonstrates how to use the SDK's built-in client classes to interact with a server subprocess.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```console
|
||||
$ ruby examples/stdio_client.rb
|
||||
```
|
||||
|
||||
The client will automatically launch `stdio_server.rb` as a subprocess and demonstrate:
|
||||
|
||||
- Listing and calling tools
|
||||
- Listing prompts
|
||||
- Listing and reading resources
|
||||
- Automatic MCP protocol initialization
|
||||
- Transport cleanup on exit
|
||||
|
||||
### 3. HTTP Server (`http_server.rb`)
|
||||
|
||||
A standalone HTTP server built with Rack that implements the MCP Streamable HTTP transport protocol. This demonstrates how to create a web-based MCP server with session management and Server-Sent Events (SSE) support.
|
||||
|
||||
|
|
@ -41,7 +60,7 @@ The server will start on `http://localhost:9292` and provide:
|
|||
- **Prompts**: `ExamplePrompt` - echoes back arguments as a prompt
|
||||
- **Resources**: `test_resource` - returns example content
|
||||
|
||||
### 3. HTTP Client Example (`http_client.rb`)
|
||||
### 4. HTTP Client Example (`http_client.rb`)
|
||||
|
||||
A client that demonstrates how to interact with the HTTP server using all MCP protocol methods.
|
||||
|
||||
|
|
@ -67,7 +86,7 @@ The client will demonstrate:
|
|||
- Listing and reading resources
|
||||
- Session cleanup
|
||||
|
||||
### 4. Streamable HTTP Server (`streamable_http_server.rb`)
|
||||
### 5. Streamable HTTP Server (`streamable_http_server.rb`)
|
||||
|
||||
A specialized HTTP server designed to test and demonstrate Server-Sent Events (SSE) functionality in the MCP protocol.
|
||||
|
||||
|
|
@ -90,7 +109,7 @@ $ ruby examples/streamable_http_server.rb
|
|||
|
||||
The server will start on `http://localhost:9393` and provide detailed instructions for testing SSE functionality.
|
||||
|
||||
### 5. Streamable HTTP Client (`streamable_http_client.rb`)
|
||||
### 6. Streamable HTTP Client (`streamable_http_client.rb`)
|
||||
|
||||
An interactive client that connects to the SSE stream and provides a menu-driven interface for testing SSE functionality.
|
||||
|
||||
|
|
|
|||
58
examples/stdio_client.rb
Normal file
58
examples/stdio_client.rb
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# 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
|
||||
Loading…
Reference in a new issue