Skip to content

feat: add injectable SDK logger - #2370

Open
mattzcarey wants to merge 13 commits into
mainfrom
feat/injectable-logger
Open

mattzcarey wants to merge 13 commits into
mainfrom
feat/injectable-logger

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Jun 25, 2026 •

Copy link
Copy Markdown
Member

Adds a logger option to Client, McpServer, createMcpHandler and OAuthClientProvider, so the SDK's own diagnostics go to a logger you choose instead of straight to console. Fixes #1262.

import { createMcpHandler, McpServer, type SdkLogger } from '@modelcontextprotocol/server';
import { Client, ClientCredentialsProvider, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';

// The SDK logs at `debug` and `warn`. Both are optional; a missing one drops that level.
// On stdio, send everything to stderr so it never mixes with JSON-RPC on stdout.
const logger: SdkLogger = {
    debug: (...args) => console.error('[debug]', ...args),
    warn: (...args) => console.error('[warn]', ...args)
};

const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { logger });
const handler = createMcpHandler(() => new McpServer({ name: 'my-server', version: '1.0.0' }, { logger }), { logger });

// The OAuth flow runs outside the Client, so it reads the logger from the auth provider.
// Built-in providers take it as an option; a custom OAuthClientProvider sets a `logger` property.
const authProvider = new ClientCredentialsProvider({ clientId, clientSecret, expectedIssuer, logger });
const client = new Client({ name: 'my-client', version: '1.0.0' }, { logger });
await client.connect(new StreamableHTTPClientTransport(serverUrl, { authProvider }));

// `console` itself also fits; other loggers need a one-line adapter per level.
new Client({ name: 'my-client', version: '1.0.0' }, { logger: console });

Today the SDK writes warnings and debug messages with console.*, which applications can't redirect or silence. On stdio, anything a library writes to stdout lands in the JSON-RPC channel.

  • Routed through the logger: client capability fallbacks, SkillsClient fallbacks, client exclusion of tools with an invalid x-mcp-header, tool-name validation, server x-mcp-header and prompt-schema warnings, Standard Schema fallbacks, the createMcpHandler responseMode: 'json' warning, and the OAuth warnings from auth() and the built-in providers (unstamped credentials, missing discoveryState(), failed refresh, discarded credentials, omitted expectedIssuer).
  • SdkLogger has no filtering of its own; that stays with the application's logger. Leaving out logger (or passing null) keeps today's console output, and a logger that throws is ignored so a diagnostic can never fail the SDK call that emitted it.
  • The low-level Server emits no diagnostics of its own, so it doesn't take the option.
  • This is separate from MCP protocol logging: it never sends notifications/message. withLogging middleware and server skills keep their existing logger / onWarning options.

pnpm check:all, pnpm build:all and the client, server and core-internal test suites ran, including logger tests (routing, null and throwing loggers, typed-parameter loggers) in client/test/client/logger.test.ts, client/test/client/auth.test.ts, client/test/client/authExtensions.test.ts, server/test/server/mcp.compat.test.ts and server/test/server/createMcpHandler.test.ts.

@mattzcarey
mattzcarey requested a review from a team as a code owner June 25, 2026 13:02
@changeset-bot

changeset-bot Bot commented Jun 25, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d2ff67f

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

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

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 Jun 25, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/codemod

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

@modelcontextprotocol/core

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

@modelcontextprotocol/server

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

@modelcontextprotocol/server-legacy

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: d2ff67f

Comment thread packages/core-internal/src/util/standardSchema.ts
Comment thread packages/core-internal/src/shared/protocol.ts Outdated
@mattzcarey
mattzcarey force-pushed the feat/injectable-logger branch from 63bb2b4 to f2d0beb Compare June 25, 2026 13:38

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points from my earlier review are now addressed in this revision — the prompt argsSchema conversion path forwards the configured logger, and prose docs plus a sourced example were added for the new option. I didn't find any new issues, but since this introduces a new public API surface (the logger ProtocolOptions field and the exported SdkLogger type), a maintainer should sign off on the API shape.

Extended reasoning...

Overview

This PR adds an injectable SdkLogger to ProtocolOptions, threads it through Protocol, Client, and McpServer, and routes existing console.* SDK diagnostics (capability-gating debug messages, tool-name validation warnings, zod-fallback warnings) through it. It exports the SdkLogger type from the core public exports, adds a changeset, prose documentation in docs/client.md and docs/server.md, a sourced example in examples/client/src/clientGuide.examples.ts, and focused tests for the client, tool-name, prompt-conversion, and zod-fallback paths. The latest commit also addresses both items raised in my prior review: promptArgumentsFromStandardSchema now accepts and forwards a logger (with the prompts/list call site passing this._logger), and prose documentation for the option was added.

Security risks

None identified. The logger is a user-supplied callback object invoked only with diagnostic strings via optional chaining; it does not handle credentials, network input, or auth flows. The default remains console, preserving existing behavior for callers that don't opt in.

Level of scrutiny

Moderate. The mechanical changes (replacing console.warn/console.debug with this._logger.*?.() and threading a defaulted parameter) are low risk and well covered by the new tests, which also assert that console is no longer hit when a custom logger is configured. However, the PR introduces a new public API surface — a logger field on ProtocolOptions and a newly exported SdkLogger type — and this repository's conventions place the burden of proof on additions to the public API. Whether the shape (partial logger with all-optional methods, no level filtering, named SdkLogger, attached to ProtocolOptions rather than a narrower options type) is the right long-term design is a maintainer judgment call, so I'm not shadow-approving.

Other factors

The change has a concrete motivating issue (#1262), the changeset correctly marks core/client/server as minor, and existing behavior is unchanged when no logger is provided. The test coverage is reasonable: client capability-gating debug routing, tool-name warning routing, prompt argsSchema conversion routing, and the zod-fallback path all assert both the custom logger call and the absence of console output. No unresolved reviewer comments remain on the thread.

@KKonstantinov

Copy link
Copy Markdown
Contributor

Hey @mattzcarey , we explored this in #1034

You could get some stuff from there, there are some caveats with Stdio, plus following SysLog - RFC5424

@mattzcarey
mattzcarey force-pushed the feat/injectable-logger branch from f2d0beb to 103073d Compare June 25, 2026 21:52
Comment thread .changeset/injectable-sdk-logger.md Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest revision addresses my earlier findings (changeset now targets core-internal, prose docs/example added, and the prompts/list path now threads the configured logger with test coverage), and I didn't find new bugs — but this adds a new public API surface (ProtocolOptions.logger and the exported SdkLogger type) and there's an open maintainer comment pointing to prior design discussion in #1034 (stdio caveats, RFC5424 levels), so a human should weigh in on the API shape before merging.

Extended reasoning...

Overview

This PR adds an injectable SDK diagnostics logger: a new logger field on ProtocolOptions (consumed by Protocol, Client, and McpServer), a new SdkLogger type exported from the public surface of core-internal, and routing of existing console.* diagnostics (client capability-gating debug messages, tool-name validation warnings, zod JSON-schema fallback warnings) through that logger. It also adds prose docs in docs/client.md and docs/server.md, a sourced example, focused tests in all three packages, and a changeset bumping core-internal/client/server as minor.

Status of prior feedback

All three issues raised on earlier revisions are resolved in the current diff: the changeset frontmatter now lists @modelcontextprotocol/core-internal instead of the schemas-only core package; promptArgumentsFromStandardSchema now accepts and forwards a logger and McpServer passes this._logger at the prompts/list call site (with a regression test); and prose documentation plus an example were added. A grep of the modified packages shows no remaining un-routed console.* diagnostics in scope (the fetch-logging middleware has its own configurable RequestLogger and is unrelated).

Security risks

None identified. The logger is a passive sink for diagnostic strings; no auth, crypto, or transport behavior changes. The only minor consideration is that user-supplied logger callbacks now run inside SDK code paths, but they are invoked with optional chaining and a throwing logger would surface like any handler error.

Level of scrutiny

The implementation is small and well-tested, but this is a deliberate public API addition on a repo whose review conventions put the burden of proof on additions and ask for design-level sign-off first. There is also an unanswered maintainer comment referencing the earlier exploration in PR #1034 (stdio caveats, RFC5424/syslog level alignment), which bears directly on whether the minimal four-method SdkLogger shape is the right long-term surface. That design call belongs to a human maintainer, so I am deferring rather than approving despite finding no correctness issues.

Other factors

Test coverage is good (client logger routing, tool-name warning routing, prompt argsSchema fallback routing, direct standardSchema fallback test), and the docs additions correctly distinguish SDK-internal diagnostics from MCP protocol logging. The codemod versions.ts regeneration matches the corrected changeset targets.

@mattzcarey
mattzcarey force-pushed the feat/injectable-logger branch from b5094b5 to caaff71 Compare July 1, 2026 09:18
Comment thread packages/client/src/client/client.ts
Comment thread .changeset/injectable-sdk-logger.md Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issues flagged in earlier review rounds (changeset targeting the wrong package, prompt-path and client-side console call sites, createMcpHandler logger option) all appear to be addressed in the current revision, with test coverage added for each. Since this introduces new public API surface (SdkLogger and the logger option across ProtocolOptions and CreateMcpHandlerOptions), it still warrants a maintainer's sign-off on the API design; note prose documentation for the option under docs/ hasn't been added yet.

Extended reasoning...

Overview

This PR adds an injectable SDK logger: a new SdkLogger type in core-internal/src/shared/logger.ts, a logger field on ProtocolOptions (consumed by Protocol/Client/Server/McpServer) and on CreateMcpHandlerOptions, and it routes existing console.debug/console.warn SDK diagnostics (client capability messages, x-mcp-header exclusion warnings, tool-name validation warnings, zod-fallback warnings, the responseMode:'json' warning) through the configured logger with console as the default. Tests are added in the client, core-internal, and server packages covering each migrated call site.

Security risks

None of consequence — the change only affects where diagnostic strings are emitted. The logger is user-supplied and called with SDK-generated messages; there is no injection surface, no auth or transport behavior change, and defaults preserve existing console output.

Level of scrutiny

The implementation is mechanical and well-tested, but it adds new public API surface (the exported SdkLogger type and the logger option on two options interfaces across three published packages). Per this repository's conventions, new public API additions carry a burden of proof and should get maintainer review of the design (naming, whether all-optional-methods partial-logger semantics are the right shape, whether the option belongs on CreateMcpHandlerOptions too), so this is not a candidate for auto-approval despite the code being straightforward.

Other factors

All issues flagged in previous review rounds have been addressed in the current diff: the changeset now targets @modelcontextprotocol/core-internal, promptArgumentsFromStandardSchema accepts and forwards the logger, the client-side x-mcp-header exclusion warning uses this._logger, and createMcpHandler accepts a logger option — each with a corresponding test. The remaining gap is prose documentation: no docs/ page describes the new option or the partial-logger semantics (all methods optional), which the repo checklist asks for on new features. The bug hunting system found no bugs in the current revision.

@mattzcarey
mattzcarey force-pushed the feat/injectable-logger branch from 5ba9e0f to a95a283 Compare July 6, 2026 19:00
@mattzcarey
mattzcarey force-pushed the feat/injectable-logger branch from a95a283 to 453b520 Compare July 6, 2026 19:02
@mattzcarey
mattzcarey enabled auto-merge (squash) July 6, 2026 19:02
Comment thread docs/servers/logging-progress-cancellation.md Outdated
@mattzcarey
mattzcarey disabled auto-merge July 7, 2026 08:32
@mattzcarey
mattzcarey force-pushed the feat/injectable-logger branch from 1b92074 to 0856ac6 Compare October 27, 2026 10:54

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

One optional note from this repository's REVIEW.md or CLAUDE.md checks was not posted as a comment, over this review's limit for such notes; it is on this commit's check card.

Comment thread packages/client/src/client/client.ts
Comment thread packages/core-internal/src/shared/logger.ts Outdated
Comment thread docs/servers/logging-progress-cancellation.md Outdated
Comment thread packages/server/src/server/createMcpHandler.ts Outdated
Comment thread packages/server/test/server/createMcpHandler.test.ts Outdated
Comment thread packages/core-internal/src/shared/logger.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

Still open from earlier reviews (4):

  • Unresolved: 4 minor or pre-existing.

Comment thread packages/client/src/client/auth.ts Fixed

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new blocking issues. 2 optional suggestions (nits or notes on pre-existing code) were found and not posted. Nothing in this review needs a push before merging.

Still open from earlier reviews (4):

  • Unresolved: 4 minor or pre-existing.

- wrap every stored logger so a null logger falls back to console and a
  throwing logger can't fail the SDK call that emitted the diagnostic
- declare SdkLogger methods with method syntax so loggers with a typed
  first parameter are accepted, and drop the unused info/error levels
- fix the createMcpHandler logger test asserting after mockRestore
},
warn: (...args) => {
try {
target.warn?.(...args);

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new blocking issues. 1 optional suggestion (a nit or a note on pre-existing code) was found and not posted. Nothing in this review needs a push before merging.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inject your own logger

3 participants