diff --git a/examples/server/src/simpleStreamableHttp.ts b/examples/server/src/simpleStreamableHttp.ts index e016c217f..c814609b9 100644 --- a/examples/server/src/simpleStreamableHttp.ts +++ b/examples/server/src/simpleStreamableHttp.ts @@ -484,16 +484,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', @@ -505,14 +505,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 def841832..d16166fd6 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 ab657f0dc..6d36ce3e3 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/mcpServer.ts b/packages/server/src/experimental/tasks/mcpServer.ts index 6fd5a6cc5..b21e2b9db 100644 --- a/packages/server/src/experimental/tasks/mcpServer.ts +++ b/packages/server/src/experimental/tasks/mcpServer.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 975cca257..2aa0d7018 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -330,10 +330,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; @@ -365,14 +365,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)) @@ -386,7 +386,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`); } @@ -394,7 +394,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 ed1ea7d67..3ebbe282b 100644 --- a/test/integration/test/client/client.test.ts +++ b/test/integration/test/client/client.test.ts @@ -2443,26 +2443,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 }> }; } } @@ -2519,26 +2519,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 }> }; } } @@ -2596,26 +2596,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 }> }; } } @@ -2677,26 +2677,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 }> }; } } @@ -2780,11 +2780,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 }; } @@ -2873,11 +2873,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 }; } @@ -2965,11 +2965,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 }; } @@ -3056,11 +3056,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 }; } @@ -3162,26 +3162,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 }> }; } } @@ -3430,26 +3430,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 848e22c98..d2aca2cc0 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 type { Task } from '@modelcontextprotocol/core'; +import { isTerminal, TaskCreationParamsSchema } 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: 60_000 }); + 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: 60_000, pollInterval: 1000 }); + expect(result.success).toBe(true); + }); +}); diff --git a/test/integration/test/server.test.ts b/test/integration/test/server.test.ts index 0b89898ba..e7d4df109 100644 --- a/test/integration/test/server.test.ts +++ b/test/integration/test/server.test.ts @@ -2249,8 +2249,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 @@ -2259,20 +2259,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 }> }; } } @@ -2444,9 +2444,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; } @@ -2471,8 +2471,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 @@ -2504,20 +2504,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 }> }; } } @@ -2601,11 +2601,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 }; } @@ -2682,11 +2682,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 }; } @@ -2761,11 +2761,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 }; } @@ -2842,11 +2842,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 }; } @@ -2950,8 +2950,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 @@ -2960,20 +2960,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 }> }; } } @@ -3185,11 +3185,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 5d811848b..0f92b6a5c 100644 --- a/test/integration/test/server/mcp.test.ts +++ b/test/integration/test/server/mcp.test.ts @@ -1793,16 +1793,16 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async (_args, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60_000 }); + const task = await extra.task.store.createTask({ ttl: 60_000 }); 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; } } ); @@ -1862,16 +1862,16 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async (_args, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60_000 }); + const task = await extra.task.store.createTask({ ttl: 60_000 }); 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; } } ); @@ -6225,10 +6225,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async ({ input }, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60_000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async work setTimeout(async () => { @@ -6240,14 +6240,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; } } @@ -6330,10 +6330,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async ({ value }, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60_000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async work setTimeout(async () => { @@ -6346,14 +6346,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; } } @@ -6438,10 +6438,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async ({ data }, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60_000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async work setTimeout(async () => { @@ -6454,14 +6454,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; } } @@ -6555,10 +6555,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async extra => { - const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60_000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async failure setTimeout(async () => { @@ -6572,14 +6572,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; } } @@ -6661,10 +6661,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async extra => { - const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60_000, pollInterval: 100 }); // Capture taskStore for use in setTimeout - const store = extra.taskStore; + const store = extra.task.store; // Simulate async cancellation setTimeout(async () => { @@ -6675,14 +6675,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; } } @@ -6748,18 +6748,18 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { }, { createTask: async (_args, extra) => { - const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 }); + const task = await extra.task.store.createTask({ ttl: 60_000, 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 148082c93..5963f8b97 100644 --- a/test/integration/test/taskLifecycle.test.ts +++ b/test/integration/test/taskLifecycle.test.ts @@ -65,7 +65,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: 60_000, pollInterval: 100 }); @@ -76,11 +76,11 @@ describe('Task Lifecycle Integration Tests', () => { try { await (shouldFail - ? extra.taskStore.storeTaskResult(task.taskId, 'failed', { + ? extra.task.store.storeTaskResult(task.taskId, 'failed', { content: [{ type: 'text', text: 'Task failed as requested' }], isError: true }) - : extra.taskStore.storeTaskResult(task.taskId, 'completed', { + : extra.task.store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Completed after ${duration}ms` }] })); } catch { @@ -91,14 +91,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 }> }; } } @@ -116,7 +116,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ userName }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60_000, pollInterval: 100 }); @@ -129,7 +129,7 @@ describe('Task Lifecycle Integration Tests', () => { if (userName) { // 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 { @@ -161,7 +161,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 { @@ -173,14 +173,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 }> }; } } @@ -420,7 +420,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ requestCount }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60_000, pollInterval: 100 }); @@ -459,7 +459,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 { @@ -470,14 +470,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 }> }; } } @@ -909,7 +909,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ messageCount }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60_000, pollInterval: 100 }); @@ -959,14 +959,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 }> }; } } @@ -1107,7 +1107,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: 60_000, pollInterval: 100 }); @@ -1153,7 +1153,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 { @@ -1162,7 +1162,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 }); @@ -1175,14 +1175,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 }> }; } } @@ -1323,7 +1323,7 @@ describe('Task Lifecycle Integration Tests', () => { }, { async createTask({ messageCount }, extra) { - const task = await extra.taskStore.createTask({ + const task = await extra.task.store.createTask({ ttl: 60_000, pollInterval: 100 }); @@ -1362,7 +1362,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 { @@ -1371,7 +1371,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 }); @@ -1384,14 +1384,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 }> }; } }