feat: deprecate roots sampling and logging
Warn when Roots, Sampling, or Logging APIs are used with MCP protocol version 2026-07-28 or newer. Add a shared protocol deprecation helper, YARD deprecation notes, and regression tests covering warnings at and after the deprecation threshold and no warnings for older or never-established protocol versions. Closes https://github.com/modelcontextprotocol/ruby-sdk/issues/390 Co-authored-by: Qlaw <noreply@qlaw.quick.shopify.io>
This commit is contained in:
parent
f4d939d349
commit
6a43d3d6fb
15 changed files with 508 additions and 3 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require "securerandom"
|
|||
require_relative "../../json_rpc_handler"
|
||||
require_relative "../configuration"
|
||||
require_relative "../methods"
|
||||
require_relative "../protocol_deprecations"
|
||||
require_relative "../version"
|
||||
|
||||
module MCP
|
||||
|
|
@ -334,6 +335,8 @@ module MCP
|
|||
)
|
||||
end
|
||||
|
||||
MCP::ProtocolDeprecations.warn_for_client_capabilities(capabilities, protocol_version: negotiated_protocol_version, uplevel: 1)
|
||||
|
||||
begin
|
||||
send_request(request: {
|
||||
jsonrpc: JsonRpcHandler::Version::V2_0,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ require "timeout"
|
|||
require_relative "../../json_rpc_handler"
|
||||
require_relative "../configuration"
|
||||
require_relative "../methods"
|
||||
require_relative "../protocol_deprecations"
|
||||
require_relative "../version"
|
||||
|
||||
module MCP
|
||||
|
|
@ -128,6 +129,8 @@ module MCP
|
|||
)
|
||||
end
|
||||
|
||||
MCP::ProtocolDeprecations.warn_for_client_capabilities(capabilities, protocol_version: negotiated_protocol_version, uplevel: 1)
|
||||
|
||||
begin
|
||||
notification = {
|
||||
jsonrpc: JsonRpcHandler::Version::V2_0,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
module MCP
|
||||
class Configuration
|
||||
LATEST_STABLE_PROTOCOL_VERSION = "2026-07-28"
|
||||
ROOTS_SAMPLING_LOGGING_DEPRECATED_PROTOCOL_VERSION = "2026-07-28"
|
||||
SUPPORTED_STABLE_PROTOCOL_VERSIONS = [
|
||||
LATEST_STABLE_PROTOCOL_VERSION, "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05",
|
||||
].freeze
|
||||
|
|
|
|||
52
lib/mcp/protocol_deprecations.rb
Normal file
52
lib/mcp/protocol_deprecations.rb
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# 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)
|
||||
return false unless 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
|
||||
|
|
@ -7,6 +7,7 @@ require_relative "instrumentation"
|
|||
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"
|
||||
|
|
@ -184,6 +185,7 @@ module MCP
|
|||
self.cache_scope = cache_scope
|
||||
@configuration = MCP.configuration.merge(configuration)
|
||||
@client = nil
|
||||
@client_protocol_version = nil
|
||||
|
||||
validate!
|
||||
|
||||
|
|
@ -354,6 +356,8 @@ module MCP
|
|||
# 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)
|
||||
|
||||
|
|
@ -374,6 +378,8 @@ module MCP
|
|||
# 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
|
||||
|
||||
|
|
@ -538,6 +544,13 @@ module MCP
|
|||
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
|
||||
|
|
@ -706,7 +719,11 @@ module MCP
|
|||
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,
|
||||
|
|
@ -773,6 +790,19 @@ module MCP
|
|||
{}
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ module MCP
|
|||
class ServerSession
|
||||
ERAS = [:legacy, :modern].freeze
|
||||
|
||||
attr_reader :session_id, :client, :logging_message_notification
|
||||
attr_reader :session_id, :client, :logging_message_notification, :protocol_version
|
||||
|
||||
# Connection-era lock of the dual-era serving model (SEP-2575): `nil` until the first era-distinctive message succeeds,
|
||||
# then `:legacy` or `:modern` for the connection's lifetime. Modern-era transports construct their per-request sessions
|
||||
|
|
@ -26,6 +26,7 @@ module MCP
|
|||
@client = nil
|
||||
@client_capabilities = nil
|
||||
@logging_message_notification = nil
|
||||
@protocol_version = nil
|
||||
@in_flight = {}
|
||||
@in_flight_mutex = Mutex.new
|
||||
@initialized = false
|
||||
|
|
@ -39,8 +40,9 @@ module MCP
|
|||
# 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
|
||||
# A successful `initialize` is the legacy-distinctive message of the dual-era serving model (SEP-2575),
|
||||
# so it also locks the connection era.
|
||||
@era ||= :legacy
|
||||
|
|
@ -130,6 +132,7 @@ module MCP
|
|||
# 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)
|
||||
|
|
@ -156,6 +159,7 @@ module MCP
|
|||
# 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)
|
||||
|
|
@ -246,6 +250,8 @@ module MCP
|
|||
# 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ require "mcp/client"
|
|||
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 @@ module MCP
|
|||
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
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ require "mcp/client/tool"
|
|||
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 @@ module MCP
|
|||
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
|
||||
|
|
|
|||
14
test/mcp/protocol_deprecations_test.rb
Normal file
14
test/mcp/protocol_deprecations_test.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
module MCP
|
||||
class ProtocolDeprecationsTest < ActiveSupport::TestCase
|
||||
test "roots, sampling, and logging are deprecated in 2026-07-28 and later" do
|
||||
refute ProtocolDeprecations.deprecated_roots_sampling_logging?(nil)
|
||||
refute ProtocolDeprecations.deprecated_roots_sampling_logging?("2025-11-25")
|
||||
assert ProtocolDeprecations.deprecated_roots_sampling_logging?("2026-07-28")
|
||||
assert ProtocolDeprecations.deprecated_roots_sampling_logging?("2027-01-01")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,6 +5,7 @@ require "test_helper"
|
|||
module MCP
|
||||
class ServerNotificationTest < ActiveSupport::TestCase
|
||||
include InstrumentationTestHelper
|
||||
include DeprecationWarningTestHelper
|
||||
|
||||
class MockTransport < Transport
|
||||
attr_reader :notifications
|
||||
|
|
@ -74,6 +75,101 @@ module MCP
|
|||
assert_equal({ "data" => { error: "Connection Failed" }, "level" => "error" }, @mock_transport.notifications.first[:params])
|
||||
end
|
||||
|
||||
test "#notify_log_message does not warn when no protocol version has been established" do
|
||||
@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
|
||||
|
||||
assert_equal Methods::NOTIFICATIONS_MESSAGE, @mock_transport.notifications.first[:method]
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ require "test_helper"
|
|||
|
||||
module MCP
|
||||
class ServerRootsTest < ActiveSupport::TestCase
|
||||
include DeprecationWarningTestHelper
|
||||
|
||||
class MockTransport < Transport
|
||||
attr_reader :requests
|
||||
|
||||
|
|
@ -62,6 +64,36 @@ module MCP
|
|||
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 @@ module MCP
|
|||
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 @@ module MCP
|
|||
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
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ require "test_helper"
|
|||
module MCP
|
||||
class ServerSamplingTest < ActiveSupport::TestCase
|
||||
include InstrumentationTestHelper
|
||||
include DeprecationWarningTestHelper
|
||||
|
||||
class MockTransport < Transport
|
||||
attr_reader :requests
|
||||
|
|
@ -63,6 +64,30 @@ module MCP
|
|||
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 @@ module MCP
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module MCP
|
|||
class ServerTest < ActiveSupport::TestCase
|
||||
include InstrumentationTestHelper
|
||||
include InitializeParamsTestHelper
|
||||
include DeprecationWarningTestHelper
|
||||
setup do
|
||||
@tool = Tool.define(
|
||||
name: "test_tool",
|
||||
|
|
@ -1378,6 +1379,67 @@ module MCP
|
|||
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],
|
||||
|
|
@ -1429,6 +1491,35 @@ module MCP
|
|||
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 = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,3 +23,21 @@ require "sorbet-runtime" if RUBY_VERSION >= "3.0"
|
|||
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue