Skip to content

CLI cannot connect to a modern server that advertises logging (sends legacy logging/setLevel) #1990

Description

@cliffhall

Summary

The CLI cannot connect to any modern-era server that advertises the logging capability. Every command fails at connect — not just logging ones — because the client sends the legacy logging/setLevel during the post-connect sequence, and the modern wire era rejects it.

$ mcp-inspector --cli --config <catalog> --server showcase --method tools/list --format json
{"error":{"code":"error","message":"Method 'logging/setLevel' is not supported by the negotiated protocol version (wire era 2026-07-28)"}}

Reproduce

npm run test-servers:build
node test-servers/build/server-composable.js --config test-servers/configs/logging-modern-http.json
# note the announced URL, then point a catalog at it with "protocolEra": "modern"
node clients/cli/build/index.js --config <catalog> --server showcase --method tools/list --format json

tools/list is incidental — any method fails, because the failure is in connect, before the method runs.

Cause

Two things combine:

  1. clients/cli/src/cli.ts:152 hardcodes initialLoggingLevel: "debug" for every CLI connection.

  2. core/mcp/inspectorClient.ts:1940 gates the connect-time call on the server capability but not on the protocol era:

    // Set initial logging level if configured and server supports it
    if (this.initialLoggingLevel && this.capabilities?.logging) {
      await this.client.setLoggingLevel(this.initialLoggingLevel, this.getRequestOptions());
    }

logging/setLevel is a legacy-era method. On the modern era the equivalent is the per-request _meta["io.modelcontextprotocol/logLevel"] opt-in (the modernLogLevel server field) — which is exactly what the root README documents for logging-modern-http.json:

Modern — the same tab instead shows Log Level per Request. Pick a level to opt in and the client stamps _meta["io.modelcontextprotocol/logLevel"] on every subsequent request.

So the call is not merely rejected, it is the wrong mechanism for that era.

Scope

CLI only. initialLoggingLevel is set in exactly one place, and neither web nor TUI passes it — they drive logging through the Logs tab, which already era-splits correctly. That is why this has gone unnoticed: the showcase config is documented as a by-hand web exercise, and the web path works.

Not a regression

Verified against both SDK versions: it reproduces identically on 2.0.0-beta.5 and on 2.0.0. Found while verifying the showcase configs for #1988 / #1989, but it predates that bump and is not caused by it.

Fix

Gate the connect-time call on the negotiated era — legacy connections keep setLoggingLevel, modern ones fall through to the modernLogLevel per-request path they already have. The guard belongs in inspectorClient.ts next to the capability check rather than in the CLI, so any future caller passing initialLoggingLevel is covered too.

Acceptance criteria

  • --cli connects to logging-modern-http.json at protocolEra: "modern" and tools/list succeeds.
  • --cli against logging-legacy-http.json still applies the initial level via logging/setLevel (no behavior change on legacy).
  • A unit test covers the era split at the inspectorClient.ts guard, so a modern connection never emits logging/setLevel.
  • npm run ci passes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingv2Issues and PRs for v2

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions