From a7bd96705a0313781977ff2ab53df5f4c3c124b9 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Fri, 23 Oct 2026 23:08:13 +0700 Subject: [PATCH] Report failed session notifications through the server configuration ## Motivation and Context `ServerSession#notify_progress`, `#notify_log_message`, `#notify_resources_updated` and `#notify_elicitation_complete` rescue a transport failure and pass it to `@server.report_exception`, which is private, so the rescue itself raised `NoMethodError`. The failure meant to be reported and swallowed reached the caller instead, typically a tool reporting progress or logging, whose call then answered Internal error. The rescues now call `exception_reporter` of the server's configuration, which is what `Server#report_exception` does, without making that method public. ## How Has This Been Tested? `test/mcp/server_notification_test.rb`. Against the previous library the new test fails with `NoMethodError: private method 'report_exception' called for an instance of MCP::Server`. ## Breaking Changes None. --- lib/mcp/server_session.rb | 8 ++++---- test/mcp/server_notification_test.rb | 29 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/mcp/server_session.rb b/lib/mcp/server_session.rb index 042b0dc4..2ced2309 100644 --- a/lib/mcp/server_session.rb +++ b/lib/mcp/server_session.rb @@ -275,14 +275,14 @@ def send_peer_cancellation(nested_request_id:, related_request_id: nil, reason: def notify_elicitation_complete(elicitation_id:) send_to_transport(Methods::NOTIFICATIONS_ELICITATION_COMPLETE, { elicitationId: elicitation_id }) rescue => e - @server.report_exception(e, notification: "elicitation_complete") + @server.configuration.exception_reporter.call(e, { notification: "elicitation_complete" }) end # Sends a resource updated notification to this session only. def notify_resources_updated(uri:) send_to_transport(Methods::NOTIFICATIONS_RESOURCES_UPDATED, { "uri" => uri }) rescue => e - @server.report_exception(e, notification: "resources_updated") + @server.configuration.exception_reporter.call(e, { notification: "resources_updated" }) end # Sends a progress notification to this session only. @@ -296,7 +296,7 @@ def notify_progress(progress_token:, progress:, total: nil, message: nil, relate send_to_transport(Methods::NOTIFICATIONS_PROGRESS, params, related_request_id: related_request_id) rescue => e - @server.report_exception(e, notification: "progress") + @server.configuration.exception_reporter.call(e, { notification: "progress" }) end # Sends a log message notification to this session only. @@ -320,7 +320,7 @@ def notify_log_message(data:, level:, logger: nil, related_request_id: nil) send_to_transport(Methods::NOTIFICATIONS_MESSAGE, params, related_request_id: related_request_id) rescue => e - @server.report_exception(e, { notification: "log_message" }) + @server.configuration.exception_reporter.call(e, { notification: "log_message" }) end private diff --git a/test/mcp/server_notification_test.rb b/test/mcp/server_notification_test.rb index ffeb3a8e..c1f32fcf 100644 --- a/test/mcp/server_notification_test.rb +++ b/test/mcp/server_notification_test.rb @@ -257,6 +257,35 @@ def send_notification(method, params = nil) assert_equal 4, call_count end + test "ServerSession notification methods report transport errors through the server's exception reporter" do + transport = Class.new(MockTransport) do + def send_notification(method, params = nil) + raise StandardError, "Transport error" + end + end.new(@server) + session = ServerSession.new(server: @server, transport: transport) + session.configure_logging(MCP::LoggingMessageNotification.new(level: "error")) + reported = [] + @server.configuration.exception_reporter = ->(exception, context) { reported << [exception.message, context] } + + assert_nothing_raised do + session.notify_elicitation_complete(elicitation_id: "elicitation-1") + session.notify_resources_updated(uri: "file:///a.txt") + session.notify_progress(progress_token: "token-1", progress: 1) + session.notify_log_message(data: "message", level: "error") + end + + assert_equal( + [ + ["Transport error", { notification: "elicitation_complete" }], + ["Transport error", { notification: "resources_updated" }], + ["Transport error", { notification: "progress" }], + ["Transport error", { notification: "log_message" }], + ], + reported, + ) + end + test "multiple notification methods can be called in sequence" do @server.notify_tools_list_changed @server.notify_prompts_list_changed