ruby-sdk/test/mcp/prompt_test.rb
Koichi ITO aa8bbad491 Support Ruby 2.7 - Ruby 3.1
## Summary

Unlike applications, libraries provide value by remaining usable for a broad range of users,
even when that means supporting older Ruby versions beyond Ruby's own EOL timeline.

I maintain RuboCop, a Ruby linter. Because linters need to work across the ecosystem,
they often need to keep compatibility with older Ruby versions to some extent.

The trade-off is that the MCP Ruby SDK must remain compatible with Ruby 2.7.
However, given the ecosystem impact, supporting Ruby 2.7 is still worthwhile.

For example, bundled gems such as `csv` (Ruby >= 2.5), `bigdecimal` (Ruby >= 2.5), `json` (Ruby >= 2.7),
and `language_server-protocol` (Ruby >= 2.5) tend to keep compatibility with older Ruby versions.

Unlike dropping support, which imposes restrictions on users,
this change broadens compatibility and does not introduce breaking changes.

## Development Note

`rubocop-shopify` (2.18) supports Ruby 3.1+, and this PR does not change that.
Since RuboCop can configure the runtime Ruby version and the target Ruby version for analysis independently,
CI continues to run RuboCop on Ruby 4.0, while analyzing code with `TargetRubyVersion: 2.7`.

When setting `TargetRubyVersion: 2.7`, several RuboCop offenses were resolved.

Additionally, Sorbet-related libraries support Ruby 3.0+. Since these are not dependencies of the MCP Ruby SDK itself,
they are versioned and tested in the development Gemfile.
2026-01-06 10:38:56 +09:00

199 lines
7 KiB
Ruby

# frozen_string_literal: true
require "test_helper"
module MCP
class PromptTest < ActiveSupport::TestCase
class TestPrompt < Prompt
description "Test prompt"
icons [{ mimeType: "image/png", sizes: ["48x48", "96x96"], src: "https://example.com", theme: "light" }]
arguments [
Prompt::Argument.new(name: "test_argument", description: "Test argument", required: true),
]
class << self
def template(args, server_context:)
Prompt::Result.new(
description: "Hello, world!",
messages: [
Prompt::Message.new(role: "user", content: Content::Text.new("Hello, world!")),
Prompt::Message.new(role: "assistant", content: Content::Text.new("Hello, friend!")),
],
)
end
end
end
test "#template returns a Result with description and messages" do
prompt = TestPrompt
expected_template_result = {
description: "Hello, world!",
messages: [
{ role: "user", content: { text: "Hello, world!", type: "text" } },
{ role: "assistant", content: { text: "Hello, friend!", type: "text" } },
],
}
result = prompt.template({ "test_argument" => "Hello, friend!" }, server_context: { user_id: 123 })
assert_equal expected_template_result, result.to_h
end
test "allows declarative definition of prompts as classes" do
class MockPrompt < Prompt
prompt_name "my_mock_prompt"
description "a mock prompt for testing"
icons [{ mimeType: "image/png", sizes: ["48x48", "96x96"], src: "https://example.com", theme: "light" }]
arguments [
Prompt::Argument.new(name: "test_argument", description: "Test argument", required: true),
]
class << self
def template(args, server_context:)
Prompt::Result.new(
description: "Hello, world!",
messages: [
Prompt::Message.new(role: "user", content: Content::Text.new("Hello, world!")),
Prompt::Message.new(role: "assistant", content: Content::Text.new(args["test_argument"])),
],
)
end
end
end
prompt = MockPrompt
assert_equal "my_mock_prompt", prompt.name_value
assert_equal "a mock prompt for testing", prompt.description
assert_equal([{ mimeType: "image/png", sizes: ["48x48", "96x96"], src: "https://example.com", theme: "light" }], prompt.icons)
assert_equal "test_argument", prompt.arguments.first.name
assert_equal "Test argument", prompt.arguments.first.description
assert prompt.arguments.first.required
expected_template_result = {
description: "Hello, world!",
messages: [
{ role: "user", content: { text: "Hello, world!", type: "text" } },
{ role: "assistant", content: { text: "Hello, friend!", type: "text" } },
],
}
result = prompt.template({ "test_argument" => "Hello, friend!" }, server_context: { user_id: 123 })
assert_equal expected_template_result, result.to_h
end
test "defaults to class name as prompt name" do
class DefaultNamePrompt < Prompt
description "a mock prompt for testing"
arguments [
Prompt::Argument.new(name: "test_argument", description: "Test argument", required: true),
]
class << self
def template(args, server_context:)
Prompt::Result.new(
description: "Hello, world!",
messages: [
Prompt::Message.new(role: "user", content: Content::Text.new("Hello, world!")),
Prompt::Message.new(role: "assistant", content: Content::Text.new(args["test_argument"])),
],
)
end
end
end
prompt = DefaultNamePrompt
assert_equal "default_name_prompt", prompt.name_value
assert_equal "a mock prompt for testing", prompt.description
assert_equal "test_argument", prompt.arguments.first.name
end
test ".define allows definition of simple prompts with a block" do
prompt = Prompt.define(
name: "mock_prompt",
title: "Mock Prompt",
description: "a mock prompt for testing",
icons: [{ mimeType: "image/png", sizes: ["48x48", "96x96"], src: "https://example.com", theme: "light" }],
arguments: [
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]}")
Prompt::Result.new(
description: "Hello, world!",
messages: [
Prompt::Message.new(role: "user", content: Content::Text.new("Hello, world!")),
Prompt::Message.new(role: "assistant", content: content),
],
)
end
assert_equal "mock_prompt", prompt.name_value
assert_equal "a mock prompt for testing", prompt.description
assert_equal([{ mimeType: "image/png", sizes: ["48x48", "96x96"], src: "https://example.com", theme: "light" }], prompt.icons)
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!",
messages: [
{ role: "user", content: { text: "Hello, world!", type: "text" } },
{ role: "assistant", content: { text: "Hello, friend! user: 123", type: "text" } },
],
}
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