Skip to content

fix(server): honor logging/setLevel when sendLoggingMessage omits sessionId - #2863

Open
2sumtech wants to merge 1 commit into
modelcontextprotocol:mainfrom
2sumtech:fix/logging-level-default-session
Open

2sumtech wants to merge 1 commit into
modelcontextprotocol:mainfrom
2sumtech:fix/logging-level-default-session

Conversation

@2sumtech

Copy link
Copy Markdown

Server.sendLoggingMessage(params) (and McpServer.sendLoggingMessage) now honors the level the client set with logging/setLevel on Streamable HTTP and SSE, as it already did on stdio.

Motivation and Context

The built-in logging/setLevel handler stores the client's level under the transport's session id (ctx.sessionId). sendLoggingMessage looks the level up under its own sessionId argument. That argument is optional, and the documented example (McpServer_sendLoggingMessage_basic) leaves it out. So:

  • stdio / in-memory: the transport has no session id, so the handler stores under undefined and the lookup of undefined finds it. The filter works.
  • Streamable HTTP / SSE: the handler stores under '<session-id>' and the lookup of undefined misses. isMessageIgnored then returns false, and every message is sent whatever level the client asked for.

On HTTP the client's setLevel has no effect, and nothing reports it. ctx.mcpReq.log() does not have this problem because it reads ctx.sessionId. That leaves two logging APIs on the same server that disagree.

Spec (2025-11-25): the SetLevelRequest level is "The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher" (schema.ts L1509). The logging page shows the same thing in its message flow: after logging/setLevel (error) the server "Only sends error level and above".

The fix is one line in isMessageIgnored. When no sessionId is passed, it uses the connected transport's session id, which is the key the handler wrote. A Server is connected to one transport, so there is exactly one such id. An explicit sessionId works as before, and sessionless transports still look up undefined, so nothing changes for them. This finishes what #917 started, which made the lookup work for sessionless (stdio) servers.

How Has This Been Tested?

I added a regression test to test/integration/test/server.test.ts, next to the existing "should respect log level for transport with sessionId" test. It uses the same session-bearing in-memory pair and calls sendLoggingMessage(params) without a sessionId, the way the JSDoc example does. It fails on main (the debug message gets through) and passes with the fix.

I also ran a temporary scenario through the e2e harness (wire() across every transport), not committed, to reproduce this over real transports. The client sets error, then the server calls sendLoggingMessage({level:'debug'}) and sendLoggingMessage({level:'error'}).

Evidence

Temporary e2e-harness scenario, before the fix:

inMemory received=["error"]
stdio received=["error"]
streamableHttp received=["debug","error"]
sse received=["debug","error"]

After the fix:

inMemory received=["error"]
stdio received=["error"]
streamableHttp received=["error"]
sse received=["error"]

Committed test, before the fix (with server.ts from origin/main):

× should respect log level for transport with sessionId when sendLoggingMessage omits sessionId
  Tests  1 failed | 57 passed (58)

After the fix:

pnpm --filter @modelcontextprotocol/test-integration test   Test Files 19 passed (19)  Tests 373 passed (373)
pnpm --filter @modelcontextprotocol/server test             Test Files 45 passed (45)  Tests 519 passed (519)
pnpm --filter @modelcontextprotocol/client test             Test Files 38 passed (38)  Tests 891 passed (891)
pnpm --filter @modelcontextprotocol/core-internal test      Test Files 69 passed (69)  Tests 1457 passed (1457)
test-e2e scenarios/logging + hosting-http + hosting-resume  Test Files 3 passed (3)    Tests 116 passed | 11 expected fail
pnpm --filter @modelcontextprotocol/server typecheck        # exit 0
pnpm --filter @modelcontextprotocol/server lint             # eslint clean; All matched files use Prettier code style!
pnpm --filter @modelcontextprotocol/test-integration lint   # All matched files use Prettier code style!
pnpm sync:snippets --check                                  # Snippet sync complete!

The full test-e2e run passes every test (2641 passed | 147 expected fail). It still exits non-zero because of two unhandled REQUEST_TIMEOUT rejections from the fake-timer protocol:timeout:max-total scenario in scenarios/protocol.test.ts. That scenario is also flaky on unpatched main in my local runs, and it doesn't touch logging.

Breaking Changes

None for callers that pass sessionId, or for sessionless transports. On Streamable HTTP and SSE, a server that calls sendLoggingMessage(params) without a sessionId now stops sending messages below the level the client set with logging/setLevel. That filtering is what the client asked for.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

There is a changeset (@modelcontextprotocol/server patch). The logging API is @deprecated under SEP-2577, but it "remains functional during the deprecation window", and this is the path the JSDoc example uses. python-sdk leaves filtering to the application's own logging/setLevel handler on the legacy era, so it has no equivalent to line up with. This fix only makes TS's own automatic handling (#882, #917) work the same on every transport.

Dedup evidence (searched 2026-09-25T00:22Z to 00:45Z UTC)

Disclosure: prepared with AI assistance (Claude Code); I reviewed the change and take responsibility for it.
🤖 Generated with Claude Code

…sionId

The logging/setLevel handler stores the level under the transport's
session id, but sendLoggingMessage(params) looked it up under an undefined
key. On session-bearing transports (Streamable HTTP, SSE) the client's
level was therefore ignored and every message was sent, while the same
call filtered correctly on stdio. Default the lookup to the connected
transport's session id.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@2sumtech
2sumtech requested a review from a team as a code owner September 25, 2026 00:47
@changeset-bot

changeset-bot Bot commented Sep 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 446fb93

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@modelcontextprotocol/server Patch
@modelcontextprotocol/client Patch
@modelcontextprotocol/codemod Patch
@modelcontextprotocol/core Patch
@modelcontextprotocol/server-legacy Patch
@modelcontextprotocol/core-internal Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Sep 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2863

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2863

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2863

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2863

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2863

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2863

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2863

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2863

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2863

commit: 446fb93

@claude claude Bot added the v2 Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes label Sep 25, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant