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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` (#390)

## [0.20.0] - 2026-06-14

### Added
Expand Down
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 @@ -156,6 +157,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 @@ -110,6 +111,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
7 changes: 6 additions & 1 deletion lib/mcp/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26"

Expand Down
50 changes: 50 additions & 0 deletions lib/mcp/protocol_deprecations.rb
Original file line number Diff line number Diff line change
@@ -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
39 changes: 38 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 @@ -140,6 +141,7 @@ def initialize(
self.page_size = page_size
@configuration = MCP.configuration.merge(configuration)
@client = nil
@client_protocol_version = nil

validate!

Expand Down Expand Up @@ -256,7 +258,12 @@ def notify_resources_list_changed
report_exception(e, { notification: "resources_list_changed" })
end

# @deprecated MCP Logging (`logging/setLevel` and `notifications/message`)
# 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 @@ -272,7 +279,13 @@ def notify_log_message(data:, level:, logger: nil)
# Called when a client notifies the server that its filesystem roots have changed.
#
# @yield [params] The notification params (typically `nil`).
# @deprecated MCP Roots (`roots/list` and
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
# 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 @@ -430,6 +443,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 @@ -568,7 +588,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 All @@ -594,6 +618,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: 10 additions & 0 deletions lib/mcp/server_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def report_progress(progress, total: nil, message: nil)
# @param data [Object] The log data to send.
# @param level [String] Log level (e.g., `"debug"`, `"info"`, `"error"`).
# @param logger [String, nil] Logger name.
# @deprecated MCP Logging (`logging/setLevel` and `notifications/message`)
# 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)
return unless @notification_target

Expand All @@ -51,6 +54,10 @@ def notify_resources_updated(uri:)
end

# Delegates to the session so the request is scoped to the originating client.
# @deprecated MCP Roots (`roots/list` and
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
# version 2026-07-28 (SEP-2577). Use tool parameters, resource URIs,
# server configuration, or environment variables instead.
def list_roots
if @notification_target.respond_to?(:list_roots)
@notification_target.list_roots(related_request_id: @related_request_id)
Expand Down Expand Up @@ -84,6 +91,9 @@ def ping
# Delegates to the session so the request is scoped to the originating client.
# Falls back to `@context` (via `method_missing`) when `@notification_target`
# does not support sampling.
# @deprecated MCP Sampling (`sampling/createMessage`) is deprecated as of
# MCP protocol version 2026-07-28 (SEP-2577). Use direct LLM provider
# APIs instead.
def create_sampling_message(**kwargs)
if @notification_target.respond_to?(:create_sampling_message)
@notification_target.create_sampling_message(**kwargs, related_request_id: @related_request_id)
Expand Down
22 changes: 20 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 @@ -98,7 +100,13 @@ def client_capabilities
end

# Sends a `roots/list` request scoped to this session.
# @deprecated MCP Roots (`roots/list` and
# `notifications/roots/list_changed`) is deprecated as of MCP protocol
# 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)

unless client_capabilities&.dig(:roots)
raise "Client does not support roots."
end
Expand All @@ -115,7 +123,12 @@ def ping(related_request_id: nil)
end

# Sends a `sampling/createMessage` request scoped to this session.
# @deprecated MCP Sampling (`sampling/createMessage`) is deprecated as of
# 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)

params = @server.build_sampling_params(client_capabilities, **kwargs)
send_to_transport_request(Methods::SAMPLING_CREATE_MESSAGE, params, related_request_id: related_request_id)
end
Expand Down Expand Up @@ -188,7 +201,12 @@ def notify_progress(progress_token:, progress:, total: nil, message: nil, relate
end

# Sends a log message notification to this session only.
# @deprecated MCP Logging (`logging/setLevel` and `notifications/message`)
# 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 @@ -801,6 +803,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
Loading