From 9bdcb5539ba9b4b2cbf15138cafb4552df1f7236 Mon Sep 17 00:00:00 2001 From: Ates Goral Date: Sun, 14 Jun 2026 11:45:38 +0200 Subject: [PATCH] feat: deprecate roots sampling and logging Warn when Roots, Sampling, or Logging APIs are used with MCP protocol version 2026-07-28, while leaving the latest stable default at 2025-11-25. Add a shared protocol deprecation helper, YARD deprecation notes, and regression tests covering warnings for 2026-07-28 and no warnings for older protocol versions. Closes https://github.com/modelcontextprotocol/ruby-sdk/issues/390 --- CHANGELOG.md | 4 ++ lib/mcp.rb | 1 + lib/mcp/client/http.rb | 3 + lib/mcp/client/stdio.rb | 3 + lib/mcp/configuration.rb | 7 ++- lib/mcp/protocol_deprecations.rb | 50 +++++++++++++++ lib/mcp/server.rb | 32 +++++++++- lib/mcp/server_session.rb | 10 ++- test/mcp/client/http_test.rb | 40 ++++++++++++ test/mcp/client/stdio_test.rb | 34 +++++++++++ test/mcp/configuration_test.rb | 4 +- test/mcp/server_notification_test.rb | 86 ++++++++++++++++++++++++++ test/mcp/server_roots_test.rb | 73 ++++++++++++++++++++++ test/mcp/server_sampling_test.rb | 43 +++++++++++++ test/mcp/server_test.rb | 91 ++++++++++++++++++++++++++++ test/test_helper.rb | 18 ++++++ 16 files changed, 493 insertions(+), 6 deletions(-) create mode 100644 lib/mcp/protocol_deprecations.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index bdb2b5b1..22ecd47f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Warn when deprecated Roots, Sampling, or Logging APIs are used with protocol version `2026-07-28` (#406) + ## [0.25.0] - 2026-07-18 ### Added diff --git a/lib/mcp.rb b/lib/mcp.rb index 5e0838ea..13d14d41 100644 --- a/lib/mcp.rb +++ b/lib/mcp.rb @@ -2,6 +2,7 @@ require_relative "json_rpc_handler" require_relative "mcp/configuration" +require_relative "mcp/protocol_deprecations" require_relative "mcp/string_utils" require_relative "mcp/transport" require_relative "mcp/version" diff --git a/lib/mcp/client/http.rb b/lib/mcp/client/http.rb index 112fe3ee..8a572cf7 100644 --- a/lib/mcp/client/http.rb +++ b/lib/mcp/client/http.rb @@ -4,6 +4,7 @@ require_relative "../../json_rpc_handler" require_relative "../configuration" require_relative "../methods" +require_relative "../protocol_deprecations" require_relative "../version" module MCP @@ -334,6 +335,8 @@ def connect(client_info: nil, protocol_version: nil, capabilities: {}) ) end + MCP::ProtocolDeprecations.warn_for_client_capabilities(capabilities, protocol_version: negotiated_protocol_version, uplevel: 1) + begin send_request(request: { jsonrpc: JsonRpcHandler::Version::V2_0, diff --git a/lib/mcp/client/stdio.rb b/lib/mcp/client/stdio.rb index 5bcf6517..747687b8 100644 --- a/lib/mcp/client/stdio.rb +++ b/lib/mcp/client/stdio.rb @@ -7,6 +7,7 @@ require_relative "../../json_rpc_handler" require_relative "../configuration" require_relative "../methods" +require_relative "../protocol_deprecations" require_relative "../version" module MCP @@ -128,6 +129,8 @@ def connect(client_info: nil, protocol_version: nil, capabilities: {}) ) end + MCP::ProtocolDeprecations.warn_for_client_capabilities(capabilities, protocol_version: negotiated_protocol_version, uplevel: 1) + begin notification = { jsonrpc: JsonRpcHandler::Version::V2_0, diff --git a/lib/mcp/configuration.rb b/lib/mcp/configuration.rb index 3603e6fb..fa097f09 100644 --- a/lib/mcp/configuration.rb +++ b/lib/mcp/configuration.rb @@ -3,8 +3,13 @@ module MCP class Configuration LATEST_STABLE_PROTOCOL_VERSION = "2025-11-25" + ROOTS_SAMPLING_LOGGING_DEPRECATED_PROTOCOL_VERSION = "2026-07-28" SUPPORTED_STABLE_PROTOCOL_VERSIONS = [ - LATEST_STABLE_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", + ROOTS_SAMPLING_LOGGING_DEPRECATED_PROTOCOL_VERSION, + LATEST_STABLE_PROTOCOL_VERSION, + "2025-06-18", + "2025-03-26", + "2024-11-05", ].freeze DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26" diff --git a/lib/mcp/protocol_deprecations.rb b/lib/mcp/protocol_deprecations.rb new file mode 100644 index 00000000..834ebd21 --- /dev/null +++ b/lib/mcp/protocol_deprecations.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require_relative "configuration" + +module MCP + module ProtocolDeprecations + extend self + + ROOTS_MESSAGE = + "MCP Roots (`roots/list` and `notifications/roots/list_changed`) is deprecated as of protocol version " \ + "2026-07-28 (SEP-2577). Use tool parameters, resource URIs, server configuration, or environment " \ + "variables instead." + SAMPLING_MESSAGE = + "MCP Sampling (`sampling/createMessage`) is deprecated as of protocol version 2026-07-28 (SEP-2577). " \ + "Use direct LLM provider APIs instead." + LOGGING_MESSAGE = + "MCP Logging (`logging/setLevel` and `notifications/message`) is deprecated as of protocol version " \ + "2026-07-28 (SEP-2577). Use stderr or OpenTelemetry instead." + + MESSAGES = { + roots: ROOTS_MESSAGE, + sampling: SAMPLING_MESSAGE, + logging: LOGGING_MESSAGE, + }.freeze + + def deprecated_roots_sampling_logging?(protocol_version) + protocol_version == Configuration::ROOTS_SAMPLING_LOGGING_DEPRECATED_PROTOCOL_VERSION + end + + def warn_for(feature, protocol_version:, uplevel: 1) + return unless deprecated_roots_sampling_logging?(protocol_version) + + Kernel.warn(MESSAGES.fetch(feature), uplevel: uplevel) + end + + def warn_for_client_capabilities(capabilities, protocol_version:, uplevel: 1) + return unless deprecated_roots_sampling_logging?(protocol_version) + return unless capabilities + + warn_for(:roots, protocol_version: protocol_version, uplevel: uplevel) if capability?(capabilities, :roots) + warn_for(:sampling, protocol_version: protocol_version, uplevel: uplevel) if capability?(capabilities, :sampling) + end + + private + + def capability?(capabilities, key) + capabilities.key?(key) || capabilities.key?(key.to_s) + end + end +end diff --git a/lib/mcp/server.rb b/lib/mcp/server.rb index 0f77af3c..846c457e 100644 --- a/lib/mcp/server.rb +++ b/lib/mcp/server.rb @@ -7,6 +7,7 @@ require_relative "methods" require_relative "logging_message_notification" require_relative "progress" +require_relative "protocol_deprecations" require_relative "server_context" require_relative "server/capabilities" require_relative "server/pagination" @@ -147,6 +148,7 @@ def initialize( self.cache_scope = cache_scope @configuration = MCP.configuration.merge(configuration) @client = nil + @client_protocol_version = nil validate! @@ -317,6 +319,8 @@ def notify_resources_list_changed # is deprecated as of MCP protocol version 2026-07-28 (SEP-2577). # Use stderr or OpenTelemetry instead. def notify_log_message(data:, level:, logger: nil) + warn_if_deprecated_protocol_feature(:logging, uplevel: 1) + return unless @transport return unless logging_message_notification&.should_notify?(level) @@ -337,6 +341,8 @@ def notify_log_message(data:, level:, logger: nil) # version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs, # server configuration, or environment variables instead. def roots_list_changed_handler(&block) + warn_if_deprecated_protocol_feature(:roots, uplevel: 1) + @handlers[Methods::NOTIFICATIONS_ROOTS_LIST_CHANGED] = block end @@ -501,6 +507,13 @@ def handle_request(request, method, session: nil, related_request_id: nil) return ->(params) { handle_cancelled_notification(params, session: session) } end + case method + when Methods::NOTIFICATIONS_ROOTS_LIST_CHANGED + warn_if_deprecated_protocol_feature(:roots, session: session, uplevel: 2) + when Methods::LOGGING_SET_LEVEL + warn_if_deprecated_protocol_feature(:logging, session: session, uplevel: 2) + end + handler = @handlers[method] unless handler instrument_call("unsupported_method", server_context: { request: request }) do @@ -640,7 +653,11 @@ def init(params, session: nil) response_instructions = nil end - session&.mark_initialized! + if session + session.mark_initialized!(protocol_version: negotiated_version) + else + @client_protocol_version = negotiated_version + end { protocolVersion: negotiated_version, @@ -707,6 +724,19 @@ def configure_logging_level(request, session: nil) {} end + def warn_if_deprecated_protocol_feature(feature, session: nil, uplevel: 1) + protocol_version = effective_deprecation_protocol_version(session) + MCP::ProtocolDeprecations.warn_for(feature, protocol_version: protocol_version, uplevel: uplevel) + end + + def effective_deprecation_protocol_version(session) + session&.protocol_version || @client_protocol_version || explicit_protocol_version + end + + def explicit_protocol_version + configuration.protocol_version if configuration.protocol_version? + end + def list_tools(request) page = paginate(@tools.values, cursor: cursor_from(request), page_size: @page_size, request: request, &:to_h) diff --git a/lib/mcp/server_session.rb b/lib/mcp/server_session.rb index 754c1d00..f580060c 100644 --- a/lib/mcp/server_session.rb +++ b/lib/mcp/server_session.rb @@ -7,7 +7,7 @@ module MCP # Holds per-connection state for a single client session. # Created by the transport layer; delegates request handling to the shared `Server`. class ServerSession - attr_reader :session_id, :client, :logging_message_notification + attr_reader :session_id, :client, :logging_message_notification, :protocol_version def initialize(server:, transport:, session_id: nil) @server = server @@ -16,6 +16,7 @@ def initialize(server:, transport:, session_id: nil) @client = nil @client_capabilities = nil @logging_message_notification = nil + @protocol_version = nil @in_flight = {} @in_flight_mutex = Mutex.new @initialized = false @@ -29,8 +30,9 @@ def initialized? # Called by `Server#init` after a successful `initialize` response, so subsequent # `initialize` requests on the same session can be rejected per MCP spec # (the initialization phase MUST be the first interaction). - def mark_initialized! + def mark_initialized!(protocol_version: nil) @initialized = true + @protocol_version = protocol_version end # Registers a `Cancellation` token for an in-flight request. @@ -107,6 +109,7 @@ def client_capabilities # version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs, # server configuration, or environment variables instead. def list_roots(related_request_id: nil) + @server.send(:warn_if_deprecated_protocol_feature, :roots, session: self, uplevel: 2) warn_unassociated_request(__method__, related_request_id) unless client_capabilities&.dig(:roots) @@ -133,6 +136,7 @@ def ping(related_request_id: nil) # MCP protocol version 2026-07-28 (SEP-2577). Use direct LLM provider # APIs instead. def create_sampling_message(related_request_id: nil, **kwargs) + @server.send(:warn_if_deprecated_protocol_feature, :sampling, session: self, uplevel: 2) warn_unassociated_request(__method__, related_request_id) params = @server.build_sampling_params(client_capabilities, **kwargs) @@ -223,6 +227,8 @@ def notify_progress(progress_token:, progress:, total: nil, message: nil, relate # is deprecated as of MCP protocol version 2026-07-28 (SEP-2577). # Use stderr or OpenTelemetry instead. def notify_log_message(data:, level:, logger: nil, related_request_id: nil) + @server.send(:warn_if_deprecated_protocol_feature, :logging, session: self, uplevel: 2) + effective_logging = @logging_message_notification || @server.logging_message_notification return unless effective_logging&.should_notify?(level) diff --git a/test/mcp/client/http_test.rb b/test/mcp/client/http_test.rb index 85f93faa..3fdb5275 100644 --- a/test/mcp/client/http_test.rb +++ b/test/mcp/client/http_test.rb @@ -11,6 +11,8 @@ module MCP class Client class HTTPTest < Minitest::Test + include DeprecationWarningTestHelper + def test_raises_load_error_when_faraday_not_available client = HTTP.new(url: url) @@ -1705,6 +1707,44 @@ def test_connect_accepts_custom_parameters assert_requested(notification_stub) end + def test_connect_warns_for_deprecated_capabilities_when_negotiated_protocol_version_is_2026_07_28 + notification_stub = stub_notification + + init_stub = stub_request(:post, url) + .with { |req| JSON.parse(req.body)["method"] == "initialize" } + .to_return( + status: 200, + headers: { "Content-Type" => "application/json" }, + body: { result: { protocolVersion: "2026-07-28" } }.to_json, + ) + + assert_deprecation_warning(/MCP Roots .*2026-07-28.*MCP Sampling .*2026-07-28/m) do + client.connect(capabilities: { roots: { listChanged: true }, sampling: {} }) + end + + assert_requested(init_stub) + assert_requested(notification_stub) + end + + def test_connect_does_not_warn_for_deprecated_capabilities_when_negotiated_protocol_version_is_older + notification_stub = stub_notification + + init_stub = stub_request(:post, url) + .with { |req| JSON.parse(req.body)["method"] == "initialize" } + .to_return( + status: 200, + headers: { "Content-Type" => "application/json" }, + body: { result: { protocolVersion: "2025-11-25" } }.to_json, + ) + + assert_no_deprecation_warning do + client.connect(capabilities: { roots: { listChanged: true }, sampling: {} }) + end + + assert_requested(init_stub) + assert_requested(notification_stub) + end + def test_connect_is_idempotent init_stub = stub_initialize notification_stub = stub_notification diff --git a/test/mcp/client/stdio_test.rb b/test/mcp/client/stdio_test.rb index 1995222b..158fa934 100644 --- a/test/mcp/client/stdio_test.rb +++ b/test/mcp/client/stdio_test.rb @@ -9,6 +9,8 @@ module MCP class Client class StdioTest < Minitest::Test + include DeprecationWarningTestHelper + def test_send_request_raises_when_connect_not_called Open3.expects(:popen3).never @@ -816,6 +818,38 @@ def test_connect_accepts_custom_parameters stdout_write.close end + def test_connect_warns_for_deprecated_capabilities_when_negotiated_protocol_version_is_2026_07_28 + stdin_read, stdin_write = IO.pipe + stdout_read, stdout_write = IO.pipe + stderr_read, _ = IO.pipe + + Open3.stubs(:popen3).returns([stdin_write, stdout_read, stderr_read, mock_wait_thread]) + + transport = Stdio.new(command: "ruby", args: ["server.rb"]) + + server_thread = Thread.new do + init_line = stdin_read.gets + init_request = JSON.parse(init_line) + stdout_write.puts(JSON.generate( + jsonrpc: "2.0", + id: init_request["id"], + result: { protocolVersion: "2026-07-28" }, + )) + stdout_write.flush + stdin_read.gets + end + + assert_deprecation_warning(/MCP Roots .*2026-07-28.*MCP Sampling .*2026-07-28/m) do + transport.connect(capabilities: { roots: { listChanged: true }, sampling: {} }) + end + ensure + server_thread.join + stdin_read.close + stdin_write.close + stdout_read.close + stdout_write.close + end + def test_connect_raises_on_jsonrpc_error_response stdin_read, stdin_write = IO.pipe stdout_read, stdout_write = IO.pipe diff --git a/test/mcp/configuration_test.rb b/test/mcp/configuration_test.rb index 8270b101..92eaf053 100644 --- a/test/mcp/configuration_test.rb +++ b/test/mcp/configuration_test.rb @@ -54,7 +54,7 @@ class ConfigurationTest < ActiveSupport::TestCase Configuration.new(protocol_version: "DRAFT-2025-v3") end - assert_equal("protocol_version must be 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message) + assert_equal("protocol_version must be 2026-07-28, 2025-11-25, 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 @@ -63,7 +63,7 @@ class ConfigurationTest < ActiveSupport::TestCase custom_version = "2025-03-27" config.protocol_version = custom_version end - assert_equal("protocol_version must be 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message) + assert_equal("protocol_version must be 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message) end test "raises ArgumentError when protocol_version is not a boolean value" do diff --git a/test/mcp/server_notification_test.rb b/test/mcp/server_notification_test.rb index 74a89cc9..8e6accde 100644 --- a/test/mcp/server_notification_test.rb +++ b/test/mcp/server_notification_test.rb @@ -5,6 +5,7 @@ module MCP class ServerNotificationTest < ActiveSupport::TestCase include InstrumentationTestHelper + include DeprecationWarningTestHelper class MockTransport < Transport attr_reader :notifications @@ -74,6 +75,91 @@ def handle_request(request); end assert_equal({ "data" => { error: "Connection Failed" }, "level" => "error" }, @mock_transport.notifications.first[:params]) end + test "#notify_log_message warns when configured protocol version is 2026-07-28" do + server = Server.new( + name: "test_server", + version: "1.0.0", + configuration: Configuration.new(protocol_version: "2026-07-28"), + ) + mock_transport = MockTransport.new(server) + server.logging_message_notification = MCP::LoggingMessageNotification.new(level: "error") + + assert_deprecation_warning(/MCP Logging .*2026-07-28/) do + server.notify_log_message(data: { error: "Connection Failed" }, level: "error") + end + + assert_equal Methods::NOTIFICATIONS_MESSAGE, mock_transport.notifications.first[:method] + end + + test "#notify_log_message warns when configured protocol version is 2026-07-28 without transport" do + server = Server.new( + name: "test_server", + version: "1.0.0", + configuration: Configuration.new(protocol_version: "2026-07-28"), + ) + server.logging_message_notification = MCP::LoggingMessageNotification.new(level: "error") + + assert_deprecation_warning(/MCP Logging .*2026-07-28/) do + server.notify_log_message(data: { error: "Connection Failed" }, level: "error") + end + end + + test "#notify_log_message warns when configured protocol version is 2026-07-28 below configured level" do + server = Server.new( + name: "test_server", + version: "1.0.0", + configuration: Configuration.new(protocol_version: "2026-07-28"), + ) + mock_transport = MockTransport.new(server) + server.logging_message_notification = MCP::LoggingMessageNotification.new(level: "error") + + assert_deprecation_warning(/MCP Logging .*2026-07-28/) do + server.notify_log_message(data: { message: "test" }, level: "info") + end + + assert_empty mock_transport.notifications + end + + test "#notify_log_message does not warn when configured protocol version is older" do + server = Server.new( + name: "test_server", + version: "1.0.0", + configuration: Configuration.new(protocol_version: "2025-11-25"), + ) + MockTransport.new(server) + server.logging_message_notification = MCP::LoggingMessageNotification.new(level: "error") + + assert_no_deprecation_warning do + server.notify_log_message(data: { error: "Connection Failed" }, level: "error") + end + end + + test "ServerSession#notify_log_message warns when negotiated protocol version is 2026-07-28 below configured level" do + server = Server.new(name: "test_server", version: "1.0.0") + mock_transport = MockTransport.new(server) + session = ServerSession.new(server: server, transport: mock_transport) + server.handle( + { + jsonrpc: "2.0", + method: "initialize", + id: 1, + params: { + protocolVersion: "2026-07-28", + capabilities: {}, + clientInfo: { name: "test-client", version: "1.0" }, + }, + }, + session: session, + ) + session.configure_logging(MCP::LoggingMessageNotification.new(level: "error")) + + assert_deprecation_warning(/MCP Logging .*2026-07-28/) do + session.notify_log_message(data: { message: "test" }, level: "info") + end + + assert_empty mock_transport.notifications + end + test "#notify_log_message sends notification with logger through transport" do @server.logging_message_notification = MCP::LoggingMessageNotification.new(level: "error") @server.notify_log_message(data: { error: "Connection Failed" }, level: "error", logger: "DatabaseLogger") diff --git a/test/mcp/server_roots_test.rb b/test/mcp/server_roots_test.rb index f8f31f9b..283fbdda 100644 --- a/test/mcp/server_roots_test.rb +++ b/test/mcp/server_roots_test.rb @@ -4,6 +4,8 @@ module MCP class ServerRootsTest < ActiveSupport::TestCase + include DeprecationWarningTestHelper + class MockTransport < Transport attr_reader :requests @@ -62,6 +64,36 @@ def close; end assert callback_called end + test "notifications/roots/list_changed warns when negotiated protocol version is 2026-07-28" do + server = Server.new(name: "test", version: "1.0") + server.handle({ + jsonrpc: "2.0", + method: "initialize", + id: 1, + params: { + protocolVersion: "2026-07-28", + capabilities: { roots: { listChanged: true } }, + clientInfo: { name: "test-client", version: "1.0" }, + }, + }) + + assert_deprecation_warning(/MCP Roots .*2026-07-28/) do + server.handle({ + jsonrpc: "2.0", + method: "notifications/roots/list_changed", + }) + end + end + + test "notifications/roots/list_changed does not warn when negotiated protocol version is older" do + assert_no_deprecation_warning do + @server.handle({ + jsonrpc: "2.0", + method: "notifications/roots/list_changed", + }) + end + end + test "notifications/roots/list_changed is handled as no-op by default" do result = @server.handle({ jsonrpc: "2.0", @@ -144,6 +176,21 @@ def close; end assert_silent { session.list_roots(related_request_id: "req-1") } end + test "ServerSession#list_roots warns when negotiated protocol version is 2026-07-28 and client lacks roots" do + session = ServerSession.new(server: @server, transport: @mock_transport) + session.store_client_info(client: { name: "test-client" }, capabilities: {}) + session.mark_initialized!(protocol_version: "2026-07-28") + + error = nil + assert_deprecation_warning(/MCP Roots .*2026-07-28/) do + error = assert_raises(RuntimeError) do + session.list_roots(related_request_id: "req-1") + end + end + + assert_equal("Client does not support roots.", error.message) + end + test "ServerSession#client_capabilities falls back to server global capabilities" do transport = MCP::Server::Transports::StreamableHTTPTransport.new(@server) @@ -219,5 +266,31 @@ def close; end end assert_equal("No active stream for roots/list request.", error.message) end + + test "ServerSession#list_roots warns when session negotiated protocol version is 2026-07-28" do + server = Server.new(name: "test", version: "1.0") + transport = MockTransport.new(server) + + session = ServerSession.new(server: server, transport: transport, session_id: "s1") + server.handle( + { + jsonrpc: "2.0", + method: "initialize", + id: 1, + params: { + protocolVersion: "2026-07-28", + capabilities: { roots: {} }, + clientInfo: { name: "http-client", version: "1.0" }, + }, + }, + session: session, + ) + + assert_deprecation_warning(/MCP Roots .*2026-07-28/) do + session.list_roots(related_request_id: "req-1") + end + + assert_equal Methods::ROOTS_LIST, transport.requests.first[:method] + end end end diff --git a/test/mcp/server_sampling_test.rb b/test/mcp/server_sampling_test.rb index 9c6985b3..b2c84f57 100644 --- a/test/mcp/server_sampling_test.rb +++ b/test/mcp/server_sampling_test.rb @@ -5,6 +5,7 @@ module MCP class ServerSamplingTest < ActiveSupport::TestCase include InstrumentationTestHelper + include DeprecationWarningTestHelper class MockTransport < Transport attr_reader :requests @@ -63,6 +64,30 @@ def close; end assert_equal "Response from LLM", result[:content][:text] end + test "create_sampling_message warns when session negotiated protocol version is 2026-07-28" do + @session.mark_initialized!(protocol_version: "2026-07-28") + + assert_deprecation_warning(/MCP Sampling .*2026-07-28/) do + @session.create_sampling_message( + related_request_id: "req-1", + messages: [{ role: "user", content: { type: "text", text: "Hello" } }], + max_tokens: 100, + ) + end + end + + test "create_sampling_message does not warn when session negotiated protocol version is older" do + @session.mark_initialized!(protocol_version: "2025-11-25") + + assert_no_deprecation_warning do + @session.create_sampling_message( + related_request_id: "req-1", + messages: [{ role: "user", content: { type: "text", text: "Hello" } }], + max_tokens: 100, + ) + end + end + test "create_sampling_message sends all optional params" do @session.create_sampling_message( related_request_id: "req-1", @@ -101,6 +126,24 @@ def close; end assert_equal("Client does not support sampling.", error.message) end + test "create_sampling_message warns when negotiated protocol version is 2026-07-28 and client lacks sampling" do + @session.store_client_info(client: { name: "test-client" }, capabilities: {}) + @session.mark_initialized!(protocol_version: "2026-07-28") + + error = nil + assert_deprecation_warning(/MCP Sampling .*2026-07-28/) do + error = assert_raises(RuntimeError) do + @session.create_sampling_message( + related_request_id: "req-1", + messages: [{ role: "user", content: { type: "text", text: "Hello" } }], + max_tokens: 100, + ) + end + end + + assert_equal("Client does not support sampling.", error.message) + end + test "create_sampling_message raises error when tools used but client lacks sampling.tools" do error = assert_raises(RuntimeError) do @session.create_sampling_message( diff --git a/test/mcp/server_test.rb b/test/mcp/server_test.rb index 18240b0e..699d6fb1 100644 --- a/test/mcp/server_test.rb +++ b/test/mcp/server_test.rb @@ -6,6 +6,7 @@ module MCP class ServerTest < ActiveSupport::TestCase include InstrumentationTestHelper include InitializeParamsTestHelper + include DeprecationWarningTestHelper setup do @tool = Tool.define( name: "test_tool", @@ -1178,6 +1179,67 @@ def read_resource_request(uri) refute response.key?(:error) end + test "#configure_logging_level warns when negotiated protocol version is 2026-07-28" do + server = Server.new(tools: [TestTool]) + server.handle( + { + jsonrpc: "2.0", + method: "initialize", + id: 1, + params: { + protocolVersion: "2026-07-28", + capabilities: {}, + clientInfo: { name: "test-client", version: "1.0" }, + }, + }, + ) + + response = nil + assert_deprecation_warning(/MCP Logging .*2026-07-28/) do + response = server.handle( + { + jsonrpc: "2.0", + id: 2, + method: "logging/setLevel", + params: { + level: "info", + }, + }, + ) + end + + assert_empty response[:result] + end + + test "#configure_logging_level does not warn when negotiated protocol version is older" do + server = Server.new(tools: [TestTool]) + server.handle( + { + jsonrpc: "2.0", + method: "initialize", + id: 1, + params: { + protocolVersion: "2025-11-25", + capabilities: {}, + clientInfo: { name: "test-client", version: "1.0" }, + }, + }, + ) + + assert_no_deprecation_warning do + server.handle( + { + jsonrpc: "2.0", + id: 2, + method: "logging/setLevel", + params: { + level: "info", + }, + }, + ) + end + end + test "#configure_logging_level returns an error object when invalid log level is provided" do server = Server.new( tools: [TestTool], @@ -1229,6 +1291,35 @@ def read_resource_request(uri) assert_includes response[:error][:data], "Server does not support logging" end + test "#configure_logging_level warns when configured protocol version is 2026-07-28 and server lacks logging capability" do + server = Server.new( + tools: [TestTool], + configuration: Configuration.new(protocol_version: "2026-07-28"), + capabilities: { + tools: { listChanged: true }, + prompts: { listChanged: true }, + resources: { listChanged: true }, + }, + ) + + response = nil + assert_deprecation_warning(/MCP Logging .*2026-07-28/) do + response = server.handle( + { + jsonrpc: "2.0", + id: 1, + method: "logging/setLevel", + params: { + level: "debug", + }, + }, + ) + end + + assert_equal(-32603, response[:error][:code]) + assert_includes response[:error][:data], "Server does not support logging" + end + test "#handle method with missing required top-level capability returns an error" do @server.capabilities = {} diff --git a/test/test_helper.rb b/test/test_helper.rb index 4de7ffdc..8cd6b9e7 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -23,3 +23,21 @@ def Hash.ruby2_keywords_hash?(_hash) require_relative "instrumentation_test_helper" require_relative "initialize_params_test_helper" + +module DeprecationWarningTestHelper + def assert_deprecation_warning(message_pattern, &block) + original_verbose = $VERBOSE + $VERBOSE = false + assert_output(nil, message_pattern, &block) + ensure + $VERBOSE = original_verbose + end + + def assert_no_deprecation_warning(&block) + original_verbose = $VERBOSE + $VERBOSE = false + assert_output(nil, "", &block) + ensure + $VERBOSE = original_verbose + end +end