Skip to content

Commit f27cb52

Browse files
committed
fix: disallow null (infinite) requested TTL
The specification only allows the server to return a null TTL, it doesn't allow the client to request that. This is intentional, as it forces clients to make a choice betwen a specific TTL or not caring what the TTL is, rather than defaulting to keeping tasks forever all the time. Allowing the requested value to be null was an implementation oversight that diverged from the spec.
1 parent 384311b commit f27cb52

File tree

4 files changed

+36
-10
lines changed

4 files changed

+36
-10
lines changed

src/shared/protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ export type RequestHandlerExtra<SendRequestT extends Request, SendNotificationT
265265

266266
taskStore?: RequestTaskStore;
267267

268-
taskRequestedTtl?: number | null;
268+
taskRequestedTtl?: number;
269269

270270
/**
271271
* The original HTTP request.

src/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@ export const CursorSchema = z.string();
3535
*/
3636
export const TaskCreationParamsSchema = z.looseObject({
3737
/**
38-
* Time in milliseconds to keep task results available after completion.
39-
* If null, the task has unlimited lifetime until manually cleaned up.
38+
* Requested duration in milliseconds to retain task from creation.
4039
*/
41-
ttl: z.union([z.number(), z.null()]).optional(),
40+
ttl: z.number().optional(),
4241

4342
/**
4443
* Time in milliseconds to wait between task status requests.

test/experimental/tasks/stores/in-memory.test.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,17 +487,16 @@ describe('InMemoryTaskStore', () => {
487487
expect(task).toBeNull();
488488
});
489489

490-
it('should support null TTL for unlimited lifetime', async () => {
491-
// Test that null TTL means unlimited lifetime
492-
const taskParams: TaskCreationParams = {
493-
ttl: null
494-
};
490+
it('should support omitted TTL for unlimited lifetime', async () => {
491+
// Test that omitting TTL means unlimited lifetime (server returns null)
492+
// Per spec: clients omit ttl to let server decide, server returns null for unlimited
493+
const taskParams: TaskCreationParams = {};
495494
const createdTask = await store.createTask(taskParams, 2222, {
496495
method: 'tools/call',
497496
params: {}
498497
});
499498

500-
// The returned task should have null TTL
499+
// The returned task should have null TTL (unlimited)
501500
expect(createdTask.ttl).toBeNull();
502501

503502
// Task should not be cleaned up even after a long time

test/experimental/tasks/task.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, it, expect } from 'vitest';
22
import { isTerminal } from '../../../src/experimental/tasks/interfaces.js';
33
import type { Task } from '../../../src/types.js';
4+
import { TaskCreationParamsSchema } from '../../../src/types.js';
45

56
describe('Task utility functions', () => {
67
describe('isTerminal', () => {
@@ -115,3 +116,30 @@ describe('Task Schema Validation', () => {
115116
});
116117
});
117118
});
119+
120+
describe('TaskCreationParams Schema Validation', () => {
121+
it('should accept ttl as a number', () => {
122+
const result = TaskCreationParamsSchema.safeParse({ ttl: 60000 });
123+
expect(result.success).toBe(true);
124+
});
125+
126+
it('should accept missing ttl (optional)', () => {
127+
const result = TaskCreationParamsSchema.safeParse({});
128+
expect(result.success).toBe(true);
129+
});
130+
131+
it('should reject null ttl (not allowed in request, only response)', () => {
132+
const result = TaskCreationParamsSchema.safeParse({ ttl: null });
133+
expect(result.success).toBe(false);
134+
});
135+
136+
it('should accept pollInterval as a number', () => {
137+
const result = TaskCreationParamsSchema.safeParse({ pollInterval: 1000 });
138+
expect(result.success).toBe(true);
139+
});
140+
141+
it('should accept both ttl and pollInterval', () => {
142+
const result = TaskCreationParamsSchema.safeParse({ ttl: 60000, pollInterval: 1000 });
143+
expect(result.success).toBe(true);
144+
});
145+
});

0 commit comments

Comments
 (0)