model-context-protocol-rb
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.
Table of Contents
- Feature Support (Server)
- Quick Start with Rails
- Installation
- Building an MCP Server
- Server Configuration Options
- Pagination Configuration Options
- Transport Configuration Options
- Redis Configuration
- Server Logging Configuration
- Registry Configuration Options
- Prompts
- Resources
- Resource Templates
- Tools
- Completions
- Development
- Contributing
- License
Feature Support (Server)
Quick Start with Rails
The model-context-protocol-rb
works out of the box with any valid Rack request. Currently, this project has no plans for building a deeper Rails integration, but it is fairly simple to build it out yourself. To support modern application deployments across multiple servers, the streamable HTTP transport requires Redis as an external dependency.
Here's an example of how you can easily integrate with Rails.
First, configure Redis in an initializer:
# config/initializers/model_context_protocol.rb
require "model_context_protocol"
ModelContextProtocol::Server.configure_redis do |config|
config.redis_url = ENV.fetch("REDIS_URL", "redis://localhost:6379/0")
config.pool_size = 20
config.pool_timeout = 5
config.enable_reaper = true
config.reaper_interval = 60
config.idle_timeout = 300
end
Then, set the routes:
constraints format: :json do
get "/mcp", to: "model_context_protocol#handle", as: :mcp_get
post "/mcp", to: "model_context_protocol#handle", as: :mcp_post
delete "/mcp", to: "model_context_protocol#handle", as: :mcp_delete
end
Then, implement a controller endpoint to handle the requests.
class ModelContextProtocolController < ActionController::API
include ActionController::Live
before_action :authenticate_user
def handle
server = ModelContextProtocol::Server.new do |config|
config.name = "MyMCPServer"
config.title = "My MCP Server"
config.version = "1.0.0"
config.registry = build_registry
config.context = {
user_id: current_user.id,
request_id: request.id
}
config.transport = {
type: :streamable_http,
env: request.env
}
config.instructions = <<~INSTRUCTIONS
This server provides prompts, tools, and resources for interacting with my app.
Key capabilities:
- Does this one thing
- Does this other thing
- Oh, yeah, and it does that one thing, too
Use this server when you need to do stuff.
INSTRUCTIONS
end
result = server.start
handle_mcp_response(result)
end
private
def build_registry
ModelContextProtocol::Server::Registry.new do
tools do
# Implement user authorization logic to dynamically build registry
register TestTool if current_user.authorized_for?(TestTool)
end
end
end
def handle_mcp_response(result)
if result[:headers]&.dig("Content-Type") == "text/event-stream"
setup_streaming_headers
stream_response(result[:stream_proc])
else
render_json_response(result)
end
end
def setup_streaming_headers
response.headers.merge!(
"Content-Type" => "text/event-stream",
"Cache-Control" => "no-cache",
"Connection" => "keep-alive"
)
end
def stream_response(stream_proc)
stream_proc&.call(response.stream)
ensure
response.stream.close rescue nil
end
def render_json_response(result)
render json: result[:json],
status: result[:status] || 200,
headers: result[:headers] || {}
end
end
Read more about the server configuration options to better understand how you can customize your MCP server.
From here, you can get started building prompts, resources, resource templates, and tools.
Installation
Add this line to your application's Gemfile:
gem 'model-context-protocol-rb'
And then execute:
bundle
Or install it yourself as:
gem install model-context-protocol-rb
Building an MCP Server
Build a simple MCP server by registering your prompts, resources, resource templates, and tools. Then, configure and run the server. Messages from the MCP client will be routed to the appropriate custom handler. This SDK provides several classes that should be used to build your handlers.
server = ModelContextProtocol::Server.new do |config|
# Name of the MCP server (intended for programmatic use)
config.name = "MCPDevelopmentServer"
# Version of the MCP server
config.version = "1.0.0"
# Optional: human-readable display name for the MCP server
config.title = "My Awesome Server"
# Optional: instuctions for how the MCP server should be used by LLMs
config.instructions = <<~INSTRUCTIONS
This server provides file system access and development tools.
Key capabilities:
- Read and write files in the project directory
- Execute shell commands for development tasks
- Analyze code structure and dependencies
Use this server when you need to interact with the local development environment.
INSTRUCTIONS
# Configure pagination options for the following methods:
# prompts/list, resources/list, resource_template/list, tools/list
config.pagination = {
default_page_size: 50, # Default items per page
max_page_size: 500, # Maximum allowed page size
cursor_ttl: 1800 # Cursor expiry in seconds (30 minutes)
}
# Disable pagination support (enabled by default)
# config.pagination = false
# Optional: require specific environment variables to be set
config.require_environment_variable("API_KEY")
# Optional: set environment variables programmatically
config.set_environment_variable("DEBUG_MODE", "true")
# Optional: provide prompts, resources, and tools with contextual variables
config.context = {
user_id: "123456",
request_id: SecureRandom.uuid
}
# Optional: explicitly specify STDIO as the transport
# This is not necessary as STDIO is the default transport
# config.transport = { type: :stdio }
# Optional: configure streamable HTTP transport if required
# config.transport = {
# type: :streamable_http,
# env: request.env,
# session_ttl: 3600 # Optional: session timeout in seconds (default: 3600)
# }
# Register prompts, resources, resource templates, and tools
config.registry = ModelContextProtocol::Server::Registry.new do
prompts list_changed: true do
register TestPrompt
end
resources list_changed: true, subscribe: true do
register TestResource
end
resource_templates do
register TestResourceTemplate
end
tools list_changed: true do
register TestTool
end
end
end
# Start the MCP server
server.start
Server Configuration Options
The following table details all available configuration options for the MCP server:
Option | Type | Required | Default | Description |
---|---|---|---|---|
name |
String | Yes | - | Name of the MCP server for programmatic use |
version |
String | Yes | - | Version of the MCP server |
title |
String | No | - | Human-readable display name for the MCP server |
instructions |
String | No | - | Instructions for how the MCP server should be used by LLMs |
pagination |
Hash/Boolean | No | See pagination table | Pagination configuration (or false to disable) |
context |
Hash | No | {} |
Contextual variables available to prompts, resources, and tools |
transport |
Hash | No | { type: :stdio } |
Transport configuration |
registry |
Registry | Yes | - | Registry containing prompts, resources, and tools |
Pagination Configuration Options
When pagination
is set to a Hash, the following options are available:
Option | Type | Required | Default | Description |
---|---|---|---|---|
default_page_size |
Integer | No | 50 |
Default number of items per page |
max_page_size |
Integer | No | 500 |
Maximum allowed page size |
cursor_ttl |
Integer | No | 1800 |
Cursor expiry time in seconds (30 minutes) |
Note: Set config.pagination = false
to completely disable pagination support.
Transport Configuration Options
The transport configuration supports two types: :stdio
(default) and :streamable_http
.
STDIO Transport
config.transport = { type: :stdio } # This is the default, can be omitted
Streamable HTTP Transport
config.transport = { type: :streamable_http, env: request.env }
When using :streamable_http
transport, the following options are available:
Option | Type | Required | Default | Description |
---|---|---|---|---|
type |
Symbol | Yes | :stdio |
Must be :streamable_http for HTTP transport |
session_ttl |
Integer | No | 3600 |
Session timeout in seconds (1 hour) |
env |
Hash | No | - | Rack environment hash (for Rails integration) |
Redis Configuration
The :streamable_http
transport requires Redis to be configured globally before use:
ModelContextProtocol::Server.configure_redis do |config|
config.redis_url = ENV.fetch('REDIS_URL')
config.pool_size = 20
config.pool_timeout = 5
config.enable_reaper = true
config.reaper_interval = 60
config.idle_timeout = 300
end
Option | Type | Required | Default | Description |
---|---|---|---|---|
redis_url |
String | Yes | - | Redis connection URL |
pool_size |
Integer | No | 20 |
Connection pool size |
pool_timeout |
Integer | No | 5 |
Pool checkout timeout in seconds |
enable_reaper |
Boolean | No | true |
Enable connection reaping |
reaper_interval |
Integer | No | 60 |
Reaper check interval in seconds |
idle_timeout |
Integer | No | 300 |
Idle connection timeout in seconds |
Server Logging Configuration
Server logging can be configured globally to customize how your MCP server writes debug and operational logs. This logging is separate from client logging (which sends messages to MCP clients via the protocol) and is used for server-side debugging, monitoring, and troubleshooting:
ModelContextProtocol::Server.configure_server_logging do |config|
config.logdev = $stderr # or a file path like '/var/log/mcp-server.log'
config.level = Logger::INFO # Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, Logger::FATAL
config.progname = "MyMCPServer" # Program name for log entries
config.formatter = proc do |severity, datetime, progname, msg|
"#{datetime.strftime('%Y-%m-%d %H:%M:%S')} #{severity} [#{progname}] #{msg}\n"
end
end
Option | Type | Required | Default | Description |
---|---|---|---|---|
logdev |
IO/String | No | $stderr |
Log destination (IO object or file path) |
level |
Integer | No | Logger::INFO |
Minimum log level to output |
progname |
String | No | "MCP-Server" |
Program name for log entries |
formatter |
Proc | No | Default timestamp format | Custom log formatter |
Note: When using :stdio
transport, server logging must not use $stdout
as it conflicts with the MCP protocol communication. Use $stderr
or a file instead.
Registry Configuration Options
The registry is configured using ModelContextProtocol::Server::Registry.new
and supports the following block types:
Block Type | Options | Description |
---|---|---|
prompts |
list_changed: Boolean |
Register prompt handlers with optional list change notifications |
resources |
list_changed: Boolean , subscribe: Boolean
|
Register resource handlers with optional list change notifications and subscriptions |
resource_templates |
- | Register resource template handlers |
tools |
list_changed: Boolean |
Register tool handlers with optional list change notifications |
Within each block, use register ClassName
to register your handlers.
Example:
config.registry = ModelContextProtocol::Server::Registry.new do
prompts list_changed: true do
register MyPrompt
register AnotherPrompt
end
resources list_changed: true, subscribe: true do
register MyResource
end
tools do
register MyTool
end
end
Prompts
The ModelContextProtocol::Server::Prompt
base class allows subclasses to define a prompt that the MCP client can use.
Define the prompt properties and then implement the call
method to build your prompt. Any arguments passed to the prompt from the MCP client will be available in the arguments
hash with symbol keys (e.g., arguments[:argument_name]
), and any context values provided in the server configuration will be available in the context
hash. Use the respond_with
instance method to ensure your prompt responds with appropriately formatted response data.
You can also send MCP log messages to clients from within your prompt by calling a valid logger level method on the client_logger
and passing a string message. For server-side debugging and monitoring, use the server_logger
to write logs that are not sent to clients.
Prompt Definition
Use the define
block to set prompt properties and configure arguments.
Property | Description |
---|---|
name |
The programmatic name of the prompt |
title |
Human-readable display name |
description |
Short description of what the prompt does |
argument |
Define an argument block with name, description, required flag, and completion |
Argument Definition
Define any arguments using argument
blocks nested within the define
block. You can mark an argument as required, and you can optionally provide a completion class. See Completions for more information.
Property | Description |
---|---|
name |
The name of the argument |
description |
A short description of the argument |
required |
Whether the argument is required (boolean) |
completion |
Available hints for completions (array or completion class) |
Prompt Methods
Define your prompt properties and arguments, implement the call
method using the message_history
DSL to build prompt messages and respond_with
to serialize them. You can wrap long running operations in a cancellable
block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a progressable
block.
Method | Context | Description |
---|---|---|
define |
Class definition | Block for defining prompt metadata and arguments |
call |
Instance method | Main method to implement prompt logic and build response |
cancellable |
Within call
|
Wrap long-running operations to allow client cancellation (e.g., cancellable { slow_operation } ) |
progressable |
Within call
|
Wrap long-running operations to send clients progress notifications (e.g., progressable { slow_operation } ) |
message_history |
Within call
|
DSL method to build an array of user and assistant messages |
respond_with |
Within call
|
Return properly formatted response data (e.g., respond_with messages: ) |
Message History DSL
Build a message history using the an intuitive DSL, creating an ordered history of user and assistant messages with flexible content blocks that can include text, image, audio, embedded resources, and resource links.
Method | Context | Description |
---|---|---|
user_message |
Within message_history
|
Create a message with user role |
assistant_message |
Within message_history
|
Create a message with assistant role |
Content Blocks
Use content blocks to properly format the content included in messages.
Method | Context | Description |
---|---|---|
text_content |
Within message blocks | Create text content block |
image_content |
Within message blocks | Create image content block (requires data: and mime_type: ) |
audio_content |
Within message blocks | Create audio content block (requires data: and mime_type: ) |
embedded_resource_content |
Within message blocks | Create embedded resource content block (requires resource: ) |
resource_link |
Within message blocks | Create resource link content block (requires name: and uri: ) |
Available Instance Variables
The arguments
passed from an MCP client are available, as well as the context
values passed in at server initialization.
Variable | Context | Description |
---|---|---|
arguments |
Within call
|
Hash containing client-provided arguments (symbol keys) |
context |
Within call
|
Hash containing server configuration context values |
client_logger |
Within call
|
Client logger instance for sending MCP log messages (e.g., client_logger.info("message") ) |
server_logger |
Within call
|
Server logger instance for debugging and monitoring (e.g., server_logger.debug("message") ) |
Examples
This is an example prompt that returns a properly formatted response:
class TestPrompt < ModelContextProtocol::Server::Prompt
define do
# The name of the prompt for programmatic use
name "brainstorm_excuses"
# The human-readable prompt name for display in UI
title "Brainstorm Excuses"
# A short description of what the tool does
description "A prompt for brainstorming excuses to get out of something"
# Define arguments to be used with your prompt
argument do
# The name of the argument
name "tone"
# A short description of the argument
description "The general tone to be used in the generated excuses"
# If the argument is required
required false
# Available hints for completions
completion ["whiny", "angry", "callous", "desperate", "nervous", "sneaky"]
end
argument do
name "undesirable_activity"
description "The thing to get out of"
required true
end
end
# You can optionally define a custom completion for an argument and pass it to completions.
# ToneCompletion = ModelContextProtocol::Server::Completion.define do
# hints = ["whiny", "angry", "callous", "desperate", "nervous", "sneaky"]
# values = hints.grep(/#{argument_value}/)
# respond_with values:
# end
# ...
# define do
# argument do
# name "tone"
# description "The general tone to be used in the generated excuses"
# required false
# completion ToneCompletion
# end
# end
# The call method is invoked by the MCP Server to generate a response to resource/read requests
def call
# You can use the client_logger
client_logger.info("Brainstorming excuses...")
# Server logging for debugging and monitoring (not sent to client)
server_logger.debug("Prompt called with arguments: #{arguments}")
server_logger.info("Generating excuse brainstorming prompt")
# Build an array of user and assistant messages
messages = message_history do
# Create a message with the user role
user_message do
# Use any type of content block in a message (text, image, audio, embedded_resource, or resource_link)
text_content(text: "My wife wants me to: #{arguments[:undesirable_activity]}... Can you believe it?")
end
# You can also create messages with the assistant role
assistant_message do
text_content(text: "Oh, that's just downright awful. How can I help?")
end
user_message do
# Reference any inputs from the client by accessing the appropriate key in the arguments hash
text_content(text: "Can you generate some excuses for me?" + (arguments[:tone] ? " Make them as #{arguments[:tone]} as possible." : ""))
end
end
# Respond with the messages
respond_with messages:
end
end
Resources
The ModelContextProtocol::Server::Resource
base class allows subclasses to define a resource that the MCP client can use.
Define the resource properties and optionally annotations, then implement the call
method to build your resource. Use the respond_with
instance method to ensure your resource responds with appropriately formatted response data.
You can also send MCP log messages to clients from within your resource by calling a valid logger level method on the client_logger
and passing a string message. For server-side debugging and monitoring, use the server_logger
to write logs that are not sent to clients.
Resource Definition
Use the define
block to set resource properties and configure annotations.
Property | Description |
---|---|
name |
The name of the resource |
title |
Human-readable display name |
description |
Short description of what the resource contains |
mime_type |
MIME type of the resource content |
uri |
URI identifier for the resource |
annotations |
Block for defining resource annotations |
Annotation Definition
Define any resource annotations using an annotations
block nested within the define
block.
Property | Description |
---|---|
audience |
Target audience for the resource (array of symbols like [:user, :assistant] ) |
priority |
Priority level (numeric value, e.g., 0.9 ) |
last_modified |
Last modified timestamp (ISO 8601 string) |
Resource Methods
Define your resource properties and annotations, implement the call
method to build resource content and respond_with
to serialize the response. You can wrap long running operations in a cancellable
block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a progressable
block.
Method | Context | Description |
---|---|---|
define |
Class definition | Block for defining resource metadata and annotations |
call |
Instance method | Main method to implement resource logic and build response |
cancellable |
Within call
|
Wrap long-running operations to allow client cancellation (e.g., cancellable { slow_operation } ) |
progressable |
Within call
|
Wrap long-running operations to send clients progress notifications (e.g., progressable { slow_operation } ) |
respond_with |
Within call
|
Return properly formatted response data (e.g., respond_with text: or respond_with binary: ) |
Available Instance Variables
Resources have access to their configured properties and server context.
Variable | Context | Description |
---|---|---|
mime_type |
Within call
|
The configured MIME type for this resource |
uri |
Within call
|
The configured URI identifier for this resource |
client_logger |
Within call
|
Client logger instance for sending MCP log messages (e.g., client_logger.info("message") ) |
server_logger |
Within call
|
Server logger instance for debugging and monitoring (e.g., server_logger.debug("message") ) |
context |
Within call
|
Hash containing server configuration context values |
Examples
This is an example resource that returns a text response:
class TestResource < ModelContextProtocol::Server::Resource
define do
name "top-secret-plans.txt"
title "Top Secret Plans"
description "Top secret plans to do top secret things"
mime_type "text/plain"
uri "file:///top-secret-plans.txt"
end
def call
respond_with text: "I'm finna eat all my wife's leftovers."
end
end
This is an example resource with annotations:
class TestAnnotatedResource < ModelContextProtocol::Server::Resource
define do
name "annotated-document.md"
description "A document with annotations showing priority and audience"
mime_type "text/markdown"
uri "file:///docs/annotated-document.md"
annotations do
audience [:user, :assistant]
priority 0.9
last_modified "2025-01-12T15:00:58Z"
end
end
def call
respond_with text: "# Annotated Document\n\nThis document has annotations."
end
end
This is an example resource that returns binary data:
class TestBinaryResource < ModelContextProtocol::Server::Resource
define do
name "project-logo.png"
description "The logo for the project"
mime_type "image/png"
uri "file:///project-logo.png"
end
def call
# In a real implementation, we would retrieve the binary resource
# This is a small valid base64 encoded string (represents "test")
data = "dGVzdA=="
respond_with binary: data
end
end
Resource Templates
The ModelContextProtocol::Server::ResourceTemplate
base class allows subclasses to define a resource template that the MCP client can use.
Define the resource template properties and URI template with optional parameter completions. Resource templates are used to define parameterized resources that clients can instantiate.
Resource Template Definition
Use the define
block to set resource template properties.
Property | Description |
---|---|
name |
The name of the resource template |
description |
Short description of what the template provides |
mime_type |
MIME type of resources created from this template |
uri_template |
URI template with parameters (e.g., "file:///{name}" ) |
URI Template Configuration
Define the URI template and configure parameter completions within the uri_template
block.
Method | Context | Description |
---|---|---|
completion |
Within uri_template block |
Define completion for a URI parameter (e.g., completion :name, ["value1", "value2"] ) |
Resource Template Methods
Resource templates only use the define
method to configure their properties - they don't have a call
method.
Method | Context | Description |
---|---|---|
define |
Class definition | Block for defining resource template metadata and URI template |
Examples
This is an example resource template that provides a completion for a parameter of the URI template:
class TestResourceTemplate < ModelContextProtocol::Server::ResourceTemplate
define do
name "project-document-resource-template"
description "A resource template for retrieving project documents"
mime_type "text/plain"
uri_template "file:///{name}" do
completion :name, ["top-secret-plans.txt"]
end
end
# You can optionally define a custom completion for an argument and pass it to completions.
# Completion = ModelContextProtocol::Server::Completion.define do
# hints = {
# "name" => ["top-secret-plans.txt"]
# }
# values = hints[argument_name].grep(/#{argument_value}/)
# respond_with values:
# end
# define do
# name "project-document-resource-template"
# description "A resource template for retrieving project documents"
# mime_type "text/plain"
# uri_template "file:///{name}" do
# completion :name, Completion
# end
# end
end
Tools
The ModelContextProtocol::Server::Tool
base class allows subclasses to define a tool that the MCP client can use.
Define the tool properties and schemas, then implement the call
method to build your tool. Any arguments passed to the tool from the MCP client will be available in the arguments
hash with symbol keys (e.g., arguments[:argument_name]
), and any context values provided in the server configuration will be available in the context
hash. Use the respond_with
instance method to ensure your prompt responds with appropriately formatted response data.
You can also send MCP log messages to clients from within your tool by calling a valid logger level method on the client_logger
and passing a string message. For server-side debugging and monitoring, use the server_logger
to write logs that are not sent to clients.
Tool Definition
Use the define
block to set tool properties and configure schemas.
Property | Description |
---|---|
name |
The programmatic name of the tool |
title |
Human-readable display name |
description |
Short description of what the tool does |
input_schema |
JSON schema block for validating tool inputs |
output_schema |
JSON schema block for validating structured content outputs |
Tool Methods
Define your tool properties and schemas, implement the call
method using content helpers and respond_with
to serialize responses. You can wrap long running operations in a cancellable
block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a progressable
block.
Method | Context | Description |
---|---|---|
define |
Class definition | Block for defining tool metadata and schemas |
call |
Instance method | Main method to implement tool logic and build response |
cancellable |
Within call
|
Wrap long-running operations to allow client cancellation (e.g., cancellable { slow_operation } ) |
progressable |
Within call
|
Wrap long-running operations to send clients progress notifications (e.g., progressable { slow_operation } ) |
respond_with |
Within call
|
Return properly formatted response data with various content types |
Content Blocks
Use content blocks to properly format the content included in tool responses.
Method | Context | Description |
---|---|---|
text_content |
Within call
|
Create text content block |
image_content |
Within call
|
Create image content block (requires data: and mime_type: ) |
audio_content |
Within call
|
Create audio content block (requires data: and mime_type: ) |
embedded_resource_content |
Within call
|
Create embedded resource content block (requires resource: ) |
resource_link |
Within call
|
Create resource link content block (requires name: and uri: ) |
Response Types
Tools can return different types of responses using respond_with
.
Response Type | Usage | Description |
---|---|---|
structured_content: |
respond_with structured_content: data |
Return structured data validated against output schema |
content: |
respond_with content: content_block |
Return single content block |
content: |
respond_with content: [content_blocks] |
Return array of mixed content blocks |
error: |
respond_with error: "message" |
Return tool error response |
Available Instance Variables
Arguments from MCP clients and server context are available, along with logging capabilities.
Variable | Context | Description |
---|---|---|
arguments |
Within call
|
Hash containing client-provided arguments (symbol keys) |
context |
Within call
|
Hash containing server configuration context values |
client_logger |
Within call
|
Client logger instance for sending MCP log messages (e.g., client_logger.info("message") ) |
server_logger |
Within call
|
Server logger instance for debugging and monitoring (e.g., server_logger.debug("message") ) |
Examples
This is an example of a tool that returns structured content validated by an output schema:
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
end
This is an example tool that returns a text response:
class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
define do
name "double"
title "Number Doubler"
description "Doubles the provided number"
input_schema do
{
type: "object",
properties: {
number: {
type: "string"
}
},
required: ["number"]
}
end
end
def call
client_logger.info("Silly user doesn't know how to double a number")
number = arguments[:number].to_i
calculation = number * 2
user_id = context[:user_id]
salutation = user_id ? "User #{user_id}, " : ""
text_content = text_content(text: salutation << "#{number} doubled is #{calculation}")
respond_with content: text_content
end
end
This is an example of a tool that returns an image:
class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
define do
name "custom-chart-generator"
description "Generates a chart in various formats"
input_schema do
{
type: "object",
properties: {
chart_type: {
type: "string",
description: "Type of chart (pie, bar, line)"
},
format: {
type: "string",
description: "Image format (jpg, svg, etc)"
}
},
required: ["chart_type", "format"]
}
end
end
def call
# Map format to mime type
mime_type = case arguments[:format].downcase
when "svg"
"image/svg+xml"
when "jpg", "jpeg"
"image/jpeg"
else
"image/png"
end
# In a real implementation, we would generate an actual chart
# This is a small valid base64 encoded string (represents "test")
data = "dGVzdA=="
image_content = image_content(data:, mime_type:)
respond_with content: image_content
end
end
This is an example of a tool that returns an embedded resource response:
class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
define do
name "resource-finder"
description "Finds a resource given a name"
input_schema do
{
type: "object",
properties: {
name: {
type: "string",
description: "The name of the resource"
}
},
required: ["name"]
}
end
end
RESOURCE_MAPPINGS = {
test_annotated_resource: TestAnnotatedResource,
test_binary_resource: TestBinaryResource,
test_resource: TestResource
}.freeze
def call
name = arguments[:name]
resource_klass = RESOURCE_MAPPINGS[name.downcase.to_sym]
unless resource_klass
return respond_with :error, text: "Resource `#{name}` not found"
end
resource_data = resource_klass.call(client_logger, context)
respond_with content: embedded_resource_content(resource: resource_data)
end
end
This is an example of a tool that returns mixed content:
class TestToolWithMixedContentResponse < ModelContextProtocol::Server::Tool
define do
name "get_temperature_history"
description "Gets comprehensive temperature history for a zip code"
input_schema do
{
type: "object",
properties: {
zip: {
type: "string"
}
},
required: ["zip"]
}
end
end
def call
client_logger.info("Getting comprehensive temperature history data")
zip = arguments[:zip]
temperature_history = retrieve_temperature_history(zip:)
temperature_history_block = text_content(text: temperature_history.join(", "))
temperature_chart = generate_weather_history_chart(temperature_history)
temperature_chart_block = image_content(
data: temperature_chart[:base64_chart_data],
mime_type: temperature_chart[:mime_type]
)
respond_with content: [temperature_history_block, temperature_chart_block]
end
private
def retrieve_temperature_history(zip:)
# Simulates a call to an API or DB to retrieve weather history
[85.2, 87.4, 89.0, 95.3, 96.0]
end
def generate_weather_history_chart(history)
# SImulate a call to generate a chart given the weather history
{
base64_chart_data: "dGVzdA==",
mime_type: "image/png"
}
end
end
This is an example of a tool that returns a tool error response:
class TestToolWithToolErrorResponse < ModelContextProtocol::Server::Tool
define do
name "api-caller"
description "Makes calls to external APIs"
input_schema do
{
type: "object",
properties: {
api_endpoint: {
type: "string",
description: "API endpoint URL"
},
method: {
type: "string",
description: "HTTP method (GET, POST, etc)"
}
},
required: ["api_endpoint", "method"]
}
end
end
def call
# Simulate an API call failure
respond_with error: "Failed to call API at #{arguments[:api_endpoint]}: Connection timed out"
end
end
This is an example of a tool that allows a client to cancel a long-running operation:
class TestToolWithCancellableSleep < ModelContextProtocol::Server::Tool
define do
name "cancellable_sleep"
title "Cancellable Sleep Tool"
description "Sleep for 3 seconds with cancellation support"
input_schema do
{
type: "object",
properties: {},
additionalProperties: false
}
end
end
def call
client_logger.info("Starting 3 second sleep operation")
result = cancellable do
sleep 3
"Sleep completed successfully"
end
respond_with content: text_content(text: result)
end
end
This is an example of a tool that automatically sends progress notifications to the client and allows the client to cancel the operation:
class TestToolWithProgressableAndCancellable < ModelContextProtocol::Server::Tool
define do
name "test_tool_with_progressable_and_cancellable"
description "A test tool that demonstrates combined progressable and cancellable functionality"
input_schema do
{
type: "object",
properties: {
max_duration: {
type: "number",
description: "Expected maximum duration in seconds"
},
work_steps: {
type: "number",
description: "Number of work steps to perform"
}
},
required: ["max_duration"]
}
end
end
def call
max_duration = arguments[:max_duration] || 10
work_steps = arguments[:work_steps] || 10
client_logger.info("Starting progressable call with max_duration=#{max_duration}, work_steps=#{work_steps}")
result = progressable(max_duration:, message: "Processing #{work_steps} items") do
cancellable do
processed_items = []
work_steps.times do |i|
sleep(max_duration / work_steps.to_f)
processed_items << "item_#{i + 1}"
end
processed_items
end
end
response = text_content(text: "Successfully processed #{result.length} items: #{result.join(", ")}")
respond_with content: response
end
end
Completions
The ModelContextProtocol::Server::Completion
base class allows subclasses to define a completion that the MCP client can use to obtain hints or suggestions for arguments to prompts and resources.
Implement the call
method to build your completion logic using the provided argument name and value. Completions are simpler than other server features - they don't use a define
block and only provide filtered suggestion lists.
Completion Methods
Completions only implement the call
method to provide completion logic.
Method | Context | Description |
---|---|---|
call |
Instance method | Main method to implement completion logic and build response |
respond_with |
Within call
|
Return properly formatted completion response (e.g., respond_with values: ) |
Available Instance Variables
Completions receive the argument name and current value being completed.
Variable | Context | Description |
---|---|---|
argument_name |
Within call
|
String name of the argument being completed |
argument_value |
Within call
|
Current partial value being typed by the user |
Examples
This is an example completion that returns an array of values in the response:
class TestCompletion < ModelContextProtocol::Server::Completion
def call
hints = {
"message" => ["hello", "world", "foo", "bar"]
}
values = hints[argument_name].grep(/#{argument_value}/)
respond_with values:
end
end
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run bundle exec rspec
to run the tests.
Generate Development Servers
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_server
If 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-http
You 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
.
Releases
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
.gem
file to rubygems.org.
Then, draft and publish release notes in Github.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/dickdavis/model-context-protocol-rb.
License
The gem is available as open source under the terms of the MIT License.