From 14b093e28990db9ccbf5ce65a4772b2fe24d957d Mon Sep 17 00:00:00 2001 From: qc <235173498@qq.com> Date: Mon, 26 Oct 2026 09:52:17 +0800 Subject: [PATCH 1/2] fix(core): reject Standard Schema failures with empty issues --- .changeset/standard-schema-empty-issues.md | 6 +++++ .../core-internal/src/util/standardSchema.ts | 2 +- .../test/util/standardSchema.test.ts | 26 ++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 .changeset/standard-schema-empty-issues.md diff --git a/.changeset/standard-schema-empty-issues.md b/.changeset/standard-schema-empty-issues.md new file mode 100644 index 0000000000..8bdb9df852 --- /dev/null +++ b/.changeset/standard-schema-empty-issues.md @@ -0,0 +1,6 @@ +--- +'@modelcontextprotocol/client': patch +'@modelcontextprotocol/server': patch +--- + +Treat Standard Schema validation results with an empty `issues` array as failures instead of accepting an undefined value. diff --git a/packages/core-internal/src/util/standardSchema.ts b/packages/core-internal/src/util/standardSchema.ts index ebaaa57572..a8aacab6fc 100644 --- a/packages/core-internal/src/util/standardSchema.ts +++ b/packages/core-internal/src/util/standardSchema.ts @@ -284,7 +284,7 @@ export async function validateStandardSchema( data: unknown ): Promise>> { const result = await schema['~standard'].validate(data); - if (result.issues && result.issues.length > 0) { + if (result.issues) { return { success: false, error: result.issues.map(i => formatIssue(i)).join(', ') }; } return { success: true, data: (result as StandardSchemaV1.SuccessResult).value as StandardSchemaV1.InferOutput }; diff --git a/packages/core-internal/test/util/standardSchema.test.ts b/packages/core-internal/test/util/standardSchema.test.ts index 8856592ff0..ad8b09144c 100644 --- a/packages/core-internal/test/util/standardSchema.test.ts +++ b/packages/core-internal/test/util/standardSchema.test.ts @@ -1,6 +1,30 @@ import * as z from 'zod/v4'; -import { standardSchemaToJsonSchema } from '../../src/util/standardSchema'; +import { type StandardSchemaV1, standardSchemaToJsonSchema, validateStandardSchema } from '../../src/util/standardSchema'; + +describe('validateStandardSchema', () => { + test.each(['synchronous', 'asynchronous'] as const)('rejects %s failures with an empty issues array', async mode => { + const schema: StandardSchemaV1 = { + '~standard': { + version: 1, + vendor: 'empty-issues', + validate: () => (mode === 'asynchronous' ? Promise.resolve({ issues: [] }) : { issues: [] }) + } + }; + + await expect(validateStandardSchema(schema, { name: 'example' })).resolves.toEqual({ success: false, error: '' }); + }); + + test('preserves transformed values when validation succeeds', async () => { + const schema = z.string().transform(value => value.toUpperCase()); + + await expect(validateStandardSchema(schema, 'hello')).resolves.toEqual({ success: true, data: 'HELLO' }); + }); + + test('preserves undefined when validation succeeds', async () => { + await expect(validateStandardSchema(z.undefined(), undefined)).resolves.toEqual({ success: true, data: undefined }); + }); +}); describe('standardSchemaToJsonSchema', () => { test('emits type:object for plain z.object schemas', () => { From 3f07d253eae64d9eba573555342f740cc5961ee0 Mon Sep 17 00:00:00 2001 From: qc <235173498@qq.com> Date: Wed, 28 Oct 2026 12:14:33 +0800 Subject: [PATCH 2/2] fix(core): give empty Standard Schema issues a non-empty error --- packages/core-internal/src/util/standardSchema.ts | 5 ++++- packages/core-internal/test/util/standardSchema.test.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core-internal/src/util/standardSchema.ts b/packages/core-internal/src/util/standardSchema.ts index a8aacab6fc..11599f19d7 100644 --- a/packages/core-internal/src/util/standardSchema.ts +++ b/packages/core-internal/src/util/standardSchema.ts @@ -285,7 +285,10 @@ export async function validateStandardSchema( ): Promise>> { const result = await schema['~standard'].validate(data); if (result.issues) { - return { success: false, error: result.issues.map(i => formatIssue(i)).join(', ') }; + // A present but empty `issues` array is still a failure, so fall back to a + // non-empty message: callers that surface `error` must not report it as success. + const message = result.issues.map(i => formatIssue(i)).join(', '); + return { success: false, error: message || 'validation failed' }; } return { success: true, data: (result as StandardSchemaV1.SuccessResult).value as StandardSchemaV1.InferOutput }; } diff --git a/packages/core-internal/test/util/standardSchema.test.ts b/packages/core-internal/test/util/standardSchema.test.ts index ad8b09144c..5c7ab4df25 100644 --- a/packages/core-internal/test/util/standardSchema.test.ts +++ b/packages/core-internal/test/util/standardSchema.test.ts @@ -12,7 +12,10 @@ describe('validateStandardSchema', () => { } }; - await expect(validateStandardSchema(schema, { name: 'example' })).resolves.toEqual({ success: false, error: '' }); + await expect(validateStandardSchema(schema, { name: 'example' })).resolves.toEqual({ + success: false, + error: 'validation failed' + }); }); test('preserves transformed values when validation succeeds', async () => {