diff --git a/examples/server/src/simpleStreamableHttp.ts b/examples/server/src/simpleStreamableHttp.ts index 7613e3786..c1049f153 100644 --- a/examples/server/src/simpleStreamableHttp.ts +++ b/examples/server/src/simpleStreamableHttp.ts @@ -478,16 +478,16 @@ const getServer = () => { } }, { - async createTask({ duration }, { taskStore, taskRequestedTtl }) { + async createTask({ duration }, { task }) { // Create the task - const task = await taskStore.createTask({ - ttl: taskRequestedTtl + const newTask = await task.store.createTask({ + ttl: task.requestedTtl }); // Simulate out-of-band work (async () => { await new Promise(resolve => setTimeout(resolve, duration)); - await taskStore.storeTaskResult(task.taskId, 'completed', { + await task.store.storeTaskResult(newTask.taskId, 'completed', { content: [ { type: 'text', @@ -499,14 +499,14 @@ const getServer = () => { // Return CreateTaskResult with the created task return { - task + task: newTask }; }, - async getTask(_args, { taskId, taskStore }) { - return await taskStore.getTask(taskId); + async getTask(_args, { task }) { + return await task.store.getTask(task.id); }, - async getTaskResult(_args, { taskId, taskStore }) { - const result = await taskStore.getTaskResult(taskId); + async getTaskResult(_args, { task }) { + const result = await task.store.getTaskResult(task.id); return result as CallToolResult; } } diff --git a/packages/core/src/experimental/tasks/interfaces.ts b/packages/core/src/experimental/tasks/interfaces.ts index c1901d70a..65971b5f0 100644 --- a/packages/core/src/experimental/tasks/interfaces.ts +++ b/packages/core/src/experimental/tasks/interfaces.ts @@ -3,7 +3,7 @@ * WARNING: These APIs are experimental and may change without notice. */ -import type { RequestHandlerExtra, RequestTaskStore } from '../../shared/protocol.js'; +import type { RequestHandlerExtra, TaskContext } from '../../shared/protocol.js'; import type { JSONRPCErrorResponse, JSONRPCNotification, @@ -23,11 +23,11 @@ import type { // ============================================================================ /** - * Extended handler extra with task store for task creation. + * Extended handler extra with task context for task creation. * @experimental */ export interface CreateTaskRequestHandlerExtra extends RequestHandlerExtra { - taskStore: RequestTaskStore; + task: TaskContext; } /** @@ -35,8 +35,7 @@ export interface CreateTaskRequestHandlerExtra extends RequestHandlerExtra { - taskId: string; - taskStore: RequestTaskStore; + task: TaskContext & { id: string }; } /** diff --git a/packages/core/src/shared/protocol.ts b/packages/core/src/shared/protocol.ts index 9c65015d1..ef7d306d2 100644 --- a/packages/core/src/shared/protocol.ts +++ b/packages/core/src/shared/protocol.ts @@ -235,6 +235,18 @@ export interface RequestTaskStore { listTasks(cursor?: string): Promise<{ tasks: Task[]; nextCursor?: string }>; } +/** + * Context for task-related operations in request handlers. + */ +export interface TaskContext { + /** The related task identifier (present when operating on existing task) */ + id?: string; + /** Task store for managing task state */ + store: RequestTaskStore; + /** Requested TTL in milliseconds (from client's task creation params) */ + requestedTtl?: number; +} + /** * Extra data given to request handlers. */ @@ -265,11 +277,11 @@ export type RequestHandlerExtra { expect(task).toBeNull(); }); - it('should support null TTL for unlimited lifetime', async () => { - // Test that null TTL means unlimited lifetime - const taskParams: TaskCreationParams = { - ttl: null - }; + it('should support omitted TTL for unlimited lifetime', async () => { + // Test that omitting TTL means unlimited lifetime (server returns null) + // Per spec: clients omit ttl to let server decide, server returns null for unlimited + const taskParams: TaskCreationParams = {}; const createdTask = await store.createTask(taskParams, 2222, { method: 'tools/call', params: {} }); - // The returned task should have null TTL + // The returned task should have null TTL (unlimited) expect(createdTask.ttl).toBeNull(); // Task should not be cleaned up even after a long time diff --git a/packages/core/test/shared/protocol.test.ts b/packages/core/test/shared/protocol.test.ts index b16a4453d..07d38c6d9 100644 --- a/packages/core/test/shared/protocol.test.ts +++ b/packages/core/test/shared/protocol.test.ts @@ -2489,12 +2489,12 @@ describe('Progress notification support for tasks', () => { // Set up a request handler that will complete the task protocol.setRequestHandler(CallToolRequestSchema, async (request, extra) => { - if (extra.taskStore) { - const task = await extra.taskStore.createTask({ ttl: 60000 }); + if (extra.task?.store) { + const task = await extra.task?.store.createTask({ ttl: 60000 }); // Simulate async work then complete the task setTimeout(async () => { - await extra.taskStore!.storeTaskResult(task.taskId, 'completed', { + await extra.task?.store!.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: 'Done' }] }); }, 50); diff --git a/packages/server/src/experimental/tasks/mcp-server.ts b/packages/server/src/experimental/tasks/mcp-server.ts index 6fd5a6cc5..b21e2b9db 100644 --- a/packages/server/src/experimental/tasks/mcp-server.ts +++ b/packages/server/src/experimental/tasks/mcp-server.ts @@ -56,15 +56,15 @@ export class ExperimentalMcpServerTasks { * execution: { taskSupport: 'required' } * }, { * createTask: async (args, extra) => { - * const task = await extra.taskStore.createTask({ ttl: 300000 }); - * startBackgroundWork(task.taskId, args); - * return { task }; + * const newTask = await extra.task.store.createTask({ ttl: 300000 }); + * startBackgroundWork(newTask.taskId, args); + * return { task: newTask }; * }, * getTask: async (args, extra) => { - * return extra.taskStore.getTask(extra.taskId); + * return extra.task.store.getTask(extra.task.id); * }, * getTaskResult: async (args, extra) => { - * return extra.taskStore.getTaskResult(extra.taskId); + * return extra.task.store.getTaskResult(extra.task.id); * } * }); * ``` diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index 8564212c1..9e26cb55d 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -332,10 +332,10 @@ export class McpServer { const isTaskHandler = 'createTask' in handler; if (isTaskHandler) { - if (!extra.taskStore) { + if (!extra.task?.store) { throw new Error('No task store provided.'); } - const taskExtra = { ...extra, taskStore: extra.taskStore }; + const taskExtra = { ...extra, task: extra.task }; if (tool.inputSchema) { const typedHandler = handler as ToolTaskHandler; @@ -367,14 +367,14 @@ export class McpServer { request: RequestT, extra: RequestHandlerExtra ): Promise { - if (!extra.taskStore) { + if (!extra.task?.store) { throw new Error('No task store provided for task-capable tool.'); } // Validate input and create task const args = await this.validateToolInput(tool, request.params.arguments, request.params.name); const handler = tool.handler as ToolTaskHandler; - const taskExtra = { ...extra, taskStore: extra.taskStore }; + const taskExtra = { ...extra, task: extra.task }; const createTaskResult: CreateTaskResult = args // undefined only if tool.inputSchema is undefined ? await Promise.resolve((handler as ToolTaskHandler).createTask(args, taskExtra)) @@ -388,7 +388,7 @@ export class McpServer { while (task.status !== 'completed' && task.status !== 'failed' && task.status !== 'cancelled') { await new Promise(resolve => setTimeout(resolve, pollInterval)); - const updatedTask = await extra.taskStore.getTask(taskId); + const updatedTask = await extra.task.store.getTask(taskId); if (!updatedTask) { throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`); } @@ -396,7 +396,7 @@ export class McpServer { } // Return the final result - return (await extra.taskStore.getTaskResult(taskId)) as CallToolResult; + return (await extra.task.store.getTaskResult(taskId)) as CallToolResult; } private _completionHandlerInitialized = false; diff --git a/test/integration/test/client/client.test.ts b/test/integration/test/client/client.test.ts index 5574a2d84..89ecd4da1 100644 --- a/test/integration/test/client/client.test.ts +++ b/test/integration/test/client/client.test.ts @@ -2370,26 +2370,26 @@ describe('Task-based execution', () => { }, { async createTask(_args, extra) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); const result = { content: [{ type: 'text', text: 'Tool executed successfully!' }] }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task!.store.getTask(extra.task!.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task!.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task!.store.getTaskResult(extra.task!.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -2446,26 +2446,26 @@ describe('Task-based execution', () => { }, { async createTask(_args, extra) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); const result = { content: [{ type: 'text', text: 'Success!' }] }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task!.store.getTask(extra.task!.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task!.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task!.store.getTaskResult(extra.task!.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -2523,26 +2523,26 @@ describe('Task-based execution', () => { }, { async createTask(_args, extra) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); const result = { content: [{ type: 'text', text: 'Result data!' }] }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task!.store.getTask(extra.task!.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task!.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task!.store.getTaskResult(extra.task!.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -2604,26 +2604,26 @@ describe('Task-based execution', () => { }, { async createTask(_args, extra) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); const result = { content: [{ type: 'text', text: 'Success!' }] }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task!.store.getTask(extra.task!.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task!.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task!.store.getTaskResult(extra.task!.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -2707,11 +2707,11 @@ describe('Task-based execution', () => { }; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2800,11 +2800,11 @@ describe('Task-based execution', () => { }; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2892,11 +2892,11 @@ describe('Task-based execution', () => { }; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2983,11 +2983,11 @@ describe('Task-based execution', () => { }; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -3089,26 +3089,26 @@ describe('Task-based execution', () => { }, { async createTask({ id }, extra) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); const result = { content: [{ type: 'text', text: `Result for ${id || 'unknown'}` }] }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task!.store.getTask(extra.task!.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task!.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task!.store.getTaskResult(extra.task!.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -3357,26 +3357,26 @@ test('should respect server task capabilities', async () => { }, { async createTask(_args, extra) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); const result = { content: [{ type: 'text', text: 'Success!' }] }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task!.store.getTask(extra.task!.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task!.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task!.store.getTaskResult(extra.task!.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } diff --git a/test/integration/test/experimental/tasks/task.test.ts b/test/integration/test/experimental/tasks/task.test.ts index 8a99fe513..6254560d3 100644 --- a/test/integration/test/experimental/tasks/task.test.ts +++ b/test/integration/test/experimental/tasks/task.test.ts @@ -1,5 +1,5 @@ -import { isTerminal } from '@modelcontextprotocol/core'; -import type { Task } from '@modelcontextprotocol/server'; +import { isTerminal, TaskCreationParamsSchema } from '@modelcontextprotocol/core'; +import type { Task } from '@modelcontextprotocol/core'; import { describe, expect, it } from 'vitest'; describe('Task utility functions', () => { @@ -115,3 +115,30 @@ describe('Task Schema Validation', () => { }); }); }); + +describe('TaskCreationParams Schema Validation', () => { + it('should accept ttl as a number', () => { + const result = TaskCreationParamsSchema.safeParse({ ttl: 60000 }); + expect(result.success).toBe(true); + }); + + it('should accept missing ttl (optional)', () => { + const result = TaskCreationParamsSchema.safeParse({}); + expect(result.success).toBe(true); + }); + + it('should reject null ttl (not allowed in request, only response)', () => { + const result = TaskCreationParamsSchema.safeParse({ ttl: null }); + expect(result.success).toBe(false); + }); + + it('should accept pollInterval as a number', () => { + const result = TaskCreationParamsSchema.safeParse({ pollInterval: 1000 }); + expect(result.success).toBe(true); + }); + + it('should accept both ttl and pollInterval', () => { + const result = TaskCreationParamsSchema.safeParse({ ttl: 60000, pollInterval: 1000 }); + expect(result.success).toBe(true); + }); +}); diff --git a/test/integration/test/server.test.ts b/test/integration/test/server.test.ts index fcac6cc45..e6b00e581 100644 --- a/test/integration/test/server.test.ts +++ b/test/integration/test/server.test.ts @@ -2251,8 +2251,8 @@ describe('Task-based execution', () => { }, { async createTask(_args, extra) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); // Simulate some async work @@ -2261,20 +2261,20 @@ describe('Task-based execution', () => { const result = { content: [{ type: 'text', text: 'Tool executed successfully!' }] }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); })(); return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task!.store.getTask(extra.task!.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task!.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task!.store.getTaskResult(extra.task!.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -2446,9 +2446,9 @@ describe('Task-based execution', () => { let taskId: string | undefined; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const createdTask = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const createdTask = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); taskId = createdTask.taskId; } @@ -2473,8 +2473,8 @@ describe('Task-based execution', () => { }, { async createTask(_args, extra) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); // Perform async work that makes a nested request @@ -2506,20 +2506,20 @@ describe('Task-based execution', () => { } ] }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); })(); return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task!.store.getTask(extra.task!.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task!.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task!.store.getTaskResult(extra.task!.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -2603,11 +2603,11 @@ describe('Task-based execution', () => { }; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2684,11 +2684,11 @@ describe('Task-based execution', () => { }; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2763,11 +2763,11 @@ describe('Task-based execution', () => { }; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2844,11 +2844,11 @@ describe('Task-based execution', () => { }; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2952,8 +2952,8 @@ describe('Task-based execution', () => { }, { async createTask({ delay, taskNum }, extra) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); // Simulate async work @@ -2962,20 +2962,20 @@ describe('Task-based execution', () => { const result = { content: [{ type: 'text', text: `Completed task ${taskNum || 'unknown'}` }] }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); })(); return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task!.store.getTask(extra.task!.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task!.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task!.store.getTaskResult(extra.task!.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -3187,11 +3187,11 @@ test('should respect client task capabilities', async () => { }; // Check if task creation is requested - if (request.params.task && extra.taskStore) { - const task = await extra.taskStore.createTask({ - ttl: extra.taskRequestedTtl + if (request.params.task && extra.task!.store) { + const task = await extra.task!.store.createTask({ + ttl: extra.task?.requestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.task!.store.storeTaskResult(task.taskId, 'completed', result); // Return CreateTaskResult when task creation is requested return { task }; } diff --git a/test/integration/test/server/mcp.test.ts b/test/integration/test/server/mcp.test.ts index f7bcececc..bc8c7b743 100644 --- a/test/integration/test/server/mcp.test.ts +++ b/test/integration/test/server/mcp.test.ts @@ -1901,16 +1901,16 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async (_args, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60000 }); + const task = await extra.task.store.createTask({ ttl: 60000 }); return { task }; }, getTask: async (_args, extra) => { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) throw new Error('Task not found'); return task; }, getTaskResult: async (_args, extra) => { - return (await extra.taskStore.getTaskResult(extra.taskId)) as CallToolResult; + return (await extra.task.store.getTaskResult(extra.task.id)) as CallToolResult; } } ); @@ -1970,16 +1970,16 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async (_args, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60000 }); + const task = await extra.task.store.createTask({ ttl: 60000 }); return { task }; }, getTask: async (_args, extra) => { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) throw new Error('Task not found'); return task; }, getTaskResult: async (_args, extra) => { - return (await extra.taskStore.getTaskResult(extra.taskId)) as CallToolResult; + return (await extra.task.store.getTaskResult(extra.task.id)) as CallToolResult; } } ); @@ -6300,10 +6300,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async ({ input }, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async work setTimeout(async () => { @@ -6315,14 +6315,14 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { return { task }; }, getTask: async (_args, extra) => { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { throw new Error('Task not found'); } return task; }, getTaskResult: async (_input, extra) => { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as CallToolResult; } } @@ -6405,10 +6405,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async ({ value }, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async work setTimeout(async () => { @@ -6421,14 +6421,14 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { return { task }; }, getTask: async (_args, extra) => { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { throw new Error('Task not found'); } return task; }, getTaskResult: async (_value, extra) => { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as CallToolResult; } } @@ -6513,10 +6513,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async ({ data }, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async work setTimeout(async () => { @@ -6529,14 +6529,14 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { return { task }; }, getTask: async (_args, extra) => { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { throw new Error('Task not found'); } return task; }, getTaskResult: async (_data, extra) => { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as CallToolResult; } } @@ -6630,10 +6630,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async extra => { - const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async failure setTimeout(async () => { @@ -6647,14 +6647,14 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { return { task }; }, getTask: async extra => { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { throw new Error('Task not found'); } return task; }, getTaskResult: async extra => { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as CallToolResult; } } @@ -6736,10 +6736,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async extra => { - const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async cancellation setTimeout(async () => { @@ -6750,14 +6750,14 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { return { task }; }, getTask: async extra => { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { throw new Error('Task not found'); } return task; }, getTaskResult: async extra => { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as CallToolResult; } } @@ -6823,18 +6823,18 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async (_args, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); return { task }; }, getTask: async (_args, extra) => { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { throw new Error('Task not found'); } return task; }, getTaskResult: async (_args, extra) => { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as CallToolResult; } } diff --git a/test/integration/test/taskLifecycle.test.ts b/test/integration/test/taskLifecycle.test.ts index 216479e93..c5b4b1a3a 100644 --- a/test/integration/test/taskLifecycle.test.ts +++ b/test/integration/test/taskLifecycle.test.ts @@ -64,7 +64,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ duration, shouldFail }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); @@ -75,12 +75,12 @@ describe('Task Lifecycle Integration Tests', () => { try { if (shouldFail) { - await extra.taskStore.storeTaskResult(task.taskId, 'failed', { + await extra.task.store.storeTaskResult(task.taskId, 'failed', { content: [{ type: 'text', text: 'Task failed as requested' }], isError: true }); } else { - await extra.taskStore.storeTaskResult(task.taskId, 'completed', { + await extra.task.store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Completed after ${duration}ms` }] }); } @@ -92,14 +92,14 @@ describe('Task Lifecycle Integration Tests', () => { return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -117,7 +117,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ userName }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); @@ -153,7 +153,7 @@ describe('Task Lifecycle Integration Tests', () => { ? elicitationResult.content.userName : 'Unknown'; try { - await extra.taskStore.storeTaskResult(task.taskId, 'completed', { + await extra.task.store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Hello, ${name}!` }] }); } catch { @@ -162,7 +162,7 @@ describe('Task Lifecycle Integration Tests', () => { } else { // Complete immediately if userName was provided try { - await extra.taskStore.storeTaskResult(task.taskId, 'completed', { + await extra.task.store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Hello, ${userName}!` }] }); } catch { @@ -174,14 +174,14 @@ describe('Task Lifecycle Integration Tests', () => { return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -421,7 +421,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ requestCount }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); @@ -460,7 +460,7 @@ describe('Task Lifecycle Integration Tests', () => { // Complete with all responses try { - await extra.taskStore.storeTaskResult(task.taskId, 'completed', { + await extra.task.store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Received responses: ${responses.join(', ')}` }] }); } catch { @@ -471,14 +471,14 @@ describe('Task Lifecycle Integration Tests', () => { return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -910,7 +910,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ messageCount }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); @@ -960,14 +960,14 @@ describe('Task Lifecycle Integration Tests', () => { return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -1108,7 +1108,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ messageCount, delayBetweenMessages }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); @@ -1154,7 +1154,7 @@ describe('Task Lifecycle Integration Tests', () => { // Complete with all responses try { - await extra.taskStore.storeTaskResult(task.taskId, 'completed', { + await extra.task.store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Received all responses: ${responses.join(', ')}` }] }); } catch { @@ -1163,7 +1163,7 @@ describe('Task Lifecycle Integration Tests', () => { } catch (error) { // Handle errors try { - await extra.taskStore.storeTaskResult(task.taskId, 'failed', { + await extra.task.store.storeTaskResult(task.taskId, 'failed', { content: [{ type: 'text', text: `Error: ${error}` }], isError: true }); @@ -1176,14 +1176,14 @@ describe('Task Lifecycle Integration Tests', () => { return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as { content: Array<{ type: 'text'; text: string }> }; } } @@ -1324,7 +1324,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ messageCount }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60000, pollInterval: 100 }); @@ -1363,7 +1363,7 @@ describe('Task Lifecycle Integration Tests', () => { // Complete the task after all messages are queued try { - await extra.taskStore.storeTaskResult(task.taskId, 'completed', { + await extra.task.store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: 'Task completed quickly' }] }); } catch { @@ -1372,7 +1372,7 @@ describe('Task Lifecycle Integration Tests', () => { } catch (error) { // Handle errors try { - await extra.taskStore.storeTaskResult(task.taskId, 'failed', { + await extra.task.store.storeTaskResult(task.taskId, 'failed', { content: [{ type: 'text', text: `Error: ${error}` }], isError: true }); @@ -1385,14 +1385,14 @@ describe('Task Lifecycle Integration Tests', () => { return { task }; }, async getTask(_args, extra) { - const task = await extra.taskStore.getTask(extra.taskId); + const task = await extra.task.store.getTask(extra.task.id); if (!task) { - throw new Error(`Task ${extra.taskId} not found`); + throw new Error(`Task ${extra.task.id} not found`); } return task; }, async getTaskResult(_args, extra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); + const result = await extra.task.store.getTaskResult(extra.task.id); return result as { content: Array<{ type: 'text'; text: string }> }; } }