Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/mcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions lib/mcp/client/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require_relative "../../json_rpc_handler"
require_relative "../configuration"
require_relative "../methods"
require_relative "../protocol_deprecations"
require_relative "../version"

module MCP
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions lib/mcp/client/stdio.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require_relative "../../json_rpc_handler"
require_relative "../configuration"
require_relative "../methods"
require_relative "../protocol_deprecations"
require_relative "../version"

module MCP
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions lib/mcp/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions lib/mcp/protocol_deprecations.rb
Original file line number Diff line number Diff line change
@@ -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
32 changes: 31 additions & 1 deletion lib/mcp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -182,6 +183,7 @@ def initialize(
self.cache_scope = cache_scope
@configuration = MCP.configuration.merge(configuration)
@client = nil
@client_protocol_version = nil

validate!

Expand Down Expand Up @@ -352,6 +354,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)

Expand All @@ -372,6 +376,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

Expand Down Expand Up @@ -536,6 +542,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
Expand Down Expand Up @@ -675,7 +688,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,
Expand Down Expand Up @@ -742,6 +759,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)

Expand Down
10 changes: 8 additions & 2 deletions lib/mcp/server_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
40 changes: 40 additions & 0 deletions test/mcp/client/http_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions test/mcp/client/stdio_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions test/mcp/protocol_deprecations_test.rb
Original file line number Diff line number Diff line change
@@ -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
Loading