Build MCP prompt handlers using the `model-context-protocol-rb` Ruby gem. Use when creating prompts that generate conversation message histories.
Prompts generate conversation message histories for MCP clients. Each prompt is a class that inherits from ModelContextProtocol::Server::Prompt, defines metadata in a define block, and implements a call method.
class BrainstormExcusesPrompt < ModelContextProtocol::Server::Prompt
define do
name "brainstorm_excuses"
title "Brainstorm Excuses"
description "A prompt for brainstorming excuses to get out of something"
argument do
name "tone"
description "The general tone to be used in the generated excuses"
required false
completion ["whiny", "angry", "callous", "desperate", "nervous", "sneaky"]
end
argument do
name "undesirable_activity"
description "The thing to get out of"
required true
end
end
def call
client_logger.info("Brainstorming excuses...")
messages = message_history do
user_message do
text_content(text: "My wife wants me to: #{arguments[:undesirable_activity]}... Can you believe it?")
end
assistant_message do
text_content(text: "Oh, that's just downright awful. How can I help?")
end
user_message do
tone_text = arguments[:tone] ? " Make them as #{arguments[:tone]} as possible." : ""
text_content(text: "Can you generate some excuses for me?#{tone_text}")
end
end
respond_with messages: messages
end
end
| Property | Required | Description |
|---|---|---|
name | Yes | Programmatic name (e.g., "code_review") |
title | No | Human-readable display name |
description | Yes | What this prompt does |
argument | No | Block defining an argument (can have multiple) |
| Property | Required | Description |
|---|---|---|
name | Yes | Argument identifier |
description | Yes | What the argument is for |
required | No | Whether the argument is required (default: false) |
completion | No | Array of hints or a Completion class |
| 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 |
Build message histories using message_history with user_message and assistant_message blocks:
def call
messages = message_history do
user_message do
text_content(text: "User message here")
end
assistant_message do
text_content(text: "Assistant response here")
end
end
respond_with messages: messages
end
Each message block returns a single content object:
message_history do
user_message do
text_content(text: "Text message")
end
user_message do
image_content(data: base64_data, mime_type: "image/png")
end
user_message do
audio_content(data: base64_data, mime_type: "audio/mp3")
end
user_message do
embedded_resource_content(resource: resource_data)
end
user_message do
resource_link(name: "file.txt", uri: "file:///file.txt")
end
end
Completions provide auto-complete suggestions for prompt arguments.
The simplest approach is an array of hint values:
argument do
name "tone"
description "The tone"
required false
completion ["formal", "casual", "humorous"]
end
For dynamic completions, create a class that inherits from ModelContextProtocol::Server::Completion:
class ToneCompletion < ModelContextProtocol::Server::Completion
TONES = ["formal", "casual", "humorous", "serious", "enthusiastic"].freeze
def call
filtered = TONES.select { |t| t.start_with?(argument_value.downcase) }
respond_with values: filtered
end
end
class MyPrompt < ModelContextProtocol::Server::Prompt
define do
name "my_prompt"
description "A prompt with custom completion"
argument do
name "tone"
description "The tone"
required false
completion ToneCompletion
end
end
end
Completion classes have access to:
| Variable | Description |
|---|---|
argument_name | String name of the argument being completed |
argument_value | Current partial value typed by the user |
Completions should return quickly (they're called during typing) and filter results based on argument_value.
Wrap long-running prompt generation in a cancellable block:
def call
messages = cancellable do
build_complex_message_history
end
respond_with messages: messages
end
For prompts, raise exceptions for errors. The server handles them appropriately:
def call
raise ArgumentError, "Invalid tone" unless valid_tone?(arguments[:tone])
messages = build_messages
respond_with messages: messages
end
def call
client_logger.info("Processing your request...")
server_logger.debug("Prompt called with arguments: #{arguments}")
messages = message_history do
user_message do
text_content(text: "Process this: #{arguments[:input]}")
end
end
respond_with messages: messages
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_prompt helper instead of calling the class directly:
RSpec.describe BrainstormExcusesPrompt, type: :mcp do
describe ".call" do
subject { call_mcp_prompt(described_class, { tone: "whiny", undesirable_activity: "doing chores" }) }
it { is_expected.to be_valid_mcp_prompt_response }
it "includes user and assistant messages" do
expect(subject).to have_message_with_role("user")
expect(subject).to have_message_with_role("assistant")
end
it "has the expected number of messages" do
expect(subject).to have_message_count(3)
end
it "incorporates the activity in the prompt" do
expect(subject).to have_message_with_role("user").containing("doing chores")
end
end
end
Completions use the call signature: CompletionClass.call(argument_name, argument_value).
RSpec.describe ToneCompletion do
describe ".call" do
subject { described_class.call("tone", argument_value) }
context "with partial value" do
let(:argument_value) { "for" }
it "returns filtered options" do
values = subject[:completion][:values]
expect(values).to include("formal")
expect(values).not_to include("casual")
end
end
end
end
it "is a valid MCP prompt" do
expect(BrainstormExcusesPrompt).to be_valid_mcp_class(:prompt)
end
| Matcher | Description |
|---|---|
be_valid_mcp_prompt_response | Response has valid prompt response structure |
have_message_with_role(role) | Response has message with given role; chainable with .containing(text) |
have_message_count(n) | Response has exact number of messages |
be_valid_mcp_class(:prompt) | Class is a valid MCP prompt with name and description |
Register prompts in the server configuration:
config.registry do
prompts do
register BrainstormExcusesPrompt
register CodeReviewPrompt
end
end
| Element | Convention | Example |
|---|---|---|
| Class name | PascalCase + Prompt | CodeReviewPrompt |
| Handler name | snake_case | code_review |
| Title | Title Case | Code Review Assistant |
| Argument name | snake_case | review_focus |
| File name | snake_case.rb | code_review_prompt.rb |
| Completion class | PascalCase + Completion | ToneCompletion |