Key takeaways

  • The SDK 1.30.0 automatic stream stopped after the 95 ms AbortSignal and ended with MCP error -32603 containing the local reason, but its method trace contained no tasks/cancel.
  • Both runs delivered five working taskStatus events and six notifications/cancelled messages by stream end. Those messages referred to JSON-RPC request lifecycles, not a transition of embedding-index-1.
  • The synthetic worker counter advanced from 11 to 19 in one run and 12 to 20 in the other during the 75 ms observation after local abort; tasks/get still returned working.
  • One explicit cancelTask call sent tasks/cancel, returned cancelled, and left the counter unchanged during the following 50 ms in both runs.
  • Agent hosts need two owned outcomes: stop waiting locally and request remote task cancellation. Treat either one without verified task state as an unknown outcome, not proof that work stopped.
01

The sharp question: when an agent stops waiting, does its tool stop working?

A user presses stop, an orchestration budget expires, or an agent run is superseded. The local client can stop polling quickly. That is not the same claim as stopping a durable remote operation that may still consume GPUs, write records, crawl pages or build an embedding index.

The distinction was observable in the SDK 1.30.0 fixture. AbortSignal ended the automatic client stream, but the transport saw no tasks/cancel. The synthetic task stayed working and its worker counter continued to advance until the host made the separate explicit cancellation request.

02

Chronology: MCP created two cancellation vocabularies

MCP already had notifications/cancelled for signalling that a previously issued JSON-RPC request should be terminated. The 2025-11-25 protocol version introduced experimental tasks as durable state machines and added tasks/cancel, addressed by task ID, for explicit task cancellation.

An implementer opened protocol issue 1956 on December 10, 2025 about the result semantics when cancellation races a pending tasks/result. On May 5, 2026, SDK issue 2018 reported a different server-side automatic polling path that ignored request abort and could leak polling loops. SDK 1.30.0 followed on July 27. This AccessAllGPT client-side reproduction ran September 3.

03

Request cancellation and task cancellation name different objects

The general cancellation message identifies a requestId. Its purpose is to stop work associated with an in-flight protocol request, and the specification warns about races because a request may already have completed. A task cancellation request identifies a taskId and returns a Task object.

That schema difference is operational, not cosmetic. Poll request 7 may be over while durable task embedding-index-1 remains working. Counting a notifications/cancelled message as proof that the task stopped confuses the observation channel with the durable execution state.

04

The task specification requires an explicit state transition

For a valid tasks/cancel, the receiver should attempt to stop execution and must transition the task to cancelled before responding. Once cancelled, the status must remain cancelled even if underlying execution later completes or fails. Cancellation of an already terminal task must be rejected with Invalid params.

The “should attempt” language matters: cancelled state is required, while physical termination can fail or race. A production host therefore needs both the protocol acknowledgement and receiver-specific evidence that expensive or consequential work actually ceased.

05

The SDK keeps the two mechanisms on separate paths

In the pinned protocol implementation, requestStream creates a task, polls tasks/get and checks options.signal. The general request layer also registers the signal to emit notifications/cancelled for request IDs. The task stream has no call to cancelTask in the inspected boundary.

The experimental client facade exposes cancelTask(taskId) separately. That API delegates to the protocol method that sends tasks/cancel. The separation gives hosts room to choose policy, but it means passing one AbortSignal is not evidence that a durable task cancellation request was issued.

06

The fixture separated local waiting from durable synthetic work

The in-process transport accepted build_embedding_index as a task-augmented tool call and returned task ID embedding-index-1 in working state. A timer incremented a synthetic worker counter every 8 ms while that state remained working. No embedding model, corpus or external job ran.

The client polled every 20 ms with a 50 ms per-request timeout and one AbortSignal scheduled for 95 ms. After the stream ended, the harness waited 75 ms, queried task state, sent explicit tasks/cancel, waited another 50 ms and queried state again.

07

Local abort ended the stream but sent no task cancellation

Each run yielded five working taskStatus events before ending with “MCP error -32603: Error: local agent wait budget ended.” At that checkpoint, the methods were initialize, tools/call and five tasks/get requests. tasks/cancel was absent.

The stream did stop, so this is not a claim that the tested client ignored AbortSignal. The finding is narrower: stopping the automatic polling generator did not automatically invoke the separate durable task-cancellation method.

08

Six cancellation notifications still did not change task state

By stream end, each run had emitted six notifications/cancelled messages—one corresponding to task creation and five to completed polls retained on the shared signal path. This repeats the request-listener behavior measured in the preceding overall-deadline investigation.

Despite those notifications, a later tasks/get returned embedding-index-1 as working with “Synthetic embedding index is running.” The control makes task state the arbiter and prevents a misleading conclusion based on notification count alone.

09

The worker advanced after the client was already done

In run one, the worker counter was 11 when local polling ended and 19 after the 75 ms observation. In run two, it moved from 12 to 20. The exact counts depend on timers and are not a performance benchmark; the directional assertion required an increase of more than three ticks.

This is synthetic receiver behavior, not proof that every MCP server continues external work. It demonstrates why a host cannot infer remote termination from its own completed iterator: the protocol permits durable task state to outlive one request or one local wait path.

10

Explicit tasks/cancel produced a different outcome

The host then called client.experimental.tasks.cancelTask with embedding-index-1. The trace recorded exactly one tasks/cancel, and the returned task carried cancelled with “Stopped by explicit tasks/cancel.” A subsequent tasks/get also returned cancelled.

The fixture cleared its worker timer in the tasks/cancel handler. The counter stayed 19 for the next 50 ms in run one and 20 in run two. This positive control proves the harness could stop the synthetic work and ties that stop to a task-level operation rather than to elapsed time.

11

Independent reports show adjacent lifecycle pressure

SDK issue 2018 reports that a server-side automatic task-polling loop failed to inspect an aborted request signal, potentially leaving one polling loop per cancelled request in a multi-tenant server. It concerns a different code path and does not reproduce the client-side durable worker result here.

Protocol issue 1956 asks what tasks/result should return when tasks/cancel arrives while a result request is pending. It documents a real implementer’s ambiguity at the cancellation/result race. Neither issue supplies prevalence or validates this fixture; together they show why cancellation needs explicit lifecycle tests rather than one generic “cancelled” metric.

12

Cancellation is a two-phase agent operation

Phase one stops local waiting: abort the stream, release the caller, and preserve task ID, request IDs, server identity and last known status. Phase two requests durable cancellation when policy calls for it, then verifies the returned task and any receiver-specific worker acknowledgement.

Do not make a language model decide these mechanics from prose. Deterministic host code should classify the tool consequence, cancellation support, deadline source and authority. The model may explain intent, but protocol calls, retries and state transitions belong to a bounded cancellation controller.

13

Design for the race after cancellation

A task can complete between the last poll and tasks/cancel. The specification requires terminal-task cancellation to fail with Invalid params. Treat that response as a reconciliation trigger: fetch state and, where valid, the result before deciding whether work succeeded, failed or requires compensation.

A cancelled status also does not erase side effects already committed. For writes, retain idempotency keys and execution receipts, query the system of record, and never launch a replacement merely because the local stream ended. Unknown outcome is a legitimate state.

14

Measure cancellation with task-aware telemetry

Record local abort time and reason, every JSON-RPC request ID, durable task ID, tasks/cancel attempt, response, task state before and after, worker acknowledgement when available, last progress, side-effect receipt and final reconciliation. Separate request_cancel_signal from task_cancel_requested and task_cancel_verified.

Alert on tasks still working beyond a local abort grace period, missing cancellation capability for consequential tools, cancellation acknowledgements without worker cessation, terminal-state races and repeated cancellation requests. Notification volume alone is not a reliable denominator.

15

What this run does not establish

The experiment did not run a language model, production host, remote MCP server, real task store, HTTP or stdio transport, embedding model, corpus, GPU, proxy, authentication, concurrent cancellation, tasks/result race, process crash, v2 package, another SDK, external side effect or user data. Its receiver deliberately kept working after local request cancellation until tasks/cancel.

It does not claim that the SDK should automatically cancel every task when a signal aborts. A host may intentionally detach and recover later. It does not establish physical worker termination from cancelled state in arbitrary receivers, a vulnerability, prevalence, cost, latency or universal behavior. MCP tasks remain experimental.

16

Decision: deploy cancellable agent tasks only with verified remote stop

Deploy autonomous long-running MCP tools only when the host preserves task identity, distinguishes local abort from tasks/cancel, defines when detachment versus cancellation is intended, reconciles terminal races, and verifies remote work or side effects under the exact server, SDK and transport path. Keep consequential tasks supervised when cancellation capability or worker acknowledgement is absent.

Circuit-break when tasks remain working after the cancellation grace period, request notifications are counted as task cancellation, cancelled state can hide committed side effects, or replacement work can start before reconciliation. Roll back after any SDK or server change until abort, explicit cancel, completion race, result race and reconnect cases pass. Stopping the client is a local event. Stopping the agent’s durable work is a separate claim that needs evidence.

17

Copy-ready MCP agent cancellation gate

Complete this record before a long-running AI tool may detach, cancel or restart automatically.

Entries stay in this browser tab and are not submitted to AccessAllGPT. Blank responses are copied as [Unresolved].

Server, protocol, SDK, transport, tool, task support, cancellation capability, retention and worker semantics.

Read/write class, resource cost, external side effects, reversibility, idempotency and business owner.

Abort source, deadline, signal propagation, iterator settlement, retained task handle and caller-visible state.

tasks/cancel authority, attempt policy, timeout, response, final task state and worker-stop acknowledgement.

Completion, failure, deletion and tasks/result races; state and result lookup; Invalid params handling.

System-of-record query, execution receipt, side-effect reconciliation and evidence required before replay.

Request IDs, task ID, last status/progress, local abort, task cancel, worker acknowledgement and deduplication.

Abort during creation, poll, sleep and result; explicit cancel; terminal race; reconnect; duplicate cancel; server crash.

Maximum working-after-abort window, capacity response, human escalation and cleanup ownership.

Unverified worker stop, hidden side effect, lost task ID, misleading notification metrics or unsafe restart.

Primary sources

  1. Tasks (protocol version 2025-11-25)Model Context Protocol Specification · Reviewed: Capabilities, cancelling tasks, task lifecycle, cancellation requirements, retention, errors and task-cancellation flow · Retrieved · Supports: The experimental task specification defines tasks/cancel as the operation for explicit task cancellation, requires a valid request to transition the task to cancelled before responding, and says receivers should attempt to stop execution.
  2. Cancellation (protocol version 2025-11-25)Model Context Protocol Specification · Reviewed: Cancellation notifications, request IDs, behavior requirements, race conditions and limitations · Retrieved · Supports: The general cancellation utility lets either party signal that a previously issued request should be terminated; it is scoped to an in-flight request and does not define durable task-state cancellation.
  3. MCP schema at the 2025-11-25 specification commitModel Context Protocol · Reviewed: Task capabilities, task status, task object, cancellation notification and tasks/cancel request and result schemas · Retrieved · Supports: The pinned schema represents notifications/cancelled with a request ID while tasks/cancel carries a durable taskId and returns the resulting task, making the two protocol messages structurally distinct.
  4. TypeScript SDK release 1.30.0Model Context Protocol TypeScript SDK · Reviewed: Release identity, publication date, v1.x line and changed-package record · Retrieved · Supports: The official release record binds the tested package to SDK 1.30.0, published July 27, 2026 on the v1.x line.
  5. Protocol task stream and cancellation methods at SDK 1.30.0Model Context Protocol TypeScript SDK · Reviewed: Automatic task polling, AbortSignal checks, request cancellation listener, getTask and cancelTask methods · Retrieved · Supports: The pinned implementation checks the caller AbortSignal in requestStream and separately exposes cancelTask as a tasks/cancel request; the automatic task-stream path does not call cancelTask when its signal aborts.
  6. Experimental client tasks facade at SDK 1.30.0Model Context Protocol TypeScript SDK · Reviewed: callToolStream, getTask, getTaskResult, listTasks and cancelTask public experimental client methods · Retrieved · Supports: The public experimental facade exposes local stream options and explicit cancelTask(taskId) as separate calls, supporting a host policy that preserves a task handle and cancels it deliberately.
  7. @modelcontextprotocol/sdk 1.30.0 registry recordnpm Registry · Reviewed: Package identity, version, distribution integrity, repository, publication metadata and runtime requirement · Retrieved · Supports: The registry identifies the installable official SDK artifact used by both clean temporary reproductions and records its package integrity and Node requirement.
  8. handleAutomaticTaskPolling ignores AbortSignal; cancelled requests poll indefinitelyModel Context Protocol TypeScript SDK issue tracker · Reviewed: Independent May 2026 report, server-side polling path, resource-leak impact, comparison path and proposed fix · Retrieved · Supports: An independent reporter describes a server-side automatic polling loop where request cancellation did not stop polling and could consume multi-tenant resources; it corroborates the practical need to distinguish request lifecycle from durable work, not this client-side fixture.
  9. Define result when pending tasks/result meets tasks/cancelModel Context Protocol issue tracker · Reviewed: Independent December 2025 interoperability question, pending result scenario, proposed error behavior and closure metadata · Retrieved · Supports: An independent implementer asked what tasks/result should return when tasks/cancel arrives concurrently, documenting an unresolved-at-filing lifecycle edge rather than validating the behavior measured here.

Limitations

This is a bounded local reproduction on Node v24.10.0, Darwin arm64, @modelcontextprotocol/sdk 1.30.0 and Zod 3.25.76 using a synthetic in-process transport, one task-augmented tools/call, an 8 ms synthetic worker counter, 20 ms polling, 50 ms per-request timeout, 95 ms AbortSignal, 75 ms post-abort observation and 50 ms post-cancel control. It did not test a model, production agent, remote server, real task store, HTTP or stdio transport, embedding operation, corpus, GPU, proxy, authentication, concurrency, result retrieval, completion race, process crash, v2 package, another SDK, external effect or user data. Timer counts are directional fixture observations from two runs, not a performance benchmark. The receiver was intentionally designed to separate request cancellation from explicit task cancellation, so this does not establish how all servers behave or prescribe automatic cancellation as the correct policy.

Disclosures

AccessAllGPT created and ran only the synthetic local fixture in the checked-in harness. TypeScript SDK issue 2018 and protocol issue 1956 are independent public implementation records about adjacent cancellation lifecycle problems; their claims are attributed and are not presented as reproductions of this client fixture. No model, production client, third-party server, credential, endpoint, corpus, payment, external action or user data was used. The MCP project, issue reporters, SDK maintainers, 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, npm or Zod. Publication-wide relationships are listed on the disclosures page.

Further AccessAllGPT guidance

  1. An MCP Task Timeout Is Not an AI Agent Deadline
  2. A Failed MCP Task Can Hide the Error an AI Agent Needs
  3. An Optional MCP Task Schema Can Strand an Agent’s Completed Result
  4. A Lost MCP SSE Response Can Hold an AI Agent Until Its Timeout
  5. Where Human Approval Belongs in AI Automation
  6. Before You Connect an MCP Server to Production
  7. AccessAllGPT Research methodology
  8. Publication disclosures

Continue the research

Get evidence-led updates for teams making production AI decisions.