From mcp-rb
Build MCP tool handlers using the `model-context-protocol-rb` Ruby gem. Use when creating tools that perform actions and return results.
How this skill is triggered — by the user, by Claude, or both
Slash command
/mcp-rb:toolsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Tools perform actions and return results to MCP clients. Each tool is a class that inherits from `ModelContextProtocol::Server::Tool`, defines metadata in a `define` block, and implements a `call` method.
Tools perform actions and return results to MCP clients. Each tool is a class that inherits from ModelContextProtocol::Server::Tool, defines metadata in a define block, and implements a call method.
class WeatherTool < ModelContextProtocol::Server::Tool
define do
name "get_weather"
title "Weather Lookup"
description "Get current weather for a location"
input_schema do
{
type: "object",
properties: {
location: {
type: "string",
description: "City name or zip code"
}
},
required: ["location"]
}
end
end
def call
location = arguments[:location]
client_logger.info("Fetching weather for #{location}")
weather = fetch_weather(location)
respond_with content: text_content(text: "Temperature: #{weather[:temp]}")
end
private
def fetch_weather(location)
{ temp: 22.5 }
end
end
| Property | Required | Description |
|---|---|---|
name | Yes | Programmatic name (e.g., "calculate") |
title | No | Human-readable display name |
description | Yes | What the tool does |
input_schema | No | JSON Schema block for validating inputs |
output_schema | No | JSON Schema block for validating structured outputs |
annotations | No | Block defining tool behavior hints |
security_schemes | No | Block defining security requirements |
| Variable | Description |
|---|---|
arguments | Hash with symbol keys containing client inputs |
context | Hash with server configuration context |
client_logger | Logger for MCP client messages |
server_logger | Logger for server-side debugging |
def call
respond_with content: text_content(text: "Result text")
end
When using output_schema, return structured data validated against the schema:
define do
name "get_weather_data"
description "Get weather data"
input_schema do
{ type: "object", properties: { location: { type: "string" } }, required: ["location"] }
end
output_schema do
{
type: "object",
properties: {
temperature: { type: "number" },
conditions: { type: "string" }
},
required: ["temperature", "conditions"]
}
end
end
def call
respond_with structured_content: { temperature: 22.5, conditions: "sunny" }
end
def call
base64_data = Base64.strict_encode64(image_bytes)
respond_with content: image_content(data: base64_data, mime_type: "image/png")
end
def call
base64_data = Base64.strict_encode64(audio_bytes)
respond_with content: audio_content(data: base64_data, mime_type: "audio/mp3")
end
def call
resource_data = SomeResource.call(client_logger, server_logger, context)
respond_with content: embedded_resource_content(resource: resource_data)
end
def call
respond_with content: resource_link(name: "config.json", uri: "file:///config.json")
end
Combine multiple content types in a single response:
def call
text = text_content(text: "Analysis results:")
chart = image_content(data: chart_data, mime_type: "image/png")
link = resource_link(name: "Raw data", uri: "data://results.csv")
respond_with content: [text, chart, link]
end
def call
respond_with error: "Something went wrong"
end
Annotations provide hints about tool behavior to clients:
define do
name "delete_file"
description "Deletes a file"
annotations do
read_only_hint false
destructive_hint true
idempotent_hint true
open_world_hint false
end
input_schema do
{ type: "object", properties: { path: { type: "string" } }, required: ["path"] }
end
end
| Property | Type | Description |
|---|---|---|
read_only_hint | Boolean | Whether the tool only reads data (no side effects) |
destructive_hint | Boolean | Whether the tool may perform destructive operations |
idempotent_hint | Boolean | Whether calling multiple times has the same effect |
open_world_hint | Boolean | Whether the tool interacts with the broader environment |
define do
name "search"
description "Search data"
security_schemes do
[
{ type: "noauth" },
{ type: "oauth2", scopes: ["search.read"] }
]
end
end
Wrap long-running operations in a cancellable block:
def call
result = cancellable do
perform_long_operation
end
respond_with content: text_content(text: result)
end
Use progressable to send automatic progress updates:
def call
result = progressable(max_duration: 30, message: "Processing") do
cancellable do
perform_long_operation
end
end
respond_with content: text_content(text: result)
end
The order matters: progressable wraps cancellable.
| Parameter | Type | Required | Description |
|---|---|---|---|
max_duration | Number | Yes | Expected maximum duration in seconds |
message | String | No | Progress message shown to client |
Tools have a dedicated error response type. Use it for validation failures, external service errors, and permission issues:
def call
return respond_with error: "Missing required argument: id" unless arguments[:id]
begin
result = external_api_call
respond_with content: text_content(text: result)
rescue Timeout::Error
respond_with error: "External service timed out"
end
end
def call
# Client-facing messages (sent via MCP protocol)
client_logger.info("Processing your request...")
client_logger.warning("Rate limit approaching")
# Server-side debugging (not sent to clients)
server_logger.debug("Arguments: #{arguments.inspect}")
server_logger.error("External API failed: #{error.message}")
end
input_schema do
{
type: "object",
properties: {
name: { type: "string", description: "User name" },
count: { type: "integer", minimum: 1, maximum: 100 },
price: { type: "number", minimum: 0 },
active: { type: "boolean" },
status: { type: "string", enum: ["pending", "active", "completed"] },
tags: { type: "array", items: { type: "string" } }
},
required: ["name"]
}
end
input_schema do
{ type: "object", properties: {}, additionalProperties: false }
end
The gem provides RSpec helpers and matchers via require "model_context_protocol/rspec". See the configuration skill for setup instructions. The examples below assume ModelContextProtocol::RSpec.configure! has been called in your spec helper.
Use the call_mcp_tool helper instead of calling the class directly:
RSpec.describe WeatherTool, type: :mcp do
describe ".call" do
subject { call_mcp_tool(described_class, { location: "New York" }, { user_id: "123" }) }
it { is_expected.to be_valid_mcp_tool_response }
it "returns weather data" do
expect(subject).to have_text_content(/Temperature/)
end
end
end
subject { call_mcp_tool(described_class, { location: "New York" }) }
it "returns structured weather data" do
expect(subject).to have_structured_content(temperature: 22.5, conditions: "sunny")
end
it "returns an image" do
expect(subject).to have_image_content(mime_type: "image/png")
end
it "returns audio" do
expect(subject).to have_audio_content(mime_type: "audio/mp3")
end
it "returns an embedded resource" do
expect(subject).to have_embedded_resource_content(uri: "file:///config.json")
end
it "returns a resource link" do
expect(subject).to have_resource_link_content(name: "config.json", uri: "file:///config.json")
end
it "returns an error response" do
expect(subject).to be_mcp_error_response("timed out")
end
it "matches error with regex" do
expect(subject).to be_mcp_error_response(/timed out/)
end
it "is a valid MCP tool" do
expect(WeatherTool).to be_valid_mcp_class(:tool)
end
| Matcher | Description |
|---|---|
be_valid_mcp_tool_response | Response has valid tool response structure |
have_text_content(text) | Response contains matching text content (String or Regexp) |
have_structured_content(hash) | Response has matching structured content |
have_image_content(mime_type:) | Response contains image content with optional MIME type |
have_audio_content(mime_type:) | Response contains audio content with optional MIME type |
have_embedded_resource_content(uri:, mime_type:) | Response contains embedded resource |
have_resource_link_content(uri:, name:) | Response contains resource link |
be_mcp_error_response(message) | Response is an error with optional message match |
be_valid_mcp_class(:tool) | Class is a valid MCP tool with name and description |
Register tools in the server configuration:
config.registry do
tools do
register WeatherTool
register CalculatorTool
end
end
| Element | Convention | Example |
|---|---|---|
| Class name | PascalCase + Tool | WeatherTool |
| Handler name | snake_case | get_weather |
| Title | Title Case | Weather Lookup |
| File name | snake_case.rb | weather_tool.rb |
npx claudepluginhub dickdavis/personal-marketplace --plugin mcp-rbScaffolds new MCP tool definitions for a TypeScript MCP server project. Use when adding new server capabilities or implementing tool endpoints.
Builds Model Context Protocol (MCP) servers in Ruby using the mcp gem. Guides tools, prompts, resources, design principles like context preservation, domain vocabulary, tool budgeting, and security.
Guides building MCP servers in TypeScript from research to evaluation. Covers design principles, SDK usage, and hosting patterns.