Document non-text content types, binary resources, and elicitation schema options

## Motivation and Context

The SDK tiering system (SEP-1730) requires Tier 1 SDKs to document all non-experimental features
with examples. A documentation coverage audit against the canonical feature list found that the README.md
only showed `type: "text"` content everywhere, leaving several implemented features invisible to users:

- Tool responses with image, audio, and embedded resource content (`MCP::Content::Image` / `Audio` / `EmbeddedResource`)
- Prompt messages carrying image or embedded resource content
- Reading binary resources via `MCP::Resource::BlobContents`
- Reading resources through a registered resource template URI
- Elicitation `requested_schema` flat-object restrictions and client-side validation behavior
- Elicitation default values (SEP-1034) and enum values with `enumNames` (SEP-1330)

All of these are existing SDK capabilities; the conformance suite already exercises them.
This change documents each with prose and a runnable-style example, closing every documentation gap
that does not require new code. Legacy SSE transport remains undocumented because the SDK intentionally
does not implement it.

## How Has This Been Tested?

Documentation-only change. Every code example was written against the current public API in lib/
(`MCP::Content`, `MCP::Resource::TextContents` / `BlobContents`, `MCP::Prompt::Message`, `create_form_elicitation`)
and cross-checked with the working implementations in conformance/server.rb, which pass the conformance suite.

## Breaking Changes

None. This only adds sections to README.md.
This commit is contained in:
Koichi ITO 2026-07-07 23:20:10 +09:00
commit a1fd3d4283

177
README.md
View file

@ -872,6 +872,53 @@ class WeatherTool < MCP::Tool
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 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
```
### Prompts
MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
@ -1010,6 +1057,49 @@ The server will handle prompt listing and execution through the MCP protocol met
- `prompts/list` - Lists all registered prompts and their schemas
- `prompts/get` - Retrieves and executes a specific prompt with arguments
### Prompts with Image and Embedded Resource Content
Prompt messages are not limited to text. The same `MCP::Content` types used in tool responses can be used as message content,
letting a prompt template include images or inline resource contents. Unlike tool responses, the content object is passed directly rather than as a hash;
`MCP::Prompt::Message` serializes it when the prompt result is returned:
```ruby
class CodeReviewPrompt < MCP::Prompt
prompt_name "code_review"
description "Review a source file with an accompanying diagram"
arguments [
MCP::Prompt::Argument.new(name: "file_uri", description: "URI of the file to review", required: true),
]
class << self
def template(args, server_context:)
MCP::Prompt::Result.new(
messages: [
MCP::Prompt::Message.new(
role: "user",
content: MCP::Content::EmbeddedResource.new(
MCP::Resource::TextContents.new(
uri: args["file_uri"],
mime_type: "text/x-ruby",
text: read_source(args["file_uri"]),
),
),
),
MCP::Prompt::Message.new(
role: "user",
content: MCP::Content::Image.new(architecture_diagram_base64, "image/png"),
),
MCP::Prompt::Message.new(
role: "user",
content: MCP::Content::Text.new("Please review the code above, using the diagram for context."),
),
],
)
end
end
end
```
### Resources
MCP spec includes [Resources](https://modelcontextprotocol.io/specification/latest/server/resources).
@ -1126,6 +1216,34 @@ server.resources_read_handler do |params|
end
```
### Reading Binary Resources
For binary resources, respond with a base64-encoded `blob` field instead of `text`.
The `MCP::Resource::TextContents` and `MCP::Resource::BlobContents` classes build the two contents shapes defined by the spec:
```ruby
server.resources_read_handler do |params|
case params[:uri]
when "file:///logo.png"
[
MCP::Resource::BlobContents.new(
uri: params[:uri],
mime_type: "image/png",
data: Base64.strict_encode64(File.binread("logo.png")),
).to_h,
]
else
[
MCP::Resource::TextContents.new(
uri: params[:uri],
mime_type: "text/plain",
text: "Hello from example resource!",
).to_h,
]
end
end
```
### Resource Templates
Resource templates follow the same pattern. Class-based templates declare a `uri_template` and
@ -1201,6 +1319,31 @@ server = MCP::Server.new(
)
```
Registered templates are listed through the `resources/templates/list` protocol method.
To serve reads for URIs that match a template, extract the variable parts of the URI in your `resources_read_handler`:
```ruby
resource_template = MCP::ResourceTemplate.new(
uri_template: "file:///items/{item_id}",
name: "item",
mime_type: "application/json",
)
server = MCP::Server.new(name: "my_server", resource_templates: [resource_template])
server.resources_read_handler do |params|
if (match = params[:uri].match(%r{\Afile:///items/(?<item_id>[^/]+)\z}))
[{
uri: params[:uri],
mimeType: "application/json",
text: { id: match[:item_id] }.to_json,
}]
else
raise MCP::Server::ResourceNotFoundError.new(params[:uri], params)
end
end
```
### Roots
The Model Context Protocol allows servers to request filesystem roots from clients through the `roots/list` method.
@ -1793,6 +1936,40 @@ server.define_tool(name: "collect_contact", description: "Collect contact info")
end
```
The `requested_schema` must be a flat object schema: a top-level `type: "object"` whose `properties` are limited to
primitive types (`string`, `number`, `integer`, `boolean`). Nested objects and arrays are not allowed, which keeps
the schema simple enough for clients to render as a form. Per the MCP specification, the client validates
the user's input against this schema before returning it, so the `content` of an `accept` response matches the requested shape.
#### Default Values and Enums
Properties may declare a `default` value (SEP-1034), which clients use to pre-fill the form.
String properties may declare `enum` values, optionally with human-readable `enumNames` (SEP-1330), which clients render as a choice list:
```ruby
server.define_tool(name: "configure_deploy", description: "Configure a deployment") do |server_context:|
result = server_context.create_form_elicitation(
message: "Configure the deployment",
requested_schema: {
type: "object",
properties: {
replicas: { type: "integer", default: 3 },
verbose: { type: "boolean", default: false },
environment: {
type: "string",
enum: ["dev", "staging", "prod"],
enumNames: ["Development", "Staging", "Production"],
default: "dev",
},
},
required: ["environment"],
},
)
MCP::Tool::Response.new([{ type: "text", text: "Deploying to #{result[:content][:environment]}" }])
end
```
#### URL Mode
URL mode directs the user to an external URL for out-of-band interactions such as OAuth flows: