ruby-sdk/docs/_server/tools.md
Koichi ITO 88831b46f6 Move the Documentation From README.md to the Documentation Site
Move the documentation that had grown to 3000+ lines in README.md onto https://ruby.sdk.modelcontextprotocol.io
and slim the README down to a quick start and feature overview, following the Python SDK layout:

- Add 21 server pages as the docs/_server/ collection (overview plus transports, discovery, tools, prompts, resources,
  roots, sampling, elicitation, multi round-trip results, notifications, notification subscriptions, cancellation, progress,
  ping, completions, logging, pagination, server context, configuration, and custom methods), 8 client pages as
  the docs/_client/ collection (overview, transports, lifecycle, multi round-trip results, cancellation, ping, pagination,
  and authorization), and 3 extension pages as the docs/_extensions/ collection (overview, capability extensions, and MCP Apps,
  following the Extensions Overview recommendation that SDK documentation list the supported extensions) -
  one page per topic, ordered to match the sidebar of the 2026-07-28 specification, with the client-side APIs
  (pinging, cancelling, and paginating from MCP::Client) documented under Building Clients
- Add top-level Examples and Protocol Versions pages after Installation, linking the runnable examples in examples/
  and summarizing the supported protocol versions, the era model, and client negotiation
- Render the three sections as just-the-docs collections, which list every page beneath a plain category heading in
  the sidebar with no folding, and serve every page at an extensionless URL under /server/, /client/, and /extensions/,
  matching the sibling SDK documentation sites
- Replace docs/building-servers.md and docs/building-clients.md with redirects to the new section overview pages via
  jekyll-redirect-from, redirect the previously published /installation.html to its extensionless URL, fold their content
  that was missing from README.md into the new pages, and update the Tool argument keys reference comment in lib/mcp/server.rb to
  the relocated Tools page
- Reduce README.md to the badges, a compact feature overview in the Python SDK style, installation instructions, a stdio
  server and client quick start, and a License section, using absolute URLs for the remaining repository links
  so they resolve on rubygems.org
- Adapt formatting where GitHub rendering habits break on the site: convert the numbered "three ways to define" lists into headings,
  since kramdown restarts numbering at 1 when code blocks split list items, convert GitHub-style alerts into just-the-docs callouts
  with the SEP-2260 server-to-client association note raised to a red warning, merge the duplicated Exception Reporting
  and Configuration Block Data sections into the Configuration page, and relocate the Streamable HTTP settings that were nested under
  the Logging section into the Transports page
- Refresh the migrated content against the current implementation: correct stale claims and broken samples the README carried,
  note on each session-era feature how it relates to the modern lifecycle of MCP 2026-07-28, and point spec links at the latest revision,
  keeping deliberate 2025-11-25 pins for pages the modern revision removed
- Style the site after the Rails API documentation palette (red links and accents on neutral surfaces) with matching
  light and dark color schemes, center the sidebar and content as one block, add a Previous/Next footer pager following
  the sidebar order, open external links in a new tab, and serve the MCP logo as the favicon

Every code block and heading from the previous README was verified to have a home in the new docs pages or the slimmed README before
the reduction; a few samples were corrected rather than copied, as noted above.
2026-08-21 13:24:04 +09:00

419 lines
13 KiB
Markdown

---
layout: default
title: Tools
nav_order: 4
---
# Tools
MCP spec includes [Tools](https://modelcontextprotocol.io/specification/latest/server/tools) which provide functionality to LLM apps.
## Defining Tools
This gem provides a `MCP::Tool` class that can be used to create tools in three ways.
### 1. As a class definition
Subclass `MCP::Tool` and declare the metadata with class-level helpers; `call` implements the tool:
```ruby
class MyTool < MCP::Tool
title "My Tool"
description "This tool performs specific functionality..."
input_schema(
properties: {
message: { type: "string" },
},
required: ["message"]
)
output_schema(
properties: {
result: { type: "string" },
success: { type: "boolean" },
timestamp: { type: "string", format: "date-time" }
},
required: ["result", "success", "timestamp"]
)
annotations(
read_only_hint: true,
destructive_hint: false,
idempotent_hint: true,
open_world_hint: false,
title: "My Tool"
)
def self.call(message:, server_context:)
MCP::Tool::Response.new([{ type: "text", text: "OK" }])
end
end
tool = MyTool
```
### 2. Using the `MCP::Tool.define` method
`MCP::Tool.define` builds a tool from keyword arguments, with the block as its implementation:
```ruby
tool = MCP::Tool.define(
name: "my_tool",
title: "My Tool",
description: "This tool performs specific functionality...",
annotations: {
read_only_hint: true,
title: "My Tool"
}
) do |args, server_context:|
MCP::Tool::Response.new([{ type: "text", text: "OK" }])
end
```
### 3. Using the `MCP::Server#define_tool` method
`MCP::Server#define_tool` registers the tool directly on a server instance:
```ruby
server = MCP::Server.new
server.define_tool(
name: "my_tool",
description: "This tool performs specific functionality...",
annotations: {
title: "My Tool",
read_only_hint: true
}
) do |args, server_context:|
Tool::Response.new([{ type: "text", text: "OK" }])
end
```
The [`server_context`](/server/server-context/) parameter is the `server_context` passed into the server and can be used to pass per request information,
e.g. around authentication state.
## Tool argument keys
Tool arguments are delivered as a `Hash` whose keys are Ruby symbols at every nesting level, including nested objects
and objects inside arrays. The transports parse incoming JSON with `JSON.parse(..., symbolize_names: true)`,
so by the time a tool runs, a wire payload such as `{"payload": {"subject": "greet"}}` arrives as `{ payload: { subject: "greet" } }`.
This means top-level values are bound through keyword arguments (`def call(message:, payload: nil, server_context:)`),
and nested objects must be read with symbol keys:
```ruby
class ExampleTool < MCP::Tool
description "Echoes a nested argument"
input_schema(
properties: {
message: { type: "string" },
payload: {
type: "object",
properties: {
subject: { type: "string" },
}
}
},
required: ["message"]
)
def self.call(message:, payload: nil, server_context:)
subject = payload && payload[:subject] # symbol key, not payload["subject"]
MCP::Tool::Response.new([{
type: "text",
text: "Message: #{message}; subject: #{subject}"
}])
end
end
```
Reading a nested value with a string key (`payload["subject"]`) returns `nil`. This is a Ruby-specific contract:
Top-level keyword arguments require symbol keys, and parsing JSON with `symbolize_names: true` symbolizes nested objects too.
Calling a tool directly in a test with `MyTool.call(payload: { "subject" => "greet" }, server_context: nil)` passes string keys
that a transport never delivers, so string-key access can pass tests yet fail against a real client.
Exercise a tool under the delivered shape by round-tripping the arguments through JSON the same way a transport does:
```ruby
delivered = JSON.parse(JSON.generate(arguments), symbolize_names: true)
MyTool.call(**delivered, server_context: nil)
```
## Tool Annotations
Tools can include annotations that provide additional metadata about their behavior. The following annotations are supported:
- `destructive_hint`: Indicates if the tool performs destructive operations. Defaults to true
- `idempotent_hint`: Indicates if the tool's operations are idempotent. Defaults to false
- `open_world_hint`: Indicates if the tool operates in an open world context. Defaults to true
- `read_only_hint`: Indicates if the tool only reads data (doesn't modify state). Defaults to false
- `title`: A human-readable title for the tool
Annotations can be set either through the class definition using the `annotations` class method or when defining a tool using the `define` method.
{: .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`
Declare the schema with the `output_schema` class helper, alongside `input_schema`:
```ruby
class WeatherTool < MCP::Tool
tool_name "get_weather"
description "Get current weather for a location"
input_schema(
properties: {
location: { type: "string" },
units: { type: "string", enum: ["celsius", "fahrenheit"] }
},
required: ["location"]
)
output_schema(
properties: {
temperature: { type: "number" },
condition: { type: "string" },
humidity: { type: "integer" }
},
required: ["temperature", "condition", "humidity"]
)
def self.call(location:, units: "celsius", server_context:)
# Call weather API and structure the response
api_response = WeatherAPI.fetch(location, units)
weather_data = {
temperature: api_response.temp,
condition: api_response.description,
humidity: api_response.humidity_percent
}
output_schema.validate_result(weather_data)
MCP::Tool::Response.new([{
type: "text",
text: weather_data.to_json
}])
end
end
```
### 2. Using `Tool.define` with `output_schema`
Pass the schema as the `output_schema:` keyword argument:
```ruby
tool = MCP::Tool.define(
name: "calculate_stats",
description: "Calculate statistics for a dataset",
input_schema: {
properties: {
numbers: { type: "array", items: { type: "number" } }
},
required: ["numbers"]
},
output_schema: {
properties: {
mean: { type: "number" },
median: { type: "number" },
count: { type: "integer" }
},
required: ["mean", "median", "count"]
}
) do |args, server_context:|
# Calculate statistics and validate against schema
MCP::Tool::Response.new([{ type: "text", text: "Statistics calculated" }])
end
```
### 3. Using `OutputSchema` objects
Construct an `MCP::Tool::OutputSchema` object explicitly:
```ruby
class DataTool < MCP::Tool
output_schema MCP::Tool::OutputSchema.new(
properties: {
success: { type: "boolean" },
data: { type: "object" }
},
required: ["success"]
)
end
```
Output schema may also describe an array of objects:
```ruby
class WeatherTool < MCP::Tool
output_schema(
type: "array",
items: {
properties: {
temperature: { type: "number" },
condition: { type: "string" },
humidity: { type: "integer" }
},
required: ["temperature", "condition", "humidity"]
}
)
end
```
Please note: in this case, you must provide `type: "array"`. The default type for output schemas is `object`,
applied only when the schema declares no root keyword (`type`, `$ref`, `oneOf`, `anyOf`, `allOf`, `not`, `if`, `const`, `enum`).
Per SEP-2106, an output schema may be any valid JSON Schema 2020-12 document, including a primitive root
(`{ type: "string" }`) or a root-level composition:
```ruby
class FlexibleTool < MCP::Tool
output_schema(
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "number" } }
]
)
end
```
Input schemas keep `type: "object"` at the root but accept the full 2020-12 vocabulary below it
(`$defs`/`$ref`, `oneOf`/`anyOf`/`allOf`/`not`, `if`/`then`/`else`). Two resource bounds apply to
all tool schemas: only same-document `$ref`s (starting with `#`) are accepted, and documents are
capped at `MCP::Tool::Schema::MAX_SCHEMA_DEPTH` nesting levels and `MCP::Tool::Schema::MAX_SUBSCHEMA_COUNT` subschema objects;
violations raise `ArgumentError` at construction time.
MCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/latest/server/tools#output-schema) specifies that:
- **Server Validation**: Servers MUST provide structured results that conform to the output schema
- **Client Validation**: Clients SHOULD validate structured results against the output schema
- **Better Integration**: Enables strict schema validation, type information, and improved developer experience
- **Backward Compatibility**: Tools returning structured content SHOULD also include serialized JSON in a TextContent block
The output schema follows standard JSON Schema format and helps ensure consistent data exchange between MCP servers and clients.
By default, server-side validation of tool results against `output_schema` is disabled for backwards compatibility.
To validate successful tool responses, enable `validate_tool_call_results` on the server [configuration](/server/configuration/):
```ruby
configuration = MCP::Configuration.new(validate_tool_call_results: true)
server = MCP::Server.new(
name: "example_server",
tools: [WeatherTool],
configuration: configuration
)
```
When enabled, successful tool responses for tools with an `output_schema` must include `structured_content` that conforms to the schema.
Error responses are not validated against the output schema.
## Tool Responses with Structured Content
Tools can return structured data alongside text content using the `structured_content` parameter.
The structured content will be included in the JSON-RPC response as the `structuredContent` field.
Per SEP-2106, `structured_content` may be any JSON value, not only an object. When a tool returns a non-object value (e.g. an array)
without providing any content blocks, the server automatically mirrors it into `content` as serialized JSON text so older clients
that only read `content` still receive the data.
```ruby
class WeatherTool < MCP::Tool
description "Get current weather and return structured data"
def self.call(location:, units: "celsius", server_context:)
# Call weather API and structure the response
api_response = WeatherAPI.fetch(location, units)
weather_data = {
temperature: api_response.temp,
condition: api_response.description,
humidity: api_response.humidity_percent
}
output_schema.validate_result(weather_data)
MCP::Tool::Response.new(
[{
type: "text",
text: weather_data.to_json
}],
structured_content: weather_data
)
end
end
```
## Tool Responses with Errors
Tools can return error information alongside text content using the `error` parameter.
The error will be included in the JSON-RPC response as the `isError` field.
```ruby
class WeatherTool < MCP::Tool
description "Get current weather and return structured data"
def self.call(server_context:)
# Do something here
content = {}
MCP::Tool::Response.new(
[{
type: "text",
text: content.to_json
}],
structured_content: content,
error: true
)
end
end
```
## Tool Responses with Image, Audio, and Embedded Resources
Tool responses are not limited to text. The `MCP::Content` module provides `Image`, `Audio`, and `EmbeddedResource` content types,
which serialize to the `image`, `audio`, and `resource` content blocks defined by the MCP spec. Image and audio data is passed as
a base64-encoded string together with its MIME type:
```ruby
class ChartTool < MCP::Tool
description "Render a chart as a PNG image"
def self.call(server_context:)
MCP::Tool::Response.new([
MCP::Content::Text.new("Here is the rendered chart:").to_h,
MCP::Content::Image.new(Base64.strict_encode64(render_chart_png), "image/png").to_h,
])
end
end
class SpeechTool < MCP::Tool
description "Synthesize speech audio"
def self.call(server_context:)
MCP::Tool::Response.new([
MCP::Content::Audio.new(Base64.strict_encode64(synthesize_wav), "audio/wav").to_h,
])
end
end
```
An [embedded resource](/server/resources/) wraps `MCP::Resource::TextContents` or `MCP::Resource::BlobContents`, allowing a tool to return resource contents inline:
```ruby
class ReportTool < MCP::Tool
description "Return a report as an embedded resource"
def self.call(server_context:)
contents = MCP::Resource::TextContents.new(
uri: "report://monthly",
mime_type: "application/json",
text: { total: 42 }.to_json,
)
MCP::Tool::Response.new([MCP::Content::EmbeddedResource.new(contents).to_h])
end
end
```