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:
commit
05fb98c3bb
26 changed files with 106 additions and 100 deletions
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ plugins:
|
|||
- rubocop-minitest
|
||||
- rubocop-rake
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.7
|
||||
|
||||
Gemspec/DevelopmentDependencies:
|
||||
Enabled: true
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
9
Gemfile
9
Gemfile
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module MCP
|
|||
end
|
||||
|
||||
def to_h
|
||||
{ uri:, mime_type: }.compact
|
||||
{ uri: uri, mime_type: mime_type }.compact
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ module MCP
|
|||
|
||||
notification = {
|
||||
jsonrpc: "2.0",
|
||||
method:,
|
||||
method: method,
|
||||
}
|
||||
notification[:params] = params if params
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ module MCP
|
|||
idempotentHint: idempotent_hint,
|
||||
openWorldHint: open_world_hint,
|
||||
readOnlyHint: read_only_hint,
|
||||
title:,
|
||||
title: title,
|
||||
}.compact
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue