Skip to content

logging/setLevel from one session changes the log level of other sessions #658

Open

Description

@atesgoral

Server#configure_logging_level sets the level on the calling session, then always sets the server-wide @logging_message_notification too (server.rb:1386).

Legacy sessions that did not set their own level fall back to that server-wide value (server_session.rb:310-314), and Server#notify_log_message uses it for broadcasts (server.rb:448-450). So when one client calls logging/setLevel, every other legacy session without its own level starts to get notifications/message at that level. This includes sessions created later.

The spec treats the log level as a preference of the connection that sets it. The modern lifecycle is not affected, because it uses the per-request logLevel from the envelope.

Repro

Run from the repository root with bundle exec ruby -I lib repro.rb:

require "mcp"

class RecordingTransport
  attr_reader :log_messages

  def initialize
    @log_messages = []
  end

  def send_notification(method, params = nil, session_id: nil, **)
    @log_messages << [session_id, params["data"]] if method == "notifications/message"
    true
  end
end

log_tool = MCP::Tool.define(name: "log_tool") do |server_context:|
  server_context.notify_log_message(data: "debug detail", level: "debug")
  MCP::Tool::Response.new([{ type: "text", text: "ok" }])
end
transport = RecordingTransport.new
server = MCP::Server.new(name: "example", tools: [log_tool])
server.transport = transport

initialize_request = {
  jsonrpc: "2.0", id: 1, method: "initialize",
  params: { protocolVersion: "2025-11-25", capabilities: {}, clientInfo: { name: "client", version: "1" } },
}
session_a = MCP::ServerSession.new(server: server, transport: transport, session_id: "A")
session_b = MCP::ServerSession.new(server: server, transport: transport, session_id: "B")
session_a.handle(initialize_request)
session_b.handle(initialize_request)

session_a.handle({ jsonrpc: "2.0", id: 2, method: "logging/setLevel", params: { level: "debug" } })
session_b.handle({ jsonrpc: "2.0", id: 3, method: "tools/call", params: { name: "log_tool", arguments: {} } })

p transport.log_messages

Expected: []. Session B never called logging/setLevel.

Actual: [["B", "debug detail"]]

Suggested fix

Set the server-wide value only on the session-less path (session is nil). With a session, change only that session's level. Applications can still set a server-wide default with Server#logging_message_notification=.

Activity

  1. added
    bugSomething isn't working
    on Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions