diff --git a/README.md b/README.md index 64617bf..1ddf95e 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,8 @@ configuration = MCP::Configuration.new(protocol_version: "2024-11-05") MCP::Server.new(name: "test_server", configuration: configuration) ``` +If no protocol version is specified, the [Draft version](https://modelcontextprotocol.io/specification/draft) will be applied by default. + This will make all new server instances use the specified protocol version instead of the default version. The protocol version can be reset to the default by setting it to `nil`: ```ruby diff --git a/lib/mcp/configuration.rb b/lib/mcp/configuration.rb index 29a1b16..1a22a24 100644 --- a/lib/mcp/configuration.rb +++ b/lib/mcp/configuration.rb @@ -2,8 +2,10 @@ module MCP class Configuration - DEFAULT_PROTOCOL_VERSION = "2025-06-18" - SUPPORTED_PROTOCOL_VERSIONS = [DEFAULT_PROTOCOL_VERSION, "2025-03-26", "2024-11-05"] + # 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 @@ -33,7 +35,7 @@ def validate_tool_call_arguments=(validate_tool_call_arguments) end def protocol_version - @protocol_version || DEFAULT_PROTOCOL_VERSION + @protocol_version || DRAFT_PROTOCOL_VERSION end def protocol_version? @@ -93,8 +95,8 @@ def merge(other) private def validate_protocol_version!(protocol_version) - unless SUPPORTED_PROTOCOL_VERSIONS.include?(protocol_version) - message = "protocol_version must be #{SUPPORTED_PROTOCOL_VERSIONS[0...-1].join(", ")}, or #{SUPPORTED_PROTOCOL_VERSIONS[-1]}" + 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 diff --git a/lib/mcp/server.rb b/lib/mcp/server.rb index 3a2dd9f..6335caf 100644 --- a/lib/mcp/server.rb +++ b/lib/mcp/server.rb @@ -106,6 +106,8 @@ def define_tool(name: nil, title: nil, description: nil, input_schema: nil, anno def define_prompt(name: nil, title: nil, description: nil, arguments: [], &block) prompt = Prompt.define(name:, title:, description:, arguments:, &block) @prompts[prompt.name_value] = prompt + + validate! end def define_custom_method(method_name:, &block) @@ -171,6 +173,21 @@ def prompts_get_handler(&block) private def validate! + # NOTE: The draft protocol version is the next version after 2025-03-26. + if @configuration.protocol_version <= "2025-03-26" + if server_info.key?(:title) + message = "Error occurred in server_info. `title` is not supported in protocol version 2025-03-26 or earlier" + raise ArgumentError, message + end + + primitive_titles = [@tools.values, @prompts.values, @resources, @resource_templates].flatten.map(&:title) + + if primitive_titles.any? + message = "Error occurred in #{primitive_titles.join(", ")}. `title` is not supported in protocol version 2025-03-26 or earlier" + raise ArgumentError, message + end + end + if @configuration.protocol_version == "2024-11-05" if @instructions message = "`instructions` supported by protocol version 2025-03-26 or higher" diff --git a/test/mcp/configuration_test.rb b/test/mcp/configuration_test.rb index cd2777d..a4193a2 100644 --- a/test/mcp/configuration_test.rb +++ b/test/mcp/configuration_test.rb @@ -35,9 +35,24 @@ class ConfigurationTest < ActiveSupport::TestCase assert_equal test_context, reported_context end + # https://github.com/modelcontextprotocol/modelcontextprotocol/blob/14ec41c/schema/draft/schema.ts#L15 test "initializes with default protocol version" do config = Configuration.new - assert_equal Configuration::DEFAULT_PROTOCOL_VERSION, config.protocol_version + assert_equal Configuration::DRAFT_PROTOCOL_VERSION, config.protocol_version + end + + test "uses the draft protocol version when protocol_version is set to nil" do + config = Configuration.new(protocol_version: nil) + assert_equal Configuration::DRAFT_PROTOCOL_VERSION, config.protocol_version + end + + test "raises ArgumentError when setting the draft protocol version" do + exception = assert_raises(ArgumentError) do + # To use the draft version externally, either omit `protocol_version` or set it to nil. + Configuration.new(protocol_version: Configuration::DRAFT_PROTOCOL_VERSION) + end + + assert_equal("protocol_version must be 2025-06-18, 2025-03-26, or 2024-11-05", exception.message) end test "raises ArgumentError when protocol_version is not a supported protocol version" do diff --git a/test/mcp/server/transports/streamable_http_transport_test.rb b/test/mcp/server/transports/streamable_http_transport_test.rb index a00d29c..2a1ee53 100644 --- a/test/mcp/server/transports/streamable_http_transport_test.rb +++ b/test/mcp/server/transports/streamable_http_transport_test.rb @@ -81,7 +81,7 @@ class StreamableHTTPTransportTest < ActiveSupport::TestCase body = JSON.parse(response[2][0]) assert_equal "2.0", body["jsonrpc"] assert_equal "123", body["id"] - assert_equal "2025-06-18", body["result"]["protocolVersion"] + assert_equal Configuration::DRAFT_PROTOCOL_VERSION, body["result"]["protocolVersion"] end test "handles GET request with valid session ID" do diff --git a/test/mcp/server_test.rb b/test/mcp/server_test.rb index 2f2d188..dba9b80 100644 --- a/test/mcp/server_test.rb +++ b/test/mcp/server_test.rb @@ -133,7 +133,7 @@ class ServerTest < ActiveSupport::TestCase jsonrpc: "2.0", id: 1, result: { - protocolVersion: "2025-06-18", + protocolVersion: Configuration::DRAFT_PROTOCOL_VERSION, capabilities: { prompts: { listChanged: true }, resources: { listChanged: true }, @@ -776,7 +776,7 @@ def call(message:, server_context: nil) } response = @server.handle(request) - assert_equal Configuration::DEFAULT_PROTOCOL_VERSION, response[:result][:protocolVersion] + assert_equal Configuration::DRAFT_PROTOCOL_VERSION, response[:result][:protocolVersion] end test "server response does not include title when not configured" do @@ -852,6 +852,67 @@ def call(message:, server_context: nil) assert_equal("Error occurred in defined_tool. `annotations` are supported by protocol version 2025-03-26 or higher", exception.message) end + test "raises error if `title` of `server_info` is used with protocol version 2025-03-26" do + configuration = Configuration.new(protocol_version: "2025-03-26") + + exception = assert_raises(ArgumentError) do + Server.new(name: "test_server", title: "Example Server Display Name", configuration: configuration) + end + assert_equal("Error occurred in server_info. `title` is not supported in protocol version 2025-03-26 or earlier", exception.message) + end + + test "raises error if `title` of tool is used with protocol version 2025-03-26" do + configuration = Configuration.new(protocol_version: "2025-03-26") + server = Server.new(name: "test_server", configuration: configuration) + + exception = assert_raises(ArgumentError) do + server.define_tool( + title: "Test tool", + ) + end + assert_equal("Error occurred in Test tool. `title` is not supported in protocol version 2025-03-26 or earlier", exception.message) + end + + test "raises error if `title` of prompt is used with protocol version 2025-03-26" do + configuration = Configuration.new(protocol_version: "2025-03-26") + server = Server.new(name: "test_server", configuration: configuration) + + exception = assert_raises(ArgumentError) do + server.define_prompt( + title: "Test prompt", + ) + end + assert_equal("Error occurred in Test prompt. `title` is not supported in protocol version 2025-03-26 or earlier", exception.message) + end + + test "raises error if `title` of resource is used with protocol version 2025-03-26" do + configuration = Configuration.new(protocol_version: "2025-03-26") + + resource = Resource.new( + uri: "https://test_resource.invalid", + name: "test-resource", + title: "Test resource", + ) + exception = assert_raises(ArgumentError) do + Server.new(name: "test_server", resources: [resource], configuration: configuration) + end + assert_equal("Error occurred in Test resource. `title` is not supported in protocol version 2025-03-26 or earlier", exception.message) + end + + test "raises error if `title` of resource template is used with protocol version 2025-03-26" do + configuration = Configuration.new(protocol_version: "2025-03-26") + + resource = Resource.new( + uri: "https://test_resource.invalid", + name: "test-resource", + title: "Test resource template", + ) + exception = assert_raises(ArgumentError) do + Server.new(name: "test_server", resources: [resource], configuration: configuration) + end + assert_equal("Error occurred in Test resource template. `title` is not supported in protocol version 2025-03-26 or earlier", exception.message) + end + test "#define_tool adds a tool to the server" do @server.define_tool( name: "defined_tool",