From 708c2146afa58dbd4a16a1d2d5d8e02bc306cbf5 Mon Sep 17 00:00:00 2001 From: local Date: Wed, 30 Sep 2026 00:26:44 +0800 Subject: [PATCH 1/2] fix(validation): silence unknown format warnings in default Ajv logger --- src/validation/ajv-provider.ts | 24 ++++++- test/validation/ajv-unknown-format.test.ts | 81 ++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 test/validation/ajv-unknown-format.test.ts diff --git a/src/validation/ajv-provider.ts b/src/validation/ajv-provider.ts index 3c2967c3a1..61bf0a0880 100644 --- a/src/validation/ajv-provider.ts +++ b/src/validation/ajv-provider.ts @@ -6,12 +6,34 @@ import Ajv from 'ajv'; import _addFormats from 'ajv-formats'; import type { JsonSchemaType, JsonSchemaValidator, JsonSchemaValidatorResult, jsonSchemaValidator } from './types.js'; +/** + * Logger for the default Ajv instance. JSON Schema treats `format` as an annotation, + * so schemas are allowed to declare formats this validator does not know about (e.g. + * vendor-specific ones like `google-duration`), and unknown formats must be ignored + * silently. Ajv's default logger emits one "unknown format ... ignored" warning per + * schema path on every compile, so a single server-side format could flood the console + * on each `listTools` call. Other warnings and errors are still surfaced on the console. + * Callers who prefer to see (or map) unknown-format diagnostics can pass their own + * pre-configured Ajv instance instead. + */ +const defaultAjvLogger = { + log: (...args: unknown[]) => console.log(...args), + warn: (...args: unknown[]) => { + const message = args[0]; + if (!(typeof message === 'string' && message.startsWith('unknown format'))) { + console.warn(...args); + } + }, + error: (...args: unknown[]) => console.error(...args) +}; + function createDefaultAjvInstance(): Ajv { const ajv = new Ajv({ strict: false, validateFormats: true, validateSchema: false, - allErrors: true + allErrors: true, + logger: defaultAjvLogger }); const addFormats = _addFormats as unknown as typeof _addFormats.default; diff --git a/test/validation/ajv-unknown-format.test.ts b/test/validation/ajv-unknown-format.test.ts new file mode 100644 index 0000000000..cd875fde78 --- /dev/null +++ b/test/validation/ajv-unknown-format.test.ts @@ -0,0 +1,81 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { AjvJsonSchemaValidator } from '../../src/validation/ajv-provider.js'; +import type { JsonSchemaType } from '../../src/validation/types.js'; + +/** + * Servers may declare vendor-specific string `format` values that this SDK does not + * know about (e.g. `google-duration` used by the Google Monitoring MCP server). + * JSON Schema treats `format` as an annotation, so those schemas must still validate, + * and Ajv's "unknown format ... ignored" warnings must not flood the console every + * time tool outputSchemas are compiled (see issue #2855). + */ +describe('AjvJsonSchemaValidator with unknown formats', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + const schemaWithUnknownFormat: JsonSchemaType = { + type: 'object', + properties: { + d: { + type: 'string', + format: 'google-duration' + } + } + }; + + it('does not warn about unknown formats', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const validator = new AjvJsonSchemaValidator(); + + validator.getValidator(schemaWithUnknownFormat); + + const unknownFormatWarnings = warnSpy.mock.calls.filter( + ([message]) => typeof message === 'string' && message.startsWith('unknown format') + ); + expect(unknownFormatWarnings).toHaveLength(0); + }); + + it('stays quiet across repeated compiles of the same schema', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const validator = new AjvJsonSchemaValidator(); + + for (let i = 0; i < 10; i++) { + validator.getValidator(schemaWithUnknownFormat); + } + + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('still validates data when the format is unknown', () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}); + const validator = new AjvJsonSchemaValidator(); + const validate = validator.getValidator<{ d: string }>(schemaWithUnknownFormat); + + const result = validate({ d: '1800s' }); + expect(result.valid).toBe(true); + expect(result.data).toEqual({ d: '1800s' }); + + const invalidResult = validate({ d: 5 }); + expect(invalidResult.valid).toBe(false); + expect(invalidResult.errorMessage).toBeDefined(); + }); + + it('still validates known formats', () => { + const validator = new AjvJsonSchemaValidator(); + const validate = validator.getValidator<{ email: string }>({ + type: 'object', + properties: { + email: { + type: 'string', + format: 'email' + } + }, + required: ['email'] + }); + + expect(validate({ email: 'user@example.com' }).valid).toBe(true); + expect(validate({ email: 'not-an-email' }).valid).toBe(false); + }); +}); From c94cd9f2dee5e9abf330d0aa7ff99cda3f88fd64 Mon Sep 17 00:00:00 2001 From: liwenjie200543 <175601277+liwenjie200543@users.noreply.github.com> Date: Wed, 30 Sep 2026 09:40:22 +0800 Subject: [PATCH 2/2] chore: add changeset for Ajv unknown format warning fix --- .changeset/silence-ajv-unknown-format-warnings.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/silence-ajv-unknown-format-warnings.md diff --git a/.changeset/silence-ajv-unknown-format-warnings.md b/.changeset/silence-ajv-unknown-format-warnings.md new file mode 100644 index 0000000000..7b9b43f940 --- /dev/null +++ b/.changeset/silence-ajv-unknown-format-warnings.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/sdk': patch +--- + +Silence Ajv's "unknown format ... ignored" warnings in the default `AjvJsonSchemaValidator` logger so vendor-specific `format` annotations (e.g. `google-duration`) no longer flood the console on every `listTools` call. Validation semantics are unchanged.