diff --git a/packages/core/src/types/types.ts b/packages/core/src/types/types.ts index 6ac79777b..2cd8556c2 100644 --- a/packages/core/src/types/types.ts +++ b/packages/core/src/types/types.ts @@ -2707,10 +2707,26 @@ export function getResultSchema(method: M): z.ZodType(schemas: readonly T[]): Record { +type MethodLiteralSchema = { + value?: unknown; + _def?: { + value?: unknown; + values?: readonly unknown[]; + }; +}; + +function getMethodLiteral(methodSchema: MethodLiteralSchema): string { + const method = methodSchema.value ?? methodSchema._def?.value ?? methodSchema._def?.values?.[0]; + if (typeof method !== 'string') { + throw new TypeError('Schema method literal must be a string'); + } + return method; +} + +function buildSchemaMap(schemas: readonly T[]): Record { const map: Record = {}; for (const schema of schemas) { - const method = schema.shape.method.value; + const method = getMethodLiteral(schema.shape.method); map[method] = schema; } return map; diff --git a/test/integration/test/issues/test1380.zod.literal-values.test.ts b/test/integration/test/issues/test1380.zod.literal-values.test.ts new file mode 100644 index 000000000..0126c713b --- /dev/null +++ b/test/integration/test/issues/test1380.zod.literal-values.test.ts @@ -0,0 +1,62 @@ +/** + * Regression test for https://github.com/modelcontextprotocol/typescript-sdk/issues/1380 + * + * Some Zod runtimes store literal values only in `._def.values[0]`, with both + * the top-level `.value` shortcut and `._def.value` absent. This test verifies + * McpServer initialization still succeeds in that scenario. + */ + +import { afterEach, describe, expect, test, vi } from 'vitest'; + +describe('Issue #1380: Zod literal method extraction', () => { + afterEach(() => { + vi.resetModules(); + vi.doUnmock('zod/v4'); + }); + + test('should construct McpServer when method literal is stored only in _def.values[0]', async () => { + vi.resetModules(); + vi.doMock('zod/v4', async () => { + const actual = await vi.importActual('zod/v4'); + + return { + ...actual, + literal: ((...args: Parameters) => { + const schema = actual.literal(...args); + + // Simulate a Zod runtime that stores the literal value only in + // _def.values[0], with neither the top-level .value shortcut + // nor _def.value present. + // + // We wrap the schema in a plain object that shadows .value with + // undefined and exposes a new _def that has only .values (no .value). + const originalDef = (schema as Record)._def as Record; + const value = originalDef.value ?? originalDef.values?.[0] ?? (schema as Record).value; + + const strippedDef: Record = {}; + for (const [k, v] of Object.entries(originalDef)) { + if (k !== 'value') strippedDef[k] = v; + } + if (!strippedDef.values) { + strippedDef.values = [value]; + } + + return Object.create(schema as object, { + value: { get: () => void 0, enumerable: true, configurable: true }, + _def: { get: () => strippedDef, enumerable: true, configurable: true } + }) as typeof schema; + }) as typeof actual.literal + }; + }); + + const { McpServer } = await import('@modelcontextprotocol/server'); + + expect( + () => + new McpServer({ + name: 'test server', + version: '1.0' + }) + ).not.toThrow(); + }); +});