The official Ruby SDK for the Model Context Protocol. Maintained in collaboration with Shopify. Trusted by 600+ developers.
Documentation
MCP Ruby SDK

The official Ruby SDK for Model Context Protocol servers and clients.
Installation
Add this line to your application's Gemfile:
gem 'mcp'And then execute:
$ bundle installOr install it yourself as:
$ gem install mcpYou may need to add additional dependencies depending on which features you wish to access.
Building an MCP Server
The MCP::Server class is the core component that handles JSON-RPC requests and responses.
It implements the Model Context Protocol specification, handling model context requests and responses.
Key Features
- Implements JSON-RPC 2.0 message handling
- Supports protocol initialization and capability negotiation
- Manages tool registration and invocation
- Supports prompt registration and execution
- Supports resource registration and retrieval
- Supports stdio & Streamable HTTP (including SSE) transports
- Supports notifications for list changes (tools, prompts, resources)
- Supports roots (server-to-client filesystem boundary queries)
- Supports sampling (server-to-client LLM completion requests)
- Supports cursor-based pagination for list operations
Supported Methods
initialize- Initializes the protocol and returns server capabilitiesping- Simple health checktools/list- Lists all registered tools and their schemastools/call- Invokes a specific tool with provided argumentsprompts/list- Lists all registered prompts and their schemasprompts/get- Retrieves a specific prompt by nameresources/list- Lists all registered resources and their schemasresources/read- Retrieves a specific resource by nameresources/templates/list- Lists all registered resource templates and their schemasresources/subscribe- Subscribes to updates for a specific resourceresources/unsubscribe- Unsubscribes from updates for a specific resourcecompletion/complete- Returns autocompletion suggestions for prompt arguments and resource URIsroots/list- Requests filesystem roots from the client (server-to-client)sampling/createMessage- Requests LLM completion from the client (server-to-client)elicitation/create- Requests user input from the client (server-to-client)
Usage
Stdio Transport
If you want to build a local command-line application, you can use the stdio transport:
require "mcp"
# Create a simple tool
class ExampleTool [!IMPORTANT]
> `MCP::Server::Transports::StreamableHTTPTransport` stores session and SSE stream state in memory,
> so it must run in a single process. Use a single-process server (e.g., Puma with `workers 0`).
> Multi-process configurations (Unicorn, or Puma with `workers > 0`) fork separate processes that
> do not share memory, which breaks session management and SSE connections.
>
> When running multiple server instances behind a load balancer, configure your load balancer to use
> sticky sessions (session affinity) so that requests with the same `Mcp-Session-Id` header are always
> routed to the same instance.
>
> Stateless mode (`stateless: true`) does not use sessions and works with any server configuration.
##### Rails (mount)
`StreamableHTTPTransport` is a Rack app that can be mounted directly in Rails routes:config/routes.rb
server = MCP::Server.new(
name: "my_server",
title: "Example Server Display Name",
version: "1.0.0",
instructions: "Use the tools of this server as a last resort",
tools: [SomeTool, AnotherTool],
prompts: [MyPrompt],
)
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
Rails.application.routes.draw do
mount transport => "/mcp"
end
`mount` directs all HTTP methods on `/mcp` to the transport. `StreamableHTTPTransport` internally dispatches
`POST` (client-to-server JSON-RPC messages, with responses optionally streamed via SSE),
`GET` (optional standalone SSE stream for server-to-client messages), and `DELETE` (session termination) per
the [MCP Streamable HTTP transport spec](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http),
so no additional route configuration is needed.
##### Rails (controller)
While the mount approach creates a single server at boot time, the controller approach creates a new server per request.
This allows you to customize tools, prompts, or configuration based on the request (e.g., different tools per route).
`StreamableHTTPTransport#handle_request` returns proper HTTP status codes (e.g., 202 Accepted for notifications):class McpController (exception, server_context) {
# Your exception reporting logic here
# For example with Bugsnag:
Bugsnag.notify(exception) do |report|
report.add_metadata(:model_context_protocol, server_context)
end
}
config.around_request = ->(data, &request_handler) {
logger.info("Start: #{data[:method]}")
request_handler.call
logger.info("Done: #{data[:method]}, tool: #{data[:tool_name]}")
}
end
or by creating an explicit configuration and passing it into the server.
This is useful for systems where an application hosts more than one MCP server but
they might require different configurations.configuration = MCP::Configuration.new
configuration.exception_reporter = ->(exception, server_context) {
# Your exception reporting logic here
# For example with Bugsnag:
Bugsnag.notify(exception) do |report|
report.add_metadata(:model_context_protocol, server_context)
end
}
configuration.around_request = ->(data, &request_handler) {
logger.info("Start: #{data[:method]}")
request_handler.call
logger.info("Done: #{data[:method]}, tool: #{data[:tool_name]}")
}
server = MCP::Server.new(
# ... all other options
configuration:,
)
### Server Context and Configuration Block Data
#### `server_context`
The `server_context` is a user-defined hash that is passed into the server instance and made available to tool and prompt calls.
It can be used to provide contextual information such as authentication state, user IDs, or request-specific data.
**Type:**server_context: { [String, Symbol] => Any }
**Example:**server = MCP::Server.new(
name: "my_server",
server_context: { user_id: current_user.id, request_id: request.uuid }
)
This hash is then passed as the `server_context` keyword argument to tool and prompt calls.
Note that exception and instrumentation callbacks do not receive this user-defined hash.
See the relevant sections below for the arguments they receive.
#### Request-specific `_meta` Parameter
The MCP protocol supports a special [`_meta` parameter](https://modelcontextprotocol.io/specification/2025-06-18/basic#general-fields) in requests that allows clients to pass request-specific metadata. The server automatically extracts this parameter and makes it available to tools and prompts as a nested field within the `server_context`.
**Access Pattern:**
When a client includes `_meta` in the request params, it becomes available as `server_context[:_meta]`:class MyTool }`
for request handling, { notification: "tools_list_changed" } for notification delivery).
This is not the user-defined server_context passed to Server.new.
Signature:
exception_reporter = ->(exception, server_context) { ... }##### Around Request
The around_request hook wraps request handling, allowing you to execute code before and after each request.
This is useful for Application Performance Monitoring (APM) tracing, logging, or other observability needs.
The hook receives a data hash and a request_handler block. You must call request_handler.call to execute the request:
Signature:
around_request = ->(data, &request_handler) { request_handler.call }**data availability by timing:**
- Before
request_handler.call:method - After
request_handler.call:tool_name,tool_arguments,prompt_name,resource_uri,error,client - Not available inside
around_request:duration(added afteraround_requestreturns)
[!NOTE]
tool_name,prompt_nameandresource_urimay only be populated for the corresponding request methods(
tools/call,prompts/get,resources/read), and may not be set depending on how the request is handled(for example,
prompt_nameis not recorded when the prompt is not found).
durationis added afteraround_requestreturns, so it is not visible from within the hook.
Example:
MCP.configure do |config|
config.around_request = ->(data, &request_handler) {
logger.info("Start: #{data[:method]}")
request_handler.call
logger.info("Done: #{data[:method]}, tool: #{data[:tool_name]}")
}
end##### Instrumentation Callback (soft-deprecated)
[!NOTE]
instrumentation_callbackis soft-deprecated. Usearound_requestinstead.To migrate, wrap the call in
begin/ensureso the callback still runs when the request fails:```ruby
# Before
config.instrumentation_callback = ->(data) { log(data) }
# After
config.around_request = ->(data, &request_handler) do
request_handler.call
ensure
log(data)
end
```
Note that
data[:duration]is not available insidearound_request.If you need it, measure elapsed time yourself within the hook, or keep using
instrumentation_callback.
The instrumentation callback is called after each request finishes, whether successfully or with an error.
It receives a hash with the following possible keys:
method: (String) The protocol method called (e.g., "ping", "tools/list")tool_name: (String, optional) The name of the tool calledtool_arguments: (Hash, optional) The arguments passed to the toolprompt_name: (String, optional) The name of the prompt calledresource_uri: (String, optional) The URI of the resource callederror: (String, optional) Error code if a lookup failedduration: (Float) Duration of the call in secondsclient: (Hash, optional) Client information withnameandversionkeys, from the initialize request
Signature:
instrumentation_callback = ->(data) { ... }Server Protocol Version
The server's protocol version can be overridden using the protocol_version keyword argument:
configuration = MCP::Configuration.new(protocol_version: "2024-11-05")
MCP::Server.new(name: "test_server", configuration: configuration)If no protocol version is specified, the latest stable version will be applied by default.
The latest stable version includes new features from the draft version.
This will make all new server instances use the specified protocol version instead of the default version. The protocol version can be reset to the default by setting it to nil:
MCP::Configuration.new(protocol_version: nil)If an invalid protocol_version value is set, an ArgumentError is raised.
Be sure to check the MCP spec for the protocol version to understand the supported features for the version being set.
Exception Reporting
The exception reporter receives two arguments:
exception: The Ruby exception object that was raisedserver_context: A hash containing contextual information about where the error occurred
The server_context hash includes:
- For tool calls:
{ tool_name: "name", arguments: { ... } } - For general request handling:
{ request: { ... } }
When an exception occurs:
1. The exception is reported via the configured reporter
2. For tool calls, a generic error response is returned to the client: { error: "Internal error occurred", isError: true }
3. For other requests, the exception is re-raised after reporting
If no exception reporter is configured, a default no-op reporter is used that silently ignores exceptions.
Tools
MCP spec includes Tools which provide functionality to LLM apps.
This gem provides a MCP::Tool class that can be used to create tools in three ways:
1. As a class definition:
class MyTool [!NOTE]
> This **Tool Annotations** feature is supported starting from `protocol_version: '2025-03-26'`.
### Tool Output Schemas
Tools can optionally define an `output_schema` to specify the expected structure of their results. This works similarly to how `input_schema` is defined and can be used in three ways:
1. **Class definition with output_schema:**class WeatherTool {} on success
`#ping` raises `MCP::Client::ServerError` when the server returns a JSON-RPC error.
It raises `MCP::Client::ValidationError` when the response `result` is missing or
is not a Hash (matching the spec requirement that `result` be an object).
Transport-level errors (for example, `MCP::Client::Stdio`'s `read_timeout:` firing)
propagate as exceptions raised by the transport layer.
### Progress
The MCP Ruby SDK supports progress tracking for long-running tool operations,
following the [MCP Progress specification](https://modelcontextprotocol.io/specification/latest/server/utilities/progress).
#### How Progress Works
1. **Client Request**: The client sends a `progressToken` in the `_meta` field when calling a tool
2. **Server Notification**: The server sends `notifications/progress` messages back to the client during tool execution
3. **Tool Integration**: Tools call `server_context.report_progress` to report incremental progress
#### Server-Side: Tool with Progress
Tools that accept a `server_context:` parameter can call `report_progress` on it.
The server automatically wraps the context in an `MCP::ServerContext` instance that provides this method:class LongRunningTool Array of every tool on the server.
Use these when you want the complete list; use `list_tools(cursor:)` etc. when you need
fine-grained iteration (e.g. to stream-process pages without loading everything into memory).
### Advanced
#### Custom Methods
The server allows you to define custom JSON-RPC methods beyond the standard MCP protocol methods using the `define_custom_method` method:server = MCP::Server.new(name: "my_server")
Define a custom method that returns a result
server.define_custom_method(method_name: "add") do |params|
params[:a] + params[:b]
end
Define a custom notification method (returns nil)
server.define_custom_method(method_name: "notify") do |params|
# Process notification
nil
end
**Key Features:**
- Accepts any method name as a string
- Block receives the request parameters as a hash
- Can handle both regular methods (with responses) and notifications
- Prevents overriding existing MCP protocol methods
- Supports instrumentation callbacks for monitoring
**Usage Example:**Client request
{
"jsonrpc": "2.0",
"id": 1,
"method": "add",
"params": { "a": 5, "b": 3 }
}
Server response
{
"jsonrpc": "2.0",
"id": 1,
"result": 8
}
**Error Handling:**
- Raises `MCP::Server::MethodAlreadyDefinedError` if trying to override an existing method
- Supports the same exception reporting and instrumentation as standard methods
## Building an MCP Client
The `MCP::Client` class provides an interface for interacting with MCP servers.
This class supports:
- Liveness check via the `ping` method (`MCP::Client#ping`)
- Tool listing via the `tools/list` method (`MCP::Client#tools`)
- Tool invocation via the `tools/call` method (`MCP::Client#call_tools`)
- Resource listing via the `resources/list` method (`MCP::Client#resources`)
- Resource template listing via the `resources/templates/list` method (`MCP::Client#resource_templates`)
- Resource reading via the `resources/read` method (`MCP::Client#read_resource`)
- Prompt listing via the `prompts/list` method (`MCP::Client#prompts`)
- Prompt retrieval via the `prompts/get` method (`MCP::Client#get_prompt`)
- Completion requests via the `completion/complete` method (`MCP::Client#complete`)
- Automatic JSON-RPC 2.0 message formatting
- UUID request ID generation
Clients are initialized with a transport layer instance that handles the low-level communication mechanics.
Authorization is handled by the transport layer.
## Transport Layer Interface
If the transport layer you need is not included in the gem, you can build and pass your own instances so long as they conform to the following interface:class CustomTransport
# Sends a JSON-RPC request to the server and returns the raw response.
#
# @param request [Hash] A complete JSON-RPC request object.
# https://www.jsonrpc.org/specification#request_object
# @return [Hash] A hash modeling a JSON-RPC response object.
# https://www.jsonrpc.org/specification#response_object
def send_request(request:)
# Your transport-specific logic here
# - HTTP: POST to endpoint with JSON body
# - WebSocket: Send message over WebSocket
# - stdio: Write to stdout, read from stdin
# - etc.
end
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: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.
You'll need to add `faraday` as a dependency in order to use the HTTP transport layer. Add `event_stream_parser` as well if the server uses SSE (`text/event-stream`) responses:gem 'mcp'
gem 'faraday', '>= 2.0'
gem 'event_stream_parser', '>= 1.0' # optional, required only for SSE responses
Example usage:http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp")
client = MCP::Client.new(transport: http_transport)
List available tools
tools = client.tools
tools.each do |tool|
puts "Bearer my_token"
}
)
client = MCP::Client.new(transport: http_transport)
client.tools # will make the call using Bearer auth
You can add any custom headers needed for your authentication scheme, or for any other purpose. The client will include these headers on every request.
#### Customizing the Faraday Connection
You can pass a block to `MCP::Client::HTTP.new` to customize the underlying Faraday connection.
The block is called after the default middleware is configured, so you can add middleware or swap the HTTP adapter:http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp") do |faraday|
faraday.use MyApp::Middleware::HttpRecorder
faraday.adapter :typhoeus
end
### Tool Objects
The client provides a wrapper class for tools returned by the server:
- `MCP::Client::Tool` - Represents a single tool with its metadata
This class provides easy access to tool properties like name, description, input schema, and output schema.
## Conformance Testing
The `conformance/` directory contains a test server and runner that validate the SDK against the MCP specification using [`@modelcontextprotocol/conformance`](https://github.com/modelcontextprotocol/conformance).
See [conformance/README.md](conformance/README.md) for usage instructions.
## Documentation
- [SDK API documentation](https://rubydoc.info/gems/mcp)
- [Model Context Protocol documentation](https://modelcontextprotocol.io)Similar MCP
Based on tags & features
Trending MCP
Most active this week