An implementation of the Model Context Protocol (MCP) in Ruby.
Provides simple abstractions that allow you to serve prompts, resources, resource templates, and tools via MCP locally (stdio) or in production (streamable HTTP backed by Redis) with minimal effort.
| Status | Feature |
|---|---|
| âś… | Prompts |
| âś… | Resources |
| âś… | Resource Templates |
| âś… | Tools |
| âś… | Completion |
| âś… | Logging |
| âś… | Pagination |
| âś… | Environment Variables |
| âś… | STDIO Transport |
| âś… | Streamable HTTP Transport |
| âś… | List Changed Notification (Prompts) Âą |
| âś… | List Changed Notification (Resources) Âą |
| ❌ | Subscriptions (Resources) |
| âś… | List Changed Notification (Tools) Âą |
| âś… | Cancellation |
| âś… | Ping |
| âś… | Progress |
Add this line to your application's Gemfile:
gem 'model-context-protocol-rb'And then execute:
bundleOr install it yourself as:
gem install model-context-protocol-rbFor detailed installation instructions, see the Installation wiki page.
Build a simple MCP server by registering your prompts, resources, resource templates, and tools. Messages from the MCP client will be routed to the appropriate custom handler.
For command-line MCP servers that communicate via standard input/output:
server = ModelContextProtocol::Server.new do |config|
config.name = "MyMCPServer"
config.version = "1.0.0"
config.registry = ModelContextProtocol::Server::Registry.new do
prompts do
register MyPrompt
end
resources do
register MyResource
end
tools do
register MyTool
end
end
end
server.startFor HTTP-based MCP servers (e.g., Rails applications), use the singleton API. This ensures only 2 background threads exist regardless of concurrent connections.
Configure Redis (required for HTTP transport):
# config/initializers/model_context_protocol.rb
ModelContextProtocol::Server.configure_redis do |config|
config.redis_url = ENV.fetch("REDIS_URL")
config.pool_size = 20
endConfigure Puma to manage the MCP server lifecycle:
# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY", 0).to_i
# Use ->(*) to accept worker index argument passed by Puma hooks
mcp_setup = ->(*) {
ModelContextProtocol::Server.setup do |config|
config.name = "MyMCPServer"
config.version = "1.0.0"
config.transport = { type: :streamable_http }
config.registry = ModelContextProtocol::Server::Registry.new do
tools { register MyTool }
end
end
}
mcp_shutdown = ->(*) { ModelContextProtocol::Server.shutdown }
# Clustered mode: workers fork, so each needs its own MCP server
on_worker_boot(&mcp_setup)
on_worker_shutdown(&mcp_shutdown)
# Single mode: on_worker_boot isn't called, so set up directly
if ENV.fetch("WEB_CONCURRENCY", 0).to_i.zero?
mcp_setup.call
at_exit(&mcp_shutdown)
endHandle each request:
class ModelContextProtocolController < ActionController::API
include ActionController::Live
def handle
result = ModelContextProtocol::Server.serve(
env: request.env,
session_context: { user_id: current_user.id }
)
if result[:stream]
response.headers.merge!(result[:headers])
result[:stream_proc]&.call(response.stream)
else
render json: result[:json], status: result[:status] || 200
end
ensure
response.stream.close rescue nil
end
endFor a complete Rails integration example, see the Quick Start with Rails guide.
For complete configuration details including all server options, Redis configuration, and logging options, see the Building an MCP Server wiki page.
Define prompts that MCP clients can use to generate contextual message sequences.
class TestPrompt < ModelContextProtocol::Server::Prompt
define do
name "brainstorm_excuses"
description "A prompt for brainstorming excuses"
argument { name "tone"; required false }
end
def call
messages = message_history do
user_message { text_content(text: "Generate excuses with #{arguments[:tone]} tone") }
end
respond_with messages:
end
endKey features:
- Define arguments with validation and completion hints
- Build message histories with user and assistant messages
- Support for text, image, audio, and embedded resource content
For complete documentation and examples, see the Prompts wiki page.
Expose data and content to MCP clients through defined resources.
class TestResource < ModelContextProtocol::Server::Resource
define do
name "config.json"
description "Application configuration"
mime_type "application/json"
uri "file:///config.json"
end
def call
respond_with text: { setting: "value" }.to_json
end
endKey features:
- Define metadata including MIME type and URI
- Return text or binary content
- Add annotations for audience and priority
For complete documentation and examples, see the Resources wiki page.
Define parameterized resources with URI templates that clients can instantiate.
class TestResourceTemplate < ModelContextProtocol::Server::ResourceTemplate
define do
name "document-template"
description "Template for retrieving documents"
mime_type "text/plain"
uri_template "file:///{name}" do
completion :name, ["readme.txt", "config.json"]
end
end
endKey features:
- Define URI templates with parameters
- Provide completion hints for template parameters
For complete documentation and examples, see the Resource Templates wiki page.
Create callable functions that MCP clients can invoke with validated inputs.
class TestToolWithStructuredContentResponse < ModelContextProtocol::Server::Tool
define do
# The name of the tool for programmatic use
name "get_weather_data"
# The human-readable tool name for display in UI
title "Weather Data Retriever"
# A short description of what the tool does
description "Get current weather data for a location"
# The JSON schema for validating tool inputs
input_schema do
{
type: "object",
properties: {
location: {
type: "string",
description: "City name or zip code"
}
},
required: ["location"]
}
end
# The JSON schema for validating structured content
output_schema do
{
type: "object",
properties: {
temperature: {
type: "number",
description: "Temperature in celsius"
},
conditions: {
type: "string",
description: "Weather conditions description"
},
humidity: {
type: "number",
description: "Humidity percentage"
}
},
required: ["temperature", "conditions", "humidity"]
}
end
end
def call
# Use values provided by the server as context
user_id = context[:user_id]
client_logger.info("Initiating request for user #{user_id}...")
# Use values provided by clients as tool arguments
location = arguments[:location]
client_logger.info("Getting weather data for #{location}...")
# Returns a hash that validates against the output schema
weather_data = get_weather_data(location)
# Respond with structured content
respond_with structured_content: weather_data
end
private
# Simulate calling an external API to get weather data for the provided input
def get_weather_data(location)
{
temperature: 22.5,
conditions: "Partly cloudy",
humidity: 65
}
end
endKey features:
- Define input and output JSON schemas
- Return text, image, audio, or embedded resource content
- Support for structured content responses
- Cancellable and progressable operations
For complete documentation and 7 detailed examples, see the Tools wiki page.
Provide argument completion hints for prompts and resource templates.
class TestCompletion < ModelContextProtocol::Server::Completion
def call
hints = { "tone" => ["whiny", "angry", "nervous"] }
values = hints[argument_name].grep(/#{argument_value}/)
respond_with values:
end
endFor complete documentation, see the Completions wiki page.
This gem provides custom RSpec matchers and helpers for testing your MCP handlers.
require "model_context_protocol/rspec"
ModelContextProtocol::RSpec.configure!
RSpec.describe WeatherTool, type: :mcp do
it "returns weather data" do
response = call_mcp_tool(WeatherTool, { location: "New York" })
expect(response).to be_valid_mcp_tool_response
expect(response).to have_text_content(/temperature/)
end
endKey features:
- Helper methods:
call_mcp_tool,call_mcp_prompt,call_mcp_resource - Class definition matchers:
be_valid_mcp_class(:tool) - Response matchers for text, image, audio, and structured content
- Prompt and resource-specific matchers
For complete matcher documentation and examples, see the Testing with RSpec wiki page.
After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rspec to run the tests.
Generate executables that you can use for testing:
# generates bin/dev for STDIO transport
bundle exec rake mcp:generate_stdio_server
# generates bin/dev-http for streamable HTTP transport
bundle exec rake mcp:generate_streamable_http_serverIf you need to test with HTTPS (e.g., for clients that require SSL), generate self-signed certificates:
# Create SSL directory and generate certificates
mkdir -p tmp/ssl
openssl req -x509 -newkey rsa:4096 -keyout tmp/ssl/server.key -out tmp/ssl/server.crt -days 365 -nodes -subj "/C=US/ST=Dev/L=Dev/O=Dev/CN=localhost"The HTTP server supports both HTTP and HTTPS:
# Run HTTP server (default)
bin/dev-http
# Run HTTPS server (requires SSL certificates in tmp/ssl/)
SSL=true bin/dev-httpYou can also run bin/console for an interactive prompt that will allow you to experiment. Execute command rp to reload the project.
To install this gem onto your local machine, run bundle exec rake install.
To release a new version, update the version number in version.rb, and submit a PR. After the PR has been merged to main, run bundle exec rake release, which will:
- create a git tag for the version,
- push the created tag,
- and push the
.gemfile to rubygems.org.
Then, draft and publish release notes in Github.
Bug reports and pull requests are welcome on GitHub at https://github.com/dickdavis/model-context-protocol-rb.
The gem is available as open source under the terms of the MIT License.