ruby-sdk/lib/mcp/configuration.rb
Koichi ITO bf729baef2 Add "Draft" protocol version to supported protocol versions
For example, the `title` introduced in #109 is not supported in the latest stable protocol version "2025-06-18":
https://modelcontextprotocol.io/specification/2025-06-18

It is supported in the Draft of the next version:
https://modelcontextprotocol.io/specification/draft

In other words, the fact that the Ruby SDK treats "2025-06-18" as the default protocol version while `title` is
only supported in the Draft results in an inconsistency.

Because of this, a new version `"Draft"` has been added to the list of supported versions.
Its value follows the schema below and is defined as `DRAFT-2025-v3`:
14ec41c/schema/draft/schema.ts (L15)

It was also considered to continue using the latest stable version "2025-06-18" as the Ruby SDK default instead of the Draft.
However, in that case, users would need to explicitly specify `MCP::Server.new(protocol_version: 'DRAFT-2025-v3')`,
which adds extra burden. In this PR, Draft is made the default in order to provide the following benefits:

- Users are likely to prefer using the Draft version over the latest stable version
- No need to specify the `protocol_version` option when initializing `MCP::Server`
- Users generally do not need to know the internal development value `DRAFT-2025-v3`

The primary case for specifying `protocol_version` explicitly is when strict adherence to a particular version is required.
However, it seems more practical to prioritize use cases where backward-compatible features, including those from the Draft,
are desirable. This is the rationale behind making the Draft version the default.

In addition, making the Draft version the default protocol version preserves backward compatibility for `MCP::Server`.

NOTE: `DRAFT-2025-v3` is a development code, so its value cannot be set externally.
It is applied either when `protocol_version` is omitted or when `nil` is set explicitly,
such as with `MCP::Configuration.new(protocol_version: nil)`.
This prevents users from creating implementations that depend on development codes like `DRAFT-2025-v3`.
2025-11-11 15:44:36 +09:00

118 lines
3.5 KiB
Ruby

# frozen_string_literal: true
module MCP
class Configuration
# DRAFT-2025-v3 is the latest draft protocol version:
# https://github.com/modelcontextprotocol/modelcontextprotocol/blob/14ec41c/schema/draft/schema.ts#L15
DRAFT_PROTOCOL_VERSION = "DRAFT-2025-v3"
SUPPORTED_STABLE_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"]
attr_writer :exception_reporter, :instrumentation_callback
def initialize(exception_reporter: nil, instrumentation_callback: nil, protocol_version: nil,
validate_tool_call_arguments: true)
@exception_reporter = exception_reporter
@instrumentation_callback = instrumentation_callback
@protocol_version = protocol_version
if protocol_version
validate_protocol_version!(protocol_version)
end
validate_value_of_validate_tool_call_arguments!(validate_tool_call_arguments)
@validate_tool_call_arguments = validate_tool_call_arguments
end
def protocol_version=(protocol_version)
validate_protocol_version!(protocol_version)
@protocol_version = protocol_version
end
def validate_tool_call_arguments=(validate_tool_call_arguments)
validate_value_of_validate_tool_call_arguments!(validate_tool_call_arguments)
@validate_tool_call_arguments = validate_tool_call_arguments
end
def protocol_version
@protocol_version || DRAFT_PROTOCOL_VERSION
end
def protocol_version?
!@protocol_version.nil?
end
def exception_reporter
@exception_reporter || default_exception_reporter
end
def exception_reporter?
!@exception_reporter.nil?
end
def instrumentation_callback
@instrumentation_callback || default_instrumentation_callback
end
def instrumentation_callback?
!@instrumentation_callback.nil?
end
attr_reader :validate_tool_call_arguments
def validate_tool_call_arguments?
!!@validate_tool_call_arguments
end
def merge(other)
return self if other.nil?
exception_reporter = if other.exception_reporter?
other.exception_reporter
else
@exception_reporter
end
instrumentation_callback = if other.instrumentation_callback?
other.instrumentation_callback
else
@instrumentation_callback
end
protocol_version = if other.protocol_version?
other.protocol_version
else
@protocol_version
end
validate_tool_call_arguments = other.validate_tool_call_arguments
Configuration.new(
exception_reporter:,
instrumentation_callback:,
protocol_version:,
validate_tool_call_arguments:,
)
end
private
def validate_protocol_version!(protocol_version)
unless SUPPORTED_STABLE_PROTOCOL_VERSIONS.include?(protocol_version)
message = "protocol_version must be #{SUPPORTED_STABLE_PROTOCOL_VERSIONS[0...-1].join(", ")}, or #{SUPPORTED_STABLE_PROTOCOL_VERSIONS[-1]}"
raise ArgumentError, message
end
end
def validate_value_of_validate_tool_call_arguments!(validate_tool_call_arguments)
unless validate_tool_call_arguments.is_a?(TrueClass) || validate_tool_call_arguments.is_a?(FalseClass)
raise ArgumentError, "validate_tool_call_arguments must be a boolean"
end
end
def default_exception_reporter
@default_exception_reporter ||= ->(exception, server_context) {}
end
def default_instrumentation_callback
@default_instrumentation_callback ||= ->(data) {}
end
end
end