Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,10 +624,12 @@ class MyPrompt < MCP::Prompt
arguments [
MCP::Prompt::Argument.new(
name: "message",
title: "Message Title",
description: "Input message",
required: true
)
]
meta({ version: "1.0", category: "example" })

class << self
def template(args, server_context:)
Expand Down Expand Up @@ -661,10 +663,12 @@ prompt = MCP::Prompt.define(
arguments: [
MCP::Prompt::Argument.new(
name: "message",
title: "Message Title",
description: "Input message",
required: true
)
]
],
meta: { version: "1.0", category: "example" }
) do |args, server_context:|
MCP::Prompt::Result.new(
description: "Response description",
Expand Down Expand Up @@ -692,10 +696,12 @@ server.define_prompt(
arguments: [
Prompt::Argument.new(
name: "message",
title: "Message Title",
description: "Input message",
required: true
)
]
],
meta: { version: "1.0", category: "example" }
) do |args, server_context:|
Prompt::Result.new(
description: "Response description",
Expand All @@ -718,7 +724,7 @@ e.g. around authentication state or user preferences.

### Key Components

- `MCP::Prompt::Argument` - Defines input parameters for the prompt template
- `MCP::Prompt::Argument` - Defines input parameters for the prompt template with name, title, description, and required flag
- `MCP::Prompt::Message` - Represents a message in the conversation with a role and content
- `MCP::Prompt::Result` - The output of a prompt template containing description and messages
- `MCP::Content::Text` - Text content for messages
Expand Down
21 changes: 19 additions & 2 deletions lib/mcp/prompt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ class << self
attr_reader :title_value
attr_reader :description_value
attr_reader :arguments_value
attr_reader :meta_value

def template(args, server_context: nil)
raise NotImplementedError, "Subclasses must implement template"
end

def to_h
{ name: name_value, title: title_value, description: description_value, arguments: arguments_value.map(&:to_h) }.compact
{
name: name_value,
title: title_value,
description: description_value,
arguments: arguments_value&.map(&:to_h),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the tests passes even without safe navigation. Is it necessary?

Suggested change
arguments: arguments_value&.map(&:to_h),
arguments: arguments_value.map(&:to_h),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good callout - this is a possible state if a Prompt class doesn't define arguments.

I added a test to exercise and cover this behaviour.

test "#to_h handles nil arguments value" do
class NoArgumentsPrompt < Prompt
description "No arguments prompt"
end
prompt = NoArgumentsPrompt
expected = {
name: "no_arguments_prompt",
description: "No arguments prompt",
}
assert_equal expected, prompt.to_h
end
end

_meta: meta_value,
}.compact
end

def inherited(subclass)
Expand All @@ -23,6 +30,7 @@ def inherited(subclass)
subclass.instance_variable_set(:@title_value, nil)
subclass.instance_variable_set(:@description_value, nil)
subclass.instance_variable_set(:@arguments_value, nil)
subclass.instance_variable_set(:@meta_value, nil)
end

def prompt_name(value = NOT_SET)
Expand Down Expand Up @@ -61,7 +69,15 @@ def arguments(value = NOT_SET)
end
end

def define(name: nil, title: nil, description: nil, arguments: [], &block)
def meta(value = NOT_SET)
if value == NOT_SET
@meta_value
else
@meta_value = value
end
end

def define(name: nil, title: nil, description: nil, arguments: [], meta: nil, &block)
Class.new(self) do
prompt_name name
title title
Expand All @@ -70,6 +86,7 @@ def define(name: nil, title: nil, description: nil, arguments: [], &block)
define_singleton_method(:template) do |args, server_context: nil|
instance_exec(args, server_context:, &block)
end
meta meta
end
end

Expand Down
13 changes: 9 additions & 4 deletions lib/mcp/prompt/argument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
module MCP
class Prompt
class Argument
attr_reader :name, :description, :required, :arguments
attr_reader :name, :title, :description, :required

def initialize(name:, description: nil, required: false)
def initialize(name:, title: nil, description: nil, required: false)
@name = name
@title = title
@description = description
@required = required
@arguments = arguments
end

def to_h
{ name:, description:, required: }.compact
{
name: name,
title: title,
description: description,
required: required,
}.compact
end
end
end
Expand Down
50 changes: 49 additions & 1 deletion test/mcp/prompt_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,14 @@ def template(args, server_context:)
title: "Mock Prompt",
description: "a mock prompt for testing",
arguments: [
Prompt::Argument.new(name: "test_argument", description: "Test argument", required: true),
Prompt::Argument.new(
name: "test_argument",
title: "Test argument title",
description: "This is a test argument description",
required: true,
),
],
meta: { foo: "bar" },
) do |args, server_context:|
content = Content::Text.new(args["test_argument"] + " user: #{server_context[:user_id]}")

Expand All @@ -130,6 +136,10 @@ def template(args, server_context:)
assert_equal "mock_prompt", prompt.name_value
assert_equal "a mock prompt for testing", prompt.description
assert_equal "test_argument", prompt.arguments.first.name
assert_equal "Test argument title", prompt.arguments.first.title
assert_equal "This is a test argument description", prompt.arguments.first.description
assert prompt.arguments.first.required
assert_equal({ foo: "bar" }, prompt.meta_value)

expected = {
description: "Hello, world!",
Expand All @@ -142,5 +152,43 @@ def template(args, server_context:)
result = prompt.template({ "test_argument" => "Hello, friend!" }, server_context: { user_id: 123 })
assert_equal expected, result.to_h
end

test "#to_h returns a hash with name, title, description, arguments, and meta" do
class FullPrompt < Prompt
prompt_name "test_prompt"
description "Test prompt description"
title "Test Prompt title"
arguments [
Prompt::Argument.new(name: "test_argument", description: "Test argument", required: true),
]
meta({ test: "meta" })
end

expected = {
name: "test_prompt",
title: "Test Prompt title",
description: "Test prompt description",
arguments: [
{ name: "test_argument", description: "Test argument", required: true },
],
_meta: { test: "meta" },
}

assert_equal expected, FullPrompt.to_h
end

test "#to_h handles nil arguments value" do
class NoArgumentsPrompt < Prompt
description "No arguments prompt"
end
prompt = NoArgumentsPrompt

expected = {
name: "no_arguments_prompt",
description: "No arguments prompt",
}

assert_equal expected, prompt.to_h
end
end
end