Key takeaways
- TaskCreationParamsSchema accepted ttl: 0 and ttl: -10 in both clean SDK 1.30.0 runs; the installed validator was a plain optional Zod number.
- InMemoryTaskStore returned ttl: 0 but its truthiness guard created no cleanup timer. The task remained available through getTask and listTasks after 80.4 ms and 79.4 ms, until the harness called cleanup.
- The same store returned ttl: -10 and Node coerced its negative timer to 1 ms; the tasks disappeared after 2.0 and 2.5 ms and emitted TimeoutNegativeWarning.
- An 80 ms task completed around 51–52 ms, remained present around 91–92 ms from creation, and disappeared around 133 ms because storing the result restarted the full 80 ms timer.
- Treat task TTL as a data-retention and recovery contract for AI tool results. Validate it, define one clock origin, verify actual deletion, and keep production state out of the demonstration store.
The sharp question: what does zero milliseconds retain?
A durable MCP task can hold an AI agent’s tool arguments, status, output and diagnostic state after the initiating request is gone. TTL therefore controls two opposing needs: preserving enough evidence to recover an expensive result, and deleting sensitive or stale state when its declared lifetime ends.
In the SDK 1.30.0 demonstration store, ttl: 0 did not mean immediate expiry. It meant no automatic cleanup timer at all. A negative value took the opposite path and became an almost-immediate Node timer. A positive value changed clock origin when the result arrived.
Chronology: one field accumulated three meanings
The task proposal opened October 20, 2025 and entered the 2025-11-25 protocol release as an experimental durable state machine. That specification described requested lifetime and returned retention from task creation. A December 26 SDK pull request removed null from client requests so clients could omit TTL but not request an unlimited lifetime.
On March 25, 2026, that SDK change merged. The same day, protocol pull request 2408 merged a fix allowing null in generated response schema, where null means unlimited. SDK 1.30.0 shipped July 27. This reproduction ran September 3 against the published package.
The protocol starts the clock at creation
The task-creation section says requestors may ask for a lifetime duration in milliseconds since creation. Retention guidance says receivers may override that request, must include the actual ttl duration—or null for unlimited—in task responses, and may delete the task and result after the TTL lifetime has elapsed regardless of status.
Deletion is a MAY, not a mandatory deadline. This experiment therefore does not claim that every extra millisecond is a protocol violation. The narrower finding is that a store returned values described as duration from creation while its actual cleanup policy used zero as no timer and restarted positive timers from completion.
TTL is an AI data boundary, not timer trivia
A long-running agent task may contain retrieved documents, code patches, database extracts, model-generated drafts or failure details. Under-retention can strand a caller that reconnects after expensive work. Over-retention enlarges the period in which task metadata or results remain available, especially when authorization context is weak.
The MCP security guidance explicitly suggests shorter TTLs to reduce exposure where tasks cannot be bound to an identified requestor. A returned zero that acts like unlimited storage defeats that operational intuition even if the demonstration store is never intended for production.
The validator accepted both sides of zero
The installed TaskCreationParamsSchema represented ttl as z.number().optional(). It did not require an integer, a positive value or a nonnegative value. Both safeParse({ ttl: 0 }) and safeParse({ ttl: -10 }) succeeded in both runs.
The protocol’s pinned TypeScript interface likewise uses number without a numeric refinement. That establishes an input gap, not an instruction that zero or negative duration has useful semantics. A receiver still owns the actual lifetime it returns and enforces.
The store used truthiness as retention policy
InMemoryTaskStore sets actualTtl to the requested value when present, otherwise null. It returns that value in Task.ttl. Its creation path then schedules cleanup under if (actualTtl), a JavaScript truthiness check rather than an explicit finite-positive validation.
Zero is falsy, so no timer is entered into cleanupTimers. Negative ten is truthy, so it reaches setTimeout. The same field is returned unchanged even though those branches do not describe comparable retention durations.
The fixture tested storage, not a model
Each scenario constructed the public InMemoryTaskStore and a synthetic tools/call request named synthetic_inference. No inference occurred. The name makes the control-plane consequence concrete without pretending that model behavior was measured.
The zero case waited 75 ms, then checked both getTask and listTasks. The negative case polled until the record disappeared. The positive case created ttl: 80, waited 50 ms, stored a completed synthetic result, checked after another 40 ms and then measured deletion.
Zero remained visible until manual cleanup
Run one checked at 80.4 ms; run two checked at 79.4 ms. In both, getTask returned the zero-TTL task and listTasks included it. The harness then called the store’s cleanup method so the test left no retained state.
The measured wait is not a proof of eternal process lifetime. The implementation evidence supplies the stronger boundary: no TTL timer was scheduled for zero. The record remains until explicit cleanup, store loss, process exit or another untested operation removes it.
Negative ten became roughly one millisecond
Both negative-TTL tasks returned ttl: -10. Node emitted TimeoutNegativeWarning, stating that the duration was set to 1. The polling loop observed deletion after 2.0 ms and 2.5 ms.
Those elapsed measurements include scheduling and polling overhead; they are not precision timer benchmarks. They corroborate Node’s documented coercion and show that an accepted negative duration became rapid deletion instead of a rejected task-creation request.
Completion moved the positive clock
The 80 ms task completed at 52.1 ms in run one and 51.4 ms in run two. The store cleared the creation timer and started another 80 ms timer when storeTaskResult saved the terminal result. At approximately 92.1 ms and 91.4 ms from creation, each task was still present.
Deletion arrived at 132.8 ms and 133.4 ms from creation, leaving the result for 80.7 ms and 82.0 ms after completion. The return value remained ttl: 80; it did not disclose that the enforced clock had moved.
The source comment confirms the reset is intentional code
The distributed store says “Reset cleanup timer to start from now” in storeTaskResult. updateTaskStatus has a parallel terminal-state branch that also clears and replaces the timer. This was not an accidental artifact of the harness’s polling loop.
The store’s interface commentary, however, describes TTL as time to keep results after completion, while TaskCreationParamsSchema says duration from creation. That documentation split helps explain the implementation, but it does not give one returned Task.ttl two clock origins on the wire.
Independent work found adjacent representation mismatches
Protocol pull request 2408 reported that generated JSON schema rejected null even though the source and documentation used null for unlimited response retention. Its author traced that mismatch to schema generation and supplied a surgical nullable annotation. This is independent evidence that TTL representation had already diverged across artifacts.
SDK pull request 1339 addressed the other direction: clients should omit ttl rather than request null, while receivers may return null. Neither contributor tested zero, negative values or completion-time reset. No independent public reproduction of the exact three observations was found, so these two clean local runs remain the only evidence for those outcomes in this article.
Do not promote the demonstration store by accident
The SDK labels InMemoryTaskStore experimental, for demonstration, and unsuitable for production because restart loses all data. Yet examples have a habit of becoming defaults. A service can inherit retention ambiguity if it wraps the store, ports its timer logic into Redis or uses it in a supposedly temporary internal deployment.
Production task storage needs an explicit expiry timestamp persisted with the record, not only an in-process callback timer. Multi-instance services also need atomic ownership, authorization binding, durable status transitions and deletion behavior that survives restarts and clock changes.
Choose and expose one clock origin
If TTL means duration from creation, compute expiresAt = createdAt + acceptedTtl once and do not move it when status changes. If the product instead promises a post-completion retrieval window, expose that as a separate contract and account for a maximum execution lifetime. Do not silently reuse one wire field for both.
Return the accepted value after enforcing local minimum and maximum policy. Reject non-finite, fractional, zero and negative values unless the protocol profile explicitly assigns them tested semantics. Use null only for a deliberate, authorized unlimited response lifetime.
Verify deletion as a distributed invariant
Test just before and after expiry for working, input-required, completed, failed and cancelled states. Repeat through get, result, list, reconnect and every replica. Restart the service between creation and expiry. Verify that indexes, queues, caches, logs and object payloads do not outlive the record’s declared policy.
Measure requested TTL, accepted TTL, createdAt, expiresAt, terminalAt, deletedAt and deletion lag. Alert on zero or negative inputs, null lifetimes, timer overflow, records visible after expiry, results deleted before the recovery objective and stores whose policy changes after status transition.
Separate recovery time from privacy limits
A team may need ten minutes for an agent to reconnect while policy allows raw tool output for only one hour. Encode both requirements: a minimum recovery window and a maximum retention boundary. Encrypt sensitive results, bind every operation to authorization context and minimize stored arguments and diagnostics.
Where context binding is unavailable, do not advertise tasks/list and prefer shorter bounded retention, as the specification advises. Cryptographically strong task IDs reduce guessing; they do not erase data, authorize a caller or make an open listing safe.
What this run does not establish
The experiment did not use a language model, production agent, McpServer, Client, HTTP, stdio, network, database, Redis, multiple processes, concurrent tasks, authentication, authorization context, crash recovery, wall-clock manipulation, browser, credential, payment, external action or user data. It directly exercised one public demonstration-store class.
It does not establish a vulnerability, exploitability, prevalence, memory cost, data breach, production impact or behavior in another SDK, runtime, store or protocol version. Millisecond timings vary with scheduling. The protocol permits receivers to delete after expiry rather than requiring an exact deletion instant, and MCP tasks remain experimental.
Decision: deploy only a measured retention contract
Deploy durable MCP tasks only when the receiver rejects ambiguous numeric TTLs, returns the lifetime it actually enforces, exposes one clock origin, binds records to authorization context, persists expiry, verifies deletion across every access path and retains results long enough for the agent’s tested recovery objective.
Circuit-break new task creation when zero acts unlimited, negative input becomes a timer, completion silently extends retention, expired results remain enumerable, active work disappears before policy allows, or operators cannot reconcile accepted and actual expiry. An AI agent’s durable result is data with a lifecycle—not a JavaScript truthiness branch.
Copy-ready MCP task retention gate
Complete this record before an AI agent service stores durable MCP task state or results.
Entries stay in this browser tab and are not submitted to AccessAllGPT. Blank responses are copied as [Unresolved].
Protocol, SDK, store, runtime, transport, tool, data classes, consequence class and authorization context.
Allowed units, integer/finite constraints, minimum, maximum, omission, null, zero, negative and overflow handling.
Creation-time or completion-time origin, immutable expiresAt calculation, monotonic versus wall-clock behavior and disclosed semantics.
Maximum task duration, reconnect window, result retrieval path, task ID persistence, retry behavior and minimum useful retention.
Maximum retention by data class, encryption, minimization, authorization binding, list policy and deletion obligations.
Database TTL/index, queue/cache cleanup, replica behavior, restart recovery, tombstones, deletion lag and verification query.
Working, input-required, completed, failed and cancelled before/after expiry; get, result, list, reconnect and concurrent transitions.
Requested/accepted TTL, createdAt, terminalAt, expiresAt, deletedAt, deletion lag, caller identity and anomalous-value alerts.
Exact SDK, runtime, store implementation, source/package hashes, protocol version and regression-test output.
Unlimited zero, accepted negative, moved clock, early result loss, visibility after expiry, cross-context access or unverified cleanup.
Primary sources
Browse the publication-wide evidence index →
- Tasks (protocol version 2025-11-25)Model Context Protocol Specification · Reviewed: Task creation, result retrieval, task retention, TTL semantics, listing, authorization and security considerations · Retrieved · Supports: The task specification defines requested TTL as a duration from task creation, requires receivers to return the actual retention duration from creation, permits deletion after that lifetime, and recommends shorter TTLs where context binding is unavailable.
- MCP schema at the 2025-11-25 specification commitModel Context Protocol · Reviewed: TaskCreationParams, Task interface, requested and actual TTL descriptions, timestamps, statuses and task request shapes · Retrieved · Supports: The pinned TypeScript schema describes requested TTL as milliseconds to retain a task from creation and actual Task.ttl as the retention duration from creation, but supplies no positive or integer constraint in the interface.
- TypeScript SDK release 1.30.0Model Context Protocol TypeScript SDK · Reviewed: Release identity, publication timestamp, v1.x target, included changes and full-changelog reference · Retrieved · Supports: The official release record identifies SDK 1.30.0 as the v1.x package release published July 27, 2026 and anchors the tested implementation version.
- InMemoryTaskStore at SDK 1.30.0Model Context Protocol TypeScript SDK · Reviewed: Task creation, actualTtl selection, cleanup timer conditions, result storage, status updates, task listing and manual cleanup · Retrieved · Supports: The demonstration store schedules creation cleanup only when actualTtl is truthy, and restarts the full TTL when storing a result or entering a terminal status.
- Task schemas at SDK 1.30.0Model Context Protocol TypeScript SDK · Reviewed: TaskCreationParamsSchema, TaskMetadataSchema, TaskSchema, TTL field descriptions and generated request and result types · Retrieved · Supports: The distributed request validator represents ttl as z.number().optional() without a positive, nonnegative or integer refinement, while the returned task permits a number or null.
- @modelcontextprotocol/sdk 1.30.0 registry recordnpm Registry · Reviewed: Package version, repository, publication metadata, distribution integrity and runtime requirements · Retrieved · Supports: The registry record binds both clean installations to the published SDK 1.30.0 artifact rather than a moving branch or local checkout.
- Allow null for Task.ttl in generated JSON schemaModel Context Protocol pull request 2408 · Reviewed: Contributor problem report, generator root cause, schema patch, merged status, scope and null-retention semantics · Retrieved · Supports: An independent contributor found that generated JSON schema had rejected null even though the source defined null as unlimited retention; the March 25, 2026 merge repaired that representation mismatch, not the zero or timer behavior tested here.
- Disallow null (infinite) requested TTLModel Context Protocol TypeScript SDK pull request 1339 · Reviewed: Request and response TTL distinction, motivation, tests, breaking-change disclosure, patch and merge metadata · Retrieved · Supports: The SDK change distinguishes an omitted client request from a server-returned null lifetime and says clients should not request unlimited retention; it merged March 25, 2026 and provides independent history of TTL-shape ambiguity.
- Timers: scheduling timersNode.js Documentation · Reviewed: Callback setTimeout delay coercion, out-of-range and negative delay behavior, scheduling guarantees and timer cancellation · Retrieved · Supports: Node documents that negative, zero, non-finite and out-of-range callback delays are set to 1 ms and that exact callback timing is not guaranteed, which bounds interpretation of the negative-TTL observation.
Limitations
This is a bounded local reproduction on Node v24.10.0, Darwin arm64, @modelcontextprotocol/sdk 1.30.0 and Zod 3.25.76. It directly exercised the experimental demonstration InMemoryTaskStore with synthetic request records. The two runs observed zero-TTL visibility after 80.4 and 79.4 ms, negative-TTL deletion after 2.0 and 2.5 ms, and 80 ms positive-TTL deletion at 132.8 and 133.4 ms after results were stored around 52.1 and 51.4 ms. The run did not test a model, production agent, protocol client/server exchange, transport, database, Redis, distribution, concurrency, authentication, crash recovery, another implementation or user data. Timings are directional fixture observations, not performance or security benchmarks. The protocol permits rather than requires deletion after expiry. The evidence does not establish exploitability, prevalence, production harm or universal MCP behavior.
Disclosures
AccessAllGPT created and ran only the synthetic local fixture in the checked-in harness. Protocol pull request 2408 and TypeScript SDK pull request 1339 are independent contributor records about adjacent TTL representation boundaries; neither reproduces the zero, negative or completion-reset outcomes. No independent public reproduction of those exact outcomes was found. No model, production client, third-party server, credential, endpoint, inference workload, payment, external action or user data was used. The MCP project, contributors, SDK maintainers, Node.js project, npm and Zod maintainers did not review this article or provide access, private data, payment or endorsement. AccessAllGPT Research is operated by NeuralArc, is independent, and is not affiliated with OpenAI, Anthropic, the MCP project, Node.js, npm or Zod. Publication-wide relationships are listed on the disclosures page.
Further AccessAllGPT guidance
- An MCP Server Can Set Your AI Agent’s Polling Pace
- Stopping an MCP Client Did Not Stop the AI Agent’s Task
- An MCP Task Timeout Is Not an AI Agent Deadline
- A Failed MCP Task Can Hide the Error an AI Agent Needs
- An Optional MCP Task Schema Can Strand an Agent’s Completed Result
- MCP Servers: The Production Security Review
- AccessAllGPT Research methodology
- Publication disclosures
Continue the research
Get evidence-led updates for teams making production AI decisions.