[v1.x] fix: disallow null (infinite) requested TTL (#1339)

Co-authored-by: Konstantin Konstantinov <KKonstantinov@users.noreply.github.com>
This commit is contained in:
Luca Chang 2026-03-25 08:57:53 -07:00 • committed by GitHub
commit 2a15851302
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 36 additions and 10 deletions

View file

@ -265,7 +265,7 @@ export type RequestHandlerExtra<SendRequestT extends Request, SendNotificationT
taskStore?: RequestTaskStore;
taskRequestedTtl?: number | null;
taskRequestedTtl?: number;
/**
* The original HTTP request.

View file

@ -35,10 +35,9 @@ export const CursorSchema = z.string();
*/
export const TaskCreationParamsSchema = z.looseObject({
/**
* Time in milliseconds to keep task results available after completion.
* If null, the task has unlimited lifetime until manually cleaned up.
* Requested duration in milliseconds to retain task from creation.
*/
ttl: z.union([z.number(), z.null()]).optional(),
ttl: z.number().optional(),
/**
* Time in milliseconds to wait between task status requests.

View file

@ -487,17 +487,16 @@ describe('InMemoryTaskStore', () => {
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

View file

@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest';
import { isTerminal } from '../../../src/experimental/tasks/interfaces.js';
import type { Task } from '../../../src/types.js';
import { TaskCreationParamsSchema } from '../../../src/types.js';
describe('Task utility functions', () => {
describe('isTerminal', () => {
@ -115,3 +116,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);
});
});