Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ AllCops:
Gemspec/DevelopmentDependencies:
Enabled: true

Lint/IncompatibleIoSelectWithFiberScheduler:
Enabled: true

Minitest/LiteralAsActualArgument:
Enabled: true
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,52 @@ class CustomTransport
end
```

### Stdio Transport Layer

Use the `MCP::Client::Stdio` transport to interact with MCP servers running as subprocesses over standard input/output.

`MCP::Client::Stdio.new` accepts the following keyword arguments:

| Parameter | Required | Description |
|---|---|---|
| `command:` | Yes | The command to spawn the server process (e.g., `"ruby"`, `"bundle"`, `"npx"`). |
| `args:` | No | An array of arguments passed to the command. Defaults to `[]`. |
| `env:` | No | A hash of environment variables to set for the server process. Defaults to `nil`. |
| `read_timeout:` | No | Timeout in seconds for waiting for a server response. Defaults to `nil` (no timeout). |

Example usage:

```ruby
stdio_transport = MCP::Client::Stdio.new(
command: "bundle",
args: ["exec", "ruby", "path/to/server.rb"],
env: { "API_KEY" => "my_secret_key" },
read_timeout: 30
)
client = MCP::Client.new(transport: stdio_transport)

# List available tools.
tools = client.tools
tools.each do |tool|
puts "Tool: #{tool.name} - #{tool.description}"
end

# Call a specific tool.
response = client.call_tool(
tool: tools.first,
arguments: { message: "Hello, world!" }
)

# Close the transport when done.
stdio_transport.close
```

The stdio transport automatically handles:

- Spawning the server process with `Open3.popen3`
- MCP protocol initialization handshake (`initialize` request + `notifications/initialized`)
- JSON-RPC 2.0 message framing over newline-delimited JSON

### HTTP Transport Layer

Use the `MCP::Client::HTTP` transport to interact with MCP servers using simple HTTP requests.
Expand Down
27 changes: 23 additions & 4 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Copy link
Contributor

Choose a reason for hiding this comment

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

The canonical spelling of the stdio transport in the MCP spec is all lowercase, "stdio", if we want to match that everywhere.


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.

Expand All @@ -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.

Expand All @@ -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.

Expand All @@ -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.

Expand Down
58 changes: 58 additions & 0 deletions examples/stdio_client.rb
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions lib/mcp/client.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require_relative "client/stdio"
require_relative "client/http"
require_relative "client/tool"

Expand Down
Loading