feat!: drop Zod v3 support (#1460)

This commit is contained in:
Matt 2026-02-03 19:48:08 +00:00 • committed by GitHub
commit be5670f4fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 669 additions and 947 deletions

View file

@ -13,7 +13,7 @@ import {
SUPPORTED_PROTOCOL_VERSIONS
} from '@modelcontextprotocol/core';
import { InMemoryTaskStore, McpServer, Server } from '@modelcontextprotocol/server';
import * as z4 from 'zod/v4';
import * as z from 'zod/v4';
/***
* Test: Initialize with Matching Protocol Version
@ -2266,7 +2266,7 @@ describe('Task-based execution', () => {
'test-tool',
{
description: 'A test tool',
inputSchema: {}
inputSchema: z.object({})
},
{
async createTask(_args, extra) {
@ -2342,7 +2342,7 @@ describe('Task-based execution', () => {
'test-tool',
{
description: 'A test tool',
inputSchema: {}
inputSchema: z.object({})
},
{
async createTask(_args, extra) {
@ -2419,7 +2419,7 @@ describe('Task-based execution', () => {
'test-tool',
{
description: 'A test tool',
inputSchema: {}
inputSchema: z.object({})
},
{
async createTask(_args, extra) {
@ -2459,9 +2459,12 @@ describe('Task-based execution', () => {
// Create a task using callToolStream to capture the task ID
let taskId: string | undefined;
const stream = client.experimental.tasks.callToolStream({ name: 'test-tool', arguments: {} }, CallToolResultSchema, {
task: { ttl: 60_000 }
});
const stream = client.experimental.tasks.callToolStream(
{ name: 'test-tool', arguments: {} },
{
task: { ttl: 60_000 }
}
);
for await (const message of stream) {
if (message.type === 'taskCreated') {
@ -2500,7 +2503,7 @@ describe('Task-based execution', () => {
'test-tool',
{
description: 'A test tool',
inputSchema: {}
inputSchema: z.object({})
},
{
async createTask(_args, extra) {
@ -2983,9 +2986,9 @@ describe('Task-based execution', () => {
'test-tool',
{
description: 'A test tool',
inputSchema: {
id: z4.string()
}
inputSchema: z.object({
id: z.string()
})
},
{
async createTask({ id }, extra) {
@ -3253,7 +3256,7 @@ test('should respect server task capabilities', async () => {
'test-tool',
{
description: 'A test tool',
inputSchema: {}
inputSchema: z.object({})
},
{
async createTask(_args, extra) {

View file

@ -9,12 +9,9 @@
import { Client } from '@modelcontextprotocol/client';
import { InMemoryTransport, ListPromptsResultSchema } from '@modelcontextprotocol/core';
import { McpServer } from '@modelcontextprotocol/server';
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
import { zodTestMatrix } from '@modelcontextprotocol/test-helpers';
describe.each(zodTestMatrix)('Issue #1277: $zodVersionLabel', (entry: ZodMatrixEntry) => {
const { z } = entry;
import * as z from 'zod/v4';
describe('Issue #1277: Zod v4', () => {
test('should preserve argument descriptions from .describe()', async () => {
const mcpServer = new McpServer({
name: 'test server',
@ -28,10 +25,10 @@ describe.each(zodTestMatrix)('Issue #1277: $zodVersionLabel', (entry: ZodMatrixE
mcpServer.registerPrompt(
'test',
{
argsSchema: {
argsSchema: z.object({
name: z.string().describe('The user name'),
value: z.string().describe('The value to set')
}
})
},
async ({ name, value }) => ({
messages: [

View file

@ -9,12 +9,9 @@
import { Client } from '@modelcontextprotocol/client';
import { CallToolResultSchema, InMemoryTransport } from '@modelcontextprotocol/core';
import { McpServer } from '@modelcontextprotocol/server';
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
import { zodTestMatrix } from '@modelcontextprotocol/test-helpers';
describe.each(zodTestMatrix)('Issue #400: $zodVersionLabel', (entry: ZodMatrixEntry) => {
const { z } = entry;
import * as z from 'zod/v4';
describe('Issue #400: Zod v4', () => {
test('should accept undefined arguments when all tool params are optional', async () => {
const mcpServer = new McpServer({
name: 'test server',
@ -28,10 +25,10 @@ describe.each(zodTestMatrix)('Issue #400: $zodVersionLabel', (entry: ZodMatrixEn
mcpServer.registerTool(
'optional-params-tool',
{
inputSchema: {
inputSchema: z.object({
limit: z.number().optional(),
offset: z.number().optional()
}
})
},
async ({ limit, offset }) => ({
content: [

View file

@ -25,7 +25,7 @@ import { createMcpExpressApp } from '@modelcontextprotocol/express';
import { InMemoryTaskStore, McpServer, Server } from '@modelcontextprotocol/server';
import type { Request, Response } from 'express';
import supertest from 'supertest';
import * as z4 from 'zod/v4';
import * as z from 'zod/v4';
describe('Server with standard protocol methods', () => {
/*
@ -2143,7 +2143,7 @@ describe('Task-based execution', () => {
'test-tool',
{
description: 'A test tool',
inputSchema: {}
inputSchema: z.object({})
},
{
async createTask(_args, extra) {
@ -2200,11 +2200,14 @@ describe('Task-based execution', () => {
// Use callToolStream to create a task and capture the task ID
let taskId: string | undefined;
const stream = client.experimental.tasks.callToolStream({ name: 'test-tool', arguments: {} }, CallToolResultSchema, {
task: {
ttl: 60_000
const stream = client.experimental.tasks.callToolStream(
{ name: 'test-tool', arguments: {} },
{
task: {
ttl: 60_000
}
}
});
);
for await (const message of stream) {
if (message.type === 'taskCreated') {
@ -2335,7 +2338,7 @@ describe('Task-based execution', () => {
);
// Track the elicitation request to verify related-task metadata
let capturedElicitRequest: z4.infer<typeof ElicitRequestSchema> | null = null;
let capturedElicitRequest: z.infer<typeof ElicitRequestSchema> | null = null;
// Set up client elicitation handler
client.setRequestHandler('elicitation/create', async (request, extra) => {
@ -2365,7 +2368,7 @@ describe('Task-based execution', () => {
'collect-info',
{
description: 'Collects user info via elicitation',
inputSchema: {}
inputSchema: z.object({})
},
{
async createTask(_args, extra) {
@ -2427,11 +2430,14 @@ describe('Task-based execution', () => {
// Call tool WITH task creation using callToolStream to capture task ID
let taskId: string | undefined;
const stream = client.experimental.tasks.callToolStream({ name: 'collect-info', arguments: {} }, CallToolResultSchema, {
task: {
ttl: 60_000
const stream = client.experimental.tasks.callToolStream(
{ name: 'collect-info', arguments: {} },
{
task: {
ttl: 60_000
}
}
});
);
for await (const message of stream) {
if (message.type === 'taskCreated') {
@ -2841,10 +2847,10 @@ describe('Task-based execution', () => {
'async-tool',
{
description: 'An async test tool',
inputSchema: {
delay: z4.number().optional().default(10),
taskNum: z4.number().optional()
}
inputSchema: z.object({
delay: z.number().optional().default(10),
taskNum: z.number().optional()
})
},
{
async createTask({ delay, taskNum }, extra) {

View file

@ -17,9 +17,8 @@ import {
UrlElicitationRequiredError
} from '@modelcontextprotocol/core';
import { completable, McpServer, ResourceTemplate } from '@modelcontextprotocol/server';
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
import { zodTestMatrix } from '@modelcontextprotocol/test-helpers';
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import * as z from 'zod/v4';
function createLatch() {
let latch = false;
@ -37,9 +36,7 @@ function createLatch() {
};
}
describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
const { z } = entry;
describe('Zod v4', () => {
describe('McpServer', () => {
/***
* Test: Basic Server Instance
@ -111,9 +108,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'long-operation',
{
description: 'A long running operation with progress updates',
inputSchema: {
inputSchema: z.object({
steps: z.number().min(1).describe('Number of steps to perform')
}
})
},
async ({ steps }, { sendNotification, _meta }) => {
const progressToken = _meta?.progressToken;
@ -406,9 +403,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
const tool = mcpServer.registerTool(
'test',
{
inputSchema: {
inputSchema: z.object({
name: z.string()
}
})
},
async ({ name }) => ({
content: [
@ -422,10 +419,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
// Update the tool with a different schema
tool.update({
paramsSchema: {
paramsSchema: z.object({
name: z.string(),
value: z.number()
},
}),
callback: async ({ name, value }) => ({
content: [
{
@ -502,9 +499,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
const tool = mcpServer.registerTool(
'test',
{
outputSchema: {
outputSchema: z.object({
result: z.number()
}
})
},
async () => ({
content: [{ type: 'text', text: '' }],
@ -516,10 +513,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
// Update the tool with a different outputSchema
tool.update({
outputSchema: {
outputSchema: z.object({
result: z.number(),
sum: z.number()
},
}),
callback: async () => ({
content: [{ type: 'text', text: '' }],
structuredContent: {
@ -648,7 +645,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerTool(
'test',
{
inputSchema: { name: z.string(), value: z.number() }
inputSchema: z.object({ name: z.string(), value: z.number() })
},
async ({ name, value }) => ({
content: [{ type: 'text', text: `${name}: ${value}` }]
@ -796,7 +793,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerTool(
'test',
{
inputSchema: { name: z.string() },
inputSchema: z.object({ name: z.string() }),
annotations: { title: 'Test Tool', readOnlyHint: true }
},
async ({ name }) => ({
@ -839,7 +836,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'test',
{
description: 'A tool with everything',
inputSchema: { name: z.string() },
inputSchema: z.object({ name: z.string() }),
annotations: {
title: 'Complete Test Tool',
readOnlyHint: true,
@ -935,10 +932,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerTool(
'test',
{
inputSchema: {
inputSchema: z.object({
name: z.string(),
value: z.number()
}
})
},
async ({ name, value }) => ({
content: [
@ -1044,14 +1041,14 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'test',
{
description: 'Test tool with structured output',
inputSchema: {
inputSchema: z.object({
input: z.string()
},
outputSchema: {
}),
outputSchema: z.object({
processedInput: z.string(),
resultType: z.string(),
timestamp: z.string()
}
})
},
async ({ input }) => ({
structuredContent: {
@ -1146,13 +1143,13 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'test',
{
description: 'Test tool with output schema but missing structured content',
inputSchema: {
inputSchema: z.object({
input: z.string()
},
outputSchema: {
}),
outputSchema: z.object({
processedInput: z.string(),
resultType: z.string()
}
})
},
async ({ input }) => ({
// Only return content without structuredContent
@ -1207,13 +1204,13 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'test',
{
description: 'Test tool with output schema but missing structured content',
inputSchema: {
inputSchema: z.object({
input: z.string()
},
outputSchema: {
}),
outputSchema: z.object({
processedInput: z.string(),
resultType: z.string()
}
})
},
async ({ input }) => ({
content: [
@ -1267,14 +1264,14 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'test',
{
description: 'Test tool with invalid structured output',
inputSchema: {
inputSchema: z.object({
input: z.string()
},
outputSchema: {
}),
outputSchema: z.object({
processedInput: z.string(),
resultType: z.string(),
timestamp: z.string()
}
})
},
async ({ input }) => ({
content: [
@ -1490,9 +1487,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'test',
{
description: 'Test tool',
inputSchema: {
inputSchema: z.object({
input: z.string()
}
})
},
async ({ input }) => ({
content: [
@ -1696,7 +1693,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'test-with-meta',
{
description: 'A tool with _meta field',
inputSchema: { name: z.string() },
inputSchema: z.object({ name: z.string() }),
_meta: metaData
},
async ({ name }) => ({
@ -1733,7 +1730,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'test-without-meta',
{
description: 'A tool without _meta field',
inputSchema: { name: z.string() }
inputSchema: z.object({ name: z.string() })
},
async ({ name }) => ({
content: [{ type: 'text', text: `Hello, ${name}!` }]
@ -1784,7 +1781,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'task-tool',
{
description: 'A tool with task support',
inputSchema: { input: z.string() },
inputSchema: z.object({ input: z.string() }),
execution: {
taskSupport: 'required'
}
@ -1853,7 +1850,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'optional-task-tool',
{
description: 'A tool with optional task support',
inputSchema: { input: z.string() },
inputSchema: z.object({ input: z.string() }),
execution: {
taskSupport: 'optional'
}
@ -3034,9 +3031,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
const prompt = mcpServer.registerPrompt(
'test',
{
argsSchema: {
argsSchema: z.object({
name: z.string()
}
})
},
async ({ name }) => ({
messages: [
@ -3053,10 +3050,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
// Update the prompt with a different schema
prompt.update({
argsSchema: {
argsSchema: z.object({
name: z.string(),
value: z.string()
},
}),
callback: async ({ name, value }) => ({
messages: [
{
@ -3260,10 +3257,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerPrompt(
'test',
{
argsSchema: {
argsSchema: z.object({
name: z.string(),
value: z.string()
}
})
},
async ({ name, value }) => ({
messages: [
@ -3355,10 +3352,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerPrompt(
'test',
{
argsSchema: {
argsSchema: z.object({
name: z.string(),
value: z.string().min(3)
}
})
},
async ({ name, value }) => ({
messages: [
@ -3476,7 +3473,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
});
// This should succeed
mcpServer.registerPrompt('echo', { argsSchema: { message: z.string() } }, ({ message }) => ({
mcpServer.registerPrompt('echo', { argsSchema: z.object({ message: z.string() }) }, ({ message }) => ({
messages: [
{
role: 'user',
@ -3521,7 +3518,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
// Register a prompt with completion
mcpServer.registerPrompt(
'echo',
{ argsSchema: { message: completable(z.string(), () => ['hello', 'world']) } },
{ argsSchema: z.object({ message: completable(z.string(), () => ['hello', 'world']) }) },
({ message }) => ({
messages: [
{
@ -3595,9 +3592,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerPrompt(
'test-prompt',
{
argsSchema: {
argsSchema: z.object({
name: z.string()
}
})
},
async ({ name }) => ({
messages: [
@ -3637,9 +3634,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerPrompt(
'test-prompt',
{
argsSchema: {
argsSchema: z.object({
name: completable(z.string(), () => ['Alice', 'Bob', 'Charlie'])
}
})
},
async ({ name }) => ({
messages: [
@ -3678,9 +3675,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerPrompt(
'test-prompt',
{
argsSchema: {
argsSchema: z.object({
name: completable(z.string(), () => ['Alice', 'Bob', 'Charlie'])
}
})
},
async ({ name }) => ({
messages: [
@ -3737,9 +3734,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerPrompt(
'test-prompt',
{
argsSchema: {
argsSchema: z.object({
name: completable(z.string(), test => ['Alice', 'Bob', 'Charlie'].filter(value => value.startsWith(test)))
}
})
},
async ({ name }) => ({
messages: [
@ -3984,9 +3981,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerPrompt(
'test-prompt',
{
argsSchema: {
argsSchema: z.object({
name: z.string().optional()
}
})
},
() => ({
messages: []
@ -4287,7 +4284,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
{
title: 'Team Greeting',
description: 'Generate a greeting for team members',
argsSchema: {
argsSchema: z.object({
department: completable(z.string(), value => {
return ['engineering', 'sales', 'marketing', 'support'].filter(d => d.startsWith(value));
}),
@ -4307,7 +4304,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
}
return ['Guest'].filter(n => n.startsWith(value));
})
}
})
},
async ({ department, name }) => ({
messages: [
@ -4444,11 +4441,11 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerTool(
'book-restaurant',
{
inputSchema: {
inputSchema: z.object({
restaurant: z.string(),
date: z.string(),
partySize: z.number()
}
})
},
async ({ restaurant, date, partySize }) => {
// Check availability
@ -5577,7 +5574,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
{
title: 'Team Greeting',
description: 'Generate a greeting for team members',
argsSchema: {
argsSchema: z.object({
department: completable(z.string(), value => {
return ['engineering', 'sales', 'marketing', 'support'].filter(d => d.startsWith(value));
}),
@ -5597,7 +5594,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
}
return ['Guest'].filter(n => n.startsWith(value));
})
}
})
},
async ({ department, name }) => ({
messages: [
@ -5734,11 +5731,11 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
mcpServer.registerTool(
'book-restaurant',
{
inputSchema: {
inputSchema: z.object({
restaurant: z.string(),
date: z.string(),
partySize: z.number()
}
})
},
async ({ restaurant, date, partySize }) => {
// Check availability
@ -6214,9 +6211,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'long-running-task',
{
description: 'A long running task',
inputSchema: {
inputSchema: z.object({
input: z.string()
},
}),
execution: {
taskSupport: 'required'
}
@ -6319,9 +6316,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'optional-task',
{
description: 'An optional task',
inputSchema: {
inputSchema: z.object({
value: z.number()
},
}),
execution: {
taskSupport: 'optional'
}
@ -6427,9 +6424,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'task-tool',
{
description: 'A task tool',
inputSchema: {
inputSchema: z.object({
data: z.string()
},
}),
execution: {
taskSupport: 'required'
}
@ -6737,9 +6734,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'invalid-task',
{
description: 'A task with forbidden support',
inputSchema: {
inputSchema: z.object({
input: z.string()
},
}),
execution: {
taskSupport: 'forbidden' as unknown as 'required'
}

View file

@ -12,10 +12,10 @@ import {
ListToolsResultSchema,
McpServer
} from '@modelcontextprotocol/server';
import type { ZNamespace, ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
import { listenOnRandomPort, zodTestMatrix } from '@modelcontextprotocol/test-helpers';
import { listenOnRandomPort } from '@modelcontextprotocol/test-helpers';
import * as z from 'zod/v4';
async function setupServer(withSessionManagement: boolean, z: ZNamespace) {
async function setupServer(withSessionManagement: boolean) {
const server: Server = createServer();
const mcpServer = new McpServer(
{ name: 'test-server', version: '1.0.0' },
@ -55,9 +55,9 @@ async function setupServer(withSessionManagement: boolean, z: ZNamespace) {
'greet',
{
description: 'A simple greeting tool',
inputSchema: {
inputSchema: z.object({
name: z.string().describe('Name to greet').default('World')
}
})
},
async ({ name }) => {
return {
@ -85,8 +85,7 @@ async function setupServer(withSessionManagement: boolean, z: ZNamespace) {
return { server, mcpServer, serverTransport, baseUrl };
}
describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
const { z } = entry;
describe('Zod v4', () => {
describe('Streamable HTTP Transport Session Management', () => {
// Function to set up the server with optional session management
describe('Stateless Mode', () => {
@ -96,7 +95,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
let baseUrl: URL;
beforeEach(async () => {
const setup = await setupServer(false, z);
const setup = await setupServer(false);
server = setup.server;
mcpServer = setup.mcpServer;
serverTransport = setup.serverTransport;
@ -260,7 +259,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
let baseUrl: URL;
beforeEach(async () => {
const setup = await setupServer(true, z);
const setup = await setupServer(true);
server = setup.server;
mcpServer = setup.mcpServer;
serverTransport = setup.serverTransport;

View file

@ -18,7 +18,7 @@ import {
TaskSchema
} from '@modelcontextprotocol/server';
import { listenOnRandomPort, waitForTaskStatus } from '@modelcontextprotocol/test-helpers';
import { z } from 'zod';
import * as z from 'zod/v4';
describe('Task Lifecycle Integration Tests', () => {
let server: Server;
@ -57,10 +57,10 @@ describe('Task Lifecycle Integration Tests', () => {
{
title: 'Long Running Task',
description: 'A tool that takes time to complete',
inputSchema: {
inputSchema: z.object({
duration: z.number().describe('Duration in milliseconds').default(1000),
shouldFail: z.boolean().describe('Whether the task should fail').default(false)
}
})
},
{
async createTask({ duration, shouldFail }, extra) {
@ -109,9 +109,9 @@ describe('Task Lifecycle Integration Tests', () => {
{
title: 'Input Required Task',
description: 'A tool that requires user input',
inputSchema: {
inputSchema: z.object({
userName: z.string().describe('User name').optional()
}
})
},
{
async createTask({ userName }, extra) {
@ -413,9 +413,9 @@ describe('Task Lifecycle Integration Tests', () => {
{
title: 'Multi Request Task',
description: 'A tool that sends multiple server requests',
inputSchema: {
inputSchema: z.object({
requestCount: z.number().describe('Number of requests to send').default(3)
}
})
},
{
async createTask({ requestCount }, extra) {
@ -902,9 +902,9 @@ describe('Task Lifecycle Integration Tests', () => {
{
title: 'Cancellable Task',
description: 'A tool that queues messages and can be cancelled',
inputSchema: {
inputSchema: z.object({
messageCount: z.number().describe('Number of messages to queue').default(2)
}
})
},
{
async createTask({ messageCount }, extra) {
@ -1099,10 +1099,10 @@ describe('Task Lifecycle Integration Tests', () => {
{
title: 'Streaming Task',
description: 'A tool that sends messages over time',
inputSchema: {
inputSchema: z.object({
messageCount: z.number().describe('Number of messages to send').default(3),
delayBetweenMessages: z.number().describe('Delay between messages in ms').default(200)
}
})
},
{
async createTask({ messageCount, delayBetweenMessages }, extra) {
@ -1316,9 +1316,9 @@ describe('Task Lifecycle Integration Tests', () => {
{
title: 'Quick Complete Task',
description: 'A tool that queues messages and completes quickly',
inputSchema: {
inputSchema: z.object({
messageCount: z.number().describe('Number of messages to queue').default(2)
}
})
},
{
async createTask({ messageCount }, extra) {
@ -1622,9 +1622,12 @@ describe('Task Lifecycle Integration Tests', () => {
await client.connect(transport);
// Use callToolStream instead of raw request()
const stream = client.experimental.tasks.callToolStream({ name: 'input-task', arguments: {} }, CallToolResultSchema, {
task: { ttl: 60_000 }
});
const stream = client.experimental.tasks.callToolStream(
{ name: 'input-task', arguments: {} },
{
task: { ttl: 60_000 }
}
);
// Collect all stream messages
const messages: Array<{ type: string; task?: unknown; result?: unknown; error?: unknown }> = [];

View file

@ -6,8 +6,8 @@ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/cli
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
import type { EventStore, JSONRPCMessage } from '@modelcontextprotocol/server';
import { CallToolResultSchema, McpServer } from '@modelcontextprotocol/server';
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
import { listenOnRandomPort, zodTestMatrix } from '@modelcontextprotocol/test-helpers';
import { listenOnRandomPort } from '@modelcontextprotocol/test-helpers';
import * as z from 'zod/v4';
/**
* Simple in-memory EventStore for testing resumability.
@ -43,8 +43,7 @@ class InMemoryEventStore implements EventStore {
}
}
describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
const { z } = entry;
describe('Zod v4', () => {
describe('Transport resumability', () => {
let server: Server;
let mcpServer: McpServer;
@ -64,9 +63,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'send-notification',
{
description: 'Sends a single notification',
inputSchema: {
inputSchema: z.object({
message: z.string().describe('Message to send').default('Test notification')
}
})
},
async ({ message }, { sendNotification }) => {
// Send notification immediately
@ -89,10 +88,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'run-notifications',
{
description: 'Sends multiple notifications over time',
inputSchema: {
inputSchema: z.object({
count: z.number().describe('Number of notifications to send').default(10),
interval: z.number().describe('Interval between notifications in ms').default(50)
}
})
},
async ({ count, interval }, { sendNotification }) => {
// Send notifications at specified intervals

View file

@ -1,12 +1,9 @@
import { Client } from '@modelcontextprotocol/client';
import { InMemoryTransport } from '@modelcontextprotocol/core';
import { McpServer, ResourceTemplate, Server } from '@modelcontextprotocol/server';
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
import { zodTestMatrix } from '@modelcontextprotocol/test-helpers';
describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
const { z } = entry;
import * as z from 'zod/v4';
describe('Zod v4', () => {
describe('Title field backwards compatibility', () => {
it('should work with tools that have title', async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
@ -19,9 +16,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
{
title: 'Test Tool Display Name',
description: 'A test tool',
inputSchema: {
inputSchema: z.object({
value: z.string()
}
})
},
async () => ({ content: [{ type: 'text', text: 'result' }] })
);
@ -44,7 +41,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
const server = new McpServer({ name: 'test-server', version: '1.0.0' }, { capabilities: {} });
// Register tool without title
server.registerTool('test-tool', { description: 'A test tool', inputSchema: { value: z.string() } }, async () => ({
server.registerTool('test-tool', { description: 'A test tool', inputSchema: z.object({ value: z.string() }) }, async () => ({
content: [{ type: 'text', text: 'result' }]
}));
@ -94,7 +91,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
{
title: 'Test Prompt Display Name',
description: 'A test prompt',
argsSchema: { input: z.string() }
argsSchema: z.object({ input: z.string() })
},
async ({ input }) => ({
messages: [