Merge pull request #206 from koic/support_for_ruby_2_7_to_3_1

Support Ruby 2.7 - Ruby 3.1
This commit is contained in:
Koichi ITO 2026-01-07 11:13:05 +09:00 • committed by GitHub
commit 05fb98c3bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 106 additions and 100 deletions

View file

@ -7,6 +7,9 @@ jobs:
strategy:
matrix:
entry:
- { ruby: '2.7', allowed-failure: false }
- { ruby: '3.0', allowed-failure: false }
- { ruby: '3.1', allowed-failure: false }
- { ruby: '3.2', allowed-failure: false }
- { ruby: '3.3', allowed-failure: false }
- { ruby: '3.4', allowed-failure: false }
@ -29,7 +32,7 @@ jobs:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2 # Specify the oldest supported Ruby version.
ruby-version: 4.0 # Specify the latest supported Ruby version.
bundler-cache: true
- run: bundle exec rake rubocop
@ -40,6 +43,6 @@ jobs:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2 # Specify the oldest supported Ruby version.
ruby-version: 4.0 # Specify the latest supported Ruby version.
bundler-cache: true
- run: bundle exec yard --no-output

View file

@ -5,6 +5,9 @@ plugins:
- rubocop-minitest
- rubocop-rake
AllCops:
TargetRubyVersion: 2.7
Gemspec/DevelopmentDependencies:
Enabled: true

View file

@ -6,7 +6,7 @@ This is the official Ruby SDK for the Model Context Protocol (MCP), implementing
## Dev environment setup
- Ruby 3.2.0+ required
- Ruby 3.2.0+ required to run the full test suite, including all Sorbet-related features
- Run `bundle install` to install dependencies
- Dependencies: `json-schema` >= 4.1 - Schema validation

View file

@ -8,17 +8,18 @@ gemspec
# Specify development dependencies below
gem "rubocop-minitest", require: false
gem "rubocop-rake", require: false
gem "rubocop-shopify", require: false
gem "rubocop-shopify", ">= 2.18", require: false if RUBY_VERSION >= "3.1"
gem "puma", ">= 5.0.0"
gem "rackup", ">= 2.1.0"
gem "activesupport"
gem "debug"
# Fix io-console install error when Ruby 3.0.
gem "debug" if RUBY_VERSION >= "3.1"
gem "rake", "~> 13.0"
gem "sorbet-static-and-runtime"
gem "sorbet-static-and-runtime" if RUBY_VERSION >= "3.0"
gem "yard", "~> 0.9"
gem "yard-sorbet", "~> 0.9"
gem "yard-sorbet", "~> 0.9" if RUBY_VERSION >= "3.1"
group :test do
gem "faraday", ">= 2.0"

View file

@ -22,14 +22,14 @@ module JsonRpcHandler
def handle(request, id_validation_pattern: DEFAULT_ALLOWED_ID_CHARACTERS, &method_finder)
if request.is_a?(Array)
return error_response(id: :unknown_id, id_validation_pattern:, error: {
return error_response(id: :unknown_id, id_validation_pattern: id_validation_pattern, error: {
code: ErrorCode::INVALID_REQUEST,
message: "Invalid Request",
data: "Request is an empty array",
}) if request.empty?
# Handle batch requests
responses = request.map { |req| process_request(req, id_validation_pattern:, &method_finder) }.compact
responses = request.map { |req| process_request(req, id_validation_pattern: id_validation_pattern, &method_finder) }.compact
# A single item is hoisted out of the array
return responses.first if responses.one?
@ -38,9 +38,9 @@ module JsonRpcHandler
responses if responses.any?
elsif request.is_a?(Hash)
# Handle single request
process_request(request, id_validation_pattern:, &method_finder)
process_request(request, id_validation_pattern: id_validation_pattern, &method_finder)
else
error_response(id: :unknown_id, id_validation_pattern:, error: {
error_response(id: :unknown_id, id_validation_pattern: id_validation_pattern, error: {
code: ErrorCode::INVALID_REQUEST,
message: "Invalid Request",
data: "Request must be an array or a hash",
@ -51,9 +51,9 @@ module JsonRpcHandler
def handle_json(request_json, id_validation_pattern: DEFAULT_ALLOWED_ID_CHARACTERS, &method_finder)
begin
request = JSON.parse(request_json, symbolize_names: true)
response = handle(request, id_validation_pattern:, &method_finder)
response = handle(request, id_validation_pattern: id_validation_pattern, &method_finder)
rescue JSON::ParserError
response = error_response(id: :unknown_id, id_validation_pattern:, error: {
response = error_response(id: :unknown_id, id_validation_pattern: id_validation_pattern, error: {
code: ErrorCode::PARSE_ERROR,
message: "Parse error",
data: "Invalid JSON",
@ -74,7 +74,7 @@ module JsonRpcHandler
'Method name must be a string and not start with "rpc."'
end
return error_response(id: :unknown_id, id_validation_pattern:, error: {
return error_response(id: :unknown_id, id_validation_pattern: id_validation_pattern, error: {
code: ErrorCode::INVALID_REQUEST,
message: "Invalid Request",
data: error,
@ -84,7 +84,7 @@ module JsonRpcHandler
params = request[:params]
unless valid_params?(params)
return error_response(id:, id_validation_pattern:, error: {
return error_response(id: id, id_validation_pattern: id_validation_pattern, error: {
code: ErrorCode::INVALID_PARAMS,
message: "Invalid params",
data: "Method parameters must be an array or an object or null",
@ -95,7 +95,7 @@ module JsonRpcHandler
method = method_finder.call(method_name)
if method.nil?
return error_response(id:, id_validation_pattern:, error: {
return error_response(id: id, id_validation_pattern: id_validation_pattern, error: {
code: ErrorCode::METHOD_NOT_FOUND,
message: "Method not found",
data: method_name,
@ -104,9 +104,9 @@ module JsonRpcHandler
result = method.call(params)
success_response(id:, result:)
success_response(id: id, result: result)
rescue StandardError => e
error_response(id:, id_validation_pattern:, error: {
error_response(id: id, id_validation_pattern: id_validation_pattern, error: {
code: ErrorCode::INTERNAL_ERROR,
message: "Internal error",
data: e.message,
@ -136,8 +136,8 @@ module JsonRpcHandler
def success_response(id:, result:)
{
jsonrpc: Version::V2_0,
id:,
result:,
id: id,
result: result,
} unless id.nil?
end

View file

@ -18,42 +18,42 @@ module MCP
rescue Faraday::BadRequestError => e
raise RequestHandlerError.new(
"The #{method} request is invalid",
{ method:, params: },
{ method: method, params: params },
error_type: :bad_request,
original_error: e,
)
rescue Faraday::UnauthorizedError => e
raise RequestHandlerError.new(
"You are unauthorized to make #{method} requests",
{ method:, params: },
{ method: method, params: params },
error_type: :unauthorized,
original_error: e,
)
rescue Faraday::ForbiddenError => e
raise RequestHandlerError.new(
"You are forbidden to make #{method} requests",
{ method:, params: },
{ method: method, params: params },
error_type: :forbidden,
original_error: e,
)
rescue Faraday::ResourceNotFound => e
raise RequestHandlerError.new(
"The #{method} request is not found",
{ method:, params: },
{ method: method, params: params },
error_type: :not_found,
original_error: e,
)
rescue Faraday::UnprocessableEntityError => e
raise RequestHandlerError.new(
"The #{method} request is unprocessable",
{ method:, params: },
{ method: method, params: params },
error_type: :unprocessable_entity,
original_error: e,
)
rescue Faraday::Error => e # Catch-all
raise RequestHandlerError.new(
"Internal error handling #{method} request",
{ method:, params: },
{ method: method, params: params },
error_type: :internal_error,
original_error: e,
)

View file

@ -85,10 +85,10 @@ module MCP
validate_tool_call_arguments = other.validate_tool_call_arguments
Configuration.new(
exception_reporter:,
instrumentation_callback:,
protocol_version:,
validate_tool_call_arguments:,
exception_reporter: exception_reporter,
instrumentation_callback: instrumentation_callback,
protocol_version: protocol_version,
validate_tool_call_arguments: validate_tool_call_arguments,
)
end

View file

@ -11,7 +11,7 @@ module MCP
end
def to_h
{ text:, annotations:, type: "text" }.compact
{ text: text, annotations: annotations, type: "text" }.compact
end
end
@ -25,7 +25,7 @@ module MCP
end
def to_h
{ data:, mime_type:, annotations:, type: "image" }.compact
{ data: data, mime_type: mime_type, annotations: annotations, type: "image" }.compact
end
end
end

View file

@ -6,7 +6,7 @@ module MCP
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
begin
@instrumentation_data = {}
add_instrumentation_data(method:)
add_instrumentation_data(method: method)
result = yield block

View file

@ -96,7 +96,7 @@ module MCP
icons icons
arguments arguments
define_singleton_method(:template) do |args, server_context: nil|
instance_exec(args, server_context:, &block)
instance_exec(args, server_context: server_context, &block)
end
meta meta
end

View file

@ -11,7 +11,7 @@ module MCP
end
def to_h
{ role:, content: content.to_h }.compact
{ role: role, content: content.to_h }.compact
end
end
end

View file

@ -11,7 +11,7 @@ module MCP
end
def to_h
{ description:, messages: messages.map(&:to_h) }.compact
{ description: description, messages: messages.map(&:to_h) }.compact
end
end
end

View file

@ -11,7 +11,7 @@ module MCP
end
def to_h
{ uri:, mime_type: }.compact
{ uri: uri, mime_type: mime_type }.compact
end
end

View file

@ -10,7 +10,7 @@ module MCP
end
def to_h
{ resource: resource.to_h, annotations: }.compact
{ resource: resource.to_h, annotations: annotations }.compact
end
end
end

View file

@ -114,7 +114,7 @@ module MCP
end
def define_tool(name: nil, title: nil, description: nil, input_schema: nil, annotations: nil, meta: nil, &block)
tool = Tool.define(name:, title:, description:, input_schema:, annotations:, meta:, &block)
tool = Tool.define(name: name, title: title, description: description, input_schema: input_schema, annotations: annotations, meta: meta, &block)
tool_name = tool.name_value
@tool_names << tool_name
@ -124,7 +124,7 @@ module MCP
end
def define_prompt(name: nil, title: nil, description: nil, arguments: [], &block)
prompt = Prompt.define(name:, title:, description:, arguments:, &block)
prompt = Prompt.define(name: name, title: title, description: description, arguments: arguments, &block)
@prompts[prompt.name_value] = prompt
validate!
@ -289,11 +289,11 @@ module MCP
def server_info
@server_info ||= {
description:,
icons:,
name:,
title:,
version:,
description: description,
icons: icons,
name: name,
title: title,
version: version,
websiteUrl: website_url,
}.compact
end
@ -316,13 +316,13 @@ module MCP
tool = tools[tool_name]
unless tool
add_instrumentation_data(tool_name:, error: :tool_not_found)
add_instrumentation_data(tool_name: tool_name, error: :tool_not_found)
return error_tool_response("Tool not found: #{tool_name}")
end
arguments = request[:arguments] || {}
add_instrumentation_data(tool_name:)
add_instrumentation_data(tool_name: tool_name)
if tool.input_schema&.missing_required_arguments?(arguments)
add_instrumentation_data(error: :missing_required_arguments)
@ -360,7 +360,7 @@ module MCP
raise RequestHandlerError.new("Prompt not found #{prompt_name}", request, error_type: :prompt_not_found)
end
add_instrumentation_data(prompt_name:)
add_instrumentation_data(prompt_name: prompt_name)
prompt_args = request[:arguments]
prompt.validate_arguments!(prompt_args)

View file

@ -42,7 +42,7 @@ module MCP
notification = {
jsonrpc: "2.0",
method:,
method: method,
}
notification[:params] = params if params

View file

@ -19,7 +19,7 @@ module MCP
idempotentHint: idempotent_hint,
openWorldHint: open_world_hint,
readOnlyHint: read_only_hint,
title:,
title: title,
}.compact
end
end

View file

@ -23,7 +23,7 @@ module MCP
end
def to_h
{ content:, isError: error?, structuredContent: @structured_content }.compact
{ content: content, isError: error?, structuredContent: @structured_content }.compact
end
end
end

View file

@ -13,7 +13,8 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/modelcontextprotocol/ruby-sdk"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.2.0"
# Since this library is used by a broad range of users, it does not align its support policy with Ruby's EOL.
spec.required_ruby_version = ">= 2.7.0"
spec.metadata["allowed_push_host"] = "https://rubygems.org"
spec.metadata["changelog_uri"] = "https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v#{spec.version}"

View file

@ -94,7 +94,7 @@ describe JsonRpcHandler do
register("add") { |params| params[:a] + params[:b] }
id = "request-123_abc"
handle jsonrpc: "2.0", id:, method: "add", params: { a: 1, b: 2 }
handle jsonrpc: "2.0", id: id, method: "add", params: { a: 1, b: 2 }
assert_rpc_success expected_result: 3
assert_equal id, @response[:id]
@ -104,7 +104,7 @@ describe JsonRpcHandler do
register("add") { |params| params[:a] + params[:b] }
id = 42
handle jsonrpc: "2.0", id:, method: "add", params: { a: 1, b: 2 }
handle jsonrpc: "2.0", id: id, method: "add", params: { a: 1, b: 2 }
assert_rpc_success expected_result: 3
assert_equal id, @response[:id]
@ -124,7 +124,7 @@ describe JsonRpcHandler do
register("add") { |params| params[:a] + params[:b] }
id = "request-123_ABC"
handle jsonrpc: "2.0", id:, method: "add", params: { a: 1, b: 2 }
handle jsonrpc: "2.0", id: id, method: "add", params: { a: 1, b: 2 }
assert_rpc_success expected_result: 3
assert_equal id, @response[:id]
@ -134,7 +134,7 @@ describe JsonRpcHandler do
register("add") { |params| params[:a] + params[:b] }
id = "550e8400-e29b-41d4-a716-446655440000"
handle jsonrpc: "2.0", id:, method: "add", params: { a: 1, b: 2 }
handle jsonrpc: "2.0", id: id, method: "add", params: { a: 1, b: 2 }
assert_rpc_success expected_result: 3
assert_equal id, @response[:id]

Some files were not shown because too many files have changed in this diff Show more