Skip to content

Commit 99ae2db

Browse files
committed
fix: route prompt schema warnings through logger
1 parent 8a8501b commit 99ae2db

6 files changed

Lines changed: 88 additions & 3 deletions

File tree

docs/client.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,24 @@ const client = new Client(
388388
);
389389
```
390390

391+
### SDK diagnostics logger
392+
393+
SDK-internal diagnostics, such as capability-gating debug messages and schema conversion warnings, use the {@linkcode @modelcontextprotocol/client!index.ProtocolOptions.logger | logger} constructor option. It defaults to `console`. The logger is partial: each method is optional, and omitted levels are silently skipped.
394+
395+
```ts source="../examples/client/src/clientGuide.examples.ts#sdkLogger_basic"
396+
const client = new Client(
397+
{ name: 'my-client', version: '1.0.0' },
398+
{
399+
logger: {
400+
warn: (...args) => console.warn('[mcp-sdk]', ...args),
401+
debug: () => {
402+
// Drop debug diagnostics
403+
}
404+
}
405+
}
406+
);
407+
```
408+
391409
### Manual notification handlers
392410

393411
For full control — or for notification types not covered by `listChanged` (such as log messages) — register handlers directly with {@linkcode @modelcontextprotocol/client!client/client.Client#setNotificationHandler | setNotificationHandler()}:

docs/server.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@ server.registerPrompt(
346346
> [!WARNING]
347347
> MCP logging is deprecated as of protocol version 2026-07-28 (SEP-2577). It remains fully functional during the deprecation window (at least twelve months); see the [deprecated features registry](https://modelcontextprotocol.io/specification/draft/deprecated). Migrate to stderr logging (STDIO servers) or OpenTelemetry.
348348
349+
This section covers MCP protocol logging sent from your server to the client. SDK-internal diagnostics, such as tool-name validation warnings and schema conversion fallbacks, are separate and use the `logger` constructor option on `Server` and `McpServer`. The option defaults to `console`; each logger method is optional, so omitted levels are skipped.
350+
349351
Logging lets your server send structured diagnostics — debug traces, progress updates, warnings — to the connected client as notifications (see [Logging](https://modelcontextprotocol.io/specification/latest/server/utilities/logging) in the MCP specification).
350352

351353
Declare the `logging` capability, then call `ctx.mcpReq.log(level, data)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside any handler:

examples/client/src/clientGuide.examples.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,24 @@ async function listChanged_basic() {
368368
return client;
369369
}
370370

371+
/** Example: Route SDK diagnostics through an application logger. */
372+
function sdkLogger_basic() {
373+
//#region sdkLogger_basic
374+
const client = new Client(
375+
{ name: 'my-client', version: '1.0.0' },
376+
{
377+
logger: {
378+
warn: (...args) => console.warn('[mcp-sdk]', ...args),
379+
debug: () => {
380+
// Drop debug diagnostics
381+
}
382+
}
383+
}
384+
);
385+
//#endregion sdkLogger_basic
386+
return client;
387+
}
388+
371389
// ---------------------------------------------------------------------------
372390
// Handling server-initiated requests
373391
// ---------------------------------------------------------------------------
@@ -615,6 +633,7 @@ void complete_basic;
615633
void notificationHandler_basic;
616634
void setLoggingLevel_basic;
617635
void listChanged_basic;
636+
void sdkLogger_basic;
618637
void capabilities_declaration;
619638
void sampling_handler;
620639
void elicitation_handler;

packages/core-internal/src/util/standardSchema.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,10 @@ export async function validateStandardSchema<T extends StandardSchemaV1>(
243243
// Prompt argument extraction
244244

245245
export function promptArgumentsFromStandardSchema(
246-
schema: StandardJSONSchemaV1
246+
schema: StandardJSONSchemaV1,
247+
logger: SdkLogger = console
247248
): Array<{ name: string; description?: string; required: boolean }> {
248-
const jsonSchema = standardSchemaToJsonSchema(schema, 'input');
249+
const jsonSchema = standardSchemaToJsonSchema(schema, 'input', logger);
249250
const properties = (jsonSchema.properties as Record<string, { description?: string }>) || {};
250251
const required = (jsonSchema.required as string[]) || [];
251252

packages/server/src/server/mcp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ export class McpServer {
465465
name,
466466
title: prompt.title,
467467
description: prompt.description,
468-
arguments: prompt.argsSchema ? promptArgumentsFromStandardSchema(prompt.argsSchema) : undefined,
468+
arguments: prompt.argsSchema ? promptArgumentsFromStandardSchema(prompt.argsSchema, this._logger) : undefined,
469469
icons: prompt.icons,
470470
_meta: prompt._meta
471471
};

packages/server/test/server/mcp.compat.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,51 @@ describe('registerTool/registerPrompt accept raw Zod shape (auto-wrapped)', () =
9191
expect(isStandardSchema(prompts['p']?.argsSchema)).toBe(true);
9292
});
9393

94+
it('routes prompt argsSchema conversion warnings to the configured logger', async () => {
95+
const warn = vi.fn();
96+
const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {});
97+
const server = new McpServer({ name: 't', version: '1.0.0' }, { logger: { warn } });
98+
99+
const argsSchema = z.object({ topic: z.string() });
100+
const { jsonSchema: _drop, ...stdNoJson } = argsSchema['~standard'] as unknown as Record<string, unknown>;
101+
void _drop;
102+
Object.defineProperty(argsSchema, '~standard', { value: { ...stdNoJson, vendor: 'zod' }, configurable: true });
103+
104+
server.registerPrompt('p', { argsSchema }, async ({ topic }) => ({
105+
messages: [{ role: 'user' as const, content: { type: 'text' as const, text: topic } }]
106+
}));
107+
108+
const [client, srv] = InMemoryTransport.createLinkedPair();
109+
try {
110+
await server.connect(srv);
111+
await client.start();
112+
113+
const responses: JSONRPCMessage[] = [];
114+
client.onmessage = m => responses.push(m);
115+
116+
await client.send({
117+
jsonrpc: '2.0',
118+
id: 1,
119+
method: 'initialize',
120+
params: {
121+
protocolVersion: LATEST_PROTOCOL_VERSION,
122+
capabilities: {},
123+
clientInfo: { name: 'c', version: '1.0.0' }
124+
}
125+
} as JSONRPCMessage);
126+
await client.send({ jsonrpc: '2.0', method: 'notifications/initialized' } as JSONRPCMessage);
127+
await client.send({ jsonrpc: '2.0', id: 2, method: 'prompts/list' } as JSONRPCMessage);
128+
129+
await vi.waitFor(() => expect(responses.some(r => 'id' in r && r.id === 2)).toBe(true));
130+
131+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('zod 4.2.0'));
132+
expect(consoleWarn).not.toHaveBeenCalled();
133+
} finally {
134+
consoleWarn.mockRestore();
135+
await server.close();
136+
}
137+
});
138+
94139
it('callback receives validated, typed args end-to-end via tools/call', async () => {
95140
const server = new McpServer({ name: 't', version: '1.0.0' });
96141

0 commit comments

Comments
 (0)