Key takeaways

  • The automatic SDK 1.30.0 stream sent initialize, tools/call and tasks/get. After tasks/get returned failed, it emitted MCP error -32603 and never sent tasks/result.
  • The generic message identified task-failed-1 but omitted the stored “upstream returned 429; retry-after=30” detail that could drive bounded recovery.
  • A direct tasks/result request for that same task immediately returned the stored isError result, proving the diagnostic existed at the protocol boundary rather than being absent from the fixture.
  • An independent SDK report and an Apify MCP Server implementation record describe the same class of lost failed-task result and an operational workaround; this run independently bounds the behavior to stable SDK 1.30.0.
  • Do not let an agent infer retry, compensation or replay policy from generic failed status. Retrieve and classify the terminal result under a tested host policy, preserving task identity and unknown-outcome state.
01

The sharp question: does failed tell the agent why the work failed?

Long-running AI tools fail in ways that require different decisions. A rate limit may justify a delayed retry. Invalid input should return to the planner. A declined payment needs an authorization flow. A partially completed write may require reconciliation rather than replay. The terminal word failed cannot safely collapse those outcomes into one branch.

In the tested automatic stream, that collapse is exactly what happened. The client observed task-failed-1 and MCP error -32603, but not the stored upstream 429 or retry-after=30. The diagnostic had not vanished: a direct tasks/result request recovered it immediately.

02

Chronology: the protocol promised terminal results before this stable release

MCP 2025-11-25 introduced experimental tasks as durable, pollable state around long-running requests. Its result-retrieval rules apply to completed, failed and cancelled terminal states: when asked for tasks/result, the receiver must return exactly what the underlying request would have returned, including a JSON-RPC error.

An independent SDK issue filed April 17, 2026 reported that automatic streaming skipped tasks/result for failed state and instead exposed a generic error. The issue was closed April 20. SDK 1.30.0 was published July 27 on the v1.x line. On September 2, this AccessAllGPT fixture found the same branch shape and runtime outcome in that installable stable artifact.

03

The specification separates state from result

tasks/get answers a state question: which task is this, and is it working, awaiting input, completed, failed or cancelled? tasks/result answers a payload question: what did the underlying request return? A failed state is therefore not a substitute for result retrieval when the caller needs the underlying error code, message, data or tool result.

The protocol requirement governs how a receiver responds when tasks/result is requested. It does not, by itself, say every convenience helper must request that method after every terminal state. That distinction matters: the fixture does not show a non-conforming receiver. It shows an automatic client path that chose not to ask for an available result.

04

The SDK branch treats completed and failed differently

In the pinned SDK 1.30.0 protocol source, requestStream polls with tasks/get. For completed, it calls getTaskResult, yields the result and returns. For input_required, it also calls getTaskResult so queued bidirectional messages can flow. For failed, it constructs an InternalError with “Task ${taskId} failed,” yields that error and returns.

That branch contains no tasks/result request and no field from statusMessage beyond the task identity. The receiver may have stored a structured tool error or JSON-RPC error, but this helper does not inspect it. The distinction is visible in source and testable over the public client API.

05

The fixture put one useful recovery signal behind the boundary

The in-process transport accepted a task-augmented warehouse_export tool call. It first returned working, then answered the next tasks/get with failed and statusMessage “Upstream rate limit.” Its tasks/result handler held an ordinary MCP tool result: isError true and text saying the upstream returned 429 with retry-after=30.

No warehouse, network or external action ran. The names make the decision consequence concrete without pretending to reproduce a production backend. The experiment isolates one question: what does the official automatic stream request and expose after failed status?

06

Three requests went out; the result request did not

Both completed runs recorded the same automatic sequence: initialize, tools/call, tasks/get. The stream yielded taskCreated with working, taskStatus with failed, then error. Its final message was “MCP error -32603: Task task-failed-1 failed.”

The harness asserted that tasks/result was absent at that checkpoint. This is stronger evidence than checking only the displayed error because it distinguishes a retrieval-and-discard bug from a branch that never retrieves the payload.

07

A direct request recovered the hidden payload

After the automatic stream ended, the same connected client sent tasks/result for task-failed-1 using CallToolResultSchema. The fixture returned isError true and the complete text “warehouse export failed: upstream returned 429; retry-after=30.”

The control establishes that the task ID remained usable, the result schema accepted the payload, and the transport could deliver it. It does not establish how long a production receiver retains failed results or whether every server stores useful diagnostics. Recovery must happen within the receiver’s retention policy.

08

The first harness failed on the exact error formatting

The initial assertion expected the raw synthesized text “Task task-failed-1 failed.” The runtime wrapped it as “MCP error -32603: Task task-failed-1 failed.” That run failed, and I corrected the evidence rather than smoothing over the prefix.

A second harness defect used the first occurrence of a method-like source string as a slice boundary and captured only the opening of requestStream. That source assertion also failed. I changed the boundary to the next class request method, then two complete clean-sandbox runs passed all 17 assertions.

09

Independent implementation evidence shows why this is operational

SDK issue 1922 tied the behavior to work adding long-running Actor tasks to Apify MCP Server. The report said a failed task’s real stored result was unreachable through requestStream. It proposed fetching tasks/result for failed in the same way as completed. This is an independent report, not proof that every MCP client behaves this way.

Apify MCP Server PR 680 later documented a workaround: store error-bearing tool results under completed because its client fetched results only for that status. The report said the mismatch hid error details and interfered with a payment-retry payload. The PR was closed without merge, so it is evidence of encountered integration pressure and tested workaround design—not a released universal fix.

10

Blind retry is the wrong fallback

An agent that receives only InternalError cannot tell rate limiting from invalid arguments, policy denial, partial execution or an internal crash. Retrying every generic failure can multiply cost and side effects. Never retry a consequential tool solely because failed lacked detail.

Preserve the task ID and move the operation to an unknown-outcome state. Under a bounded recovery policy, request the terminal result, validate it against the expected result schema, classify retryability in deterministic host code, and consult idempotency and side-effect records before allowing another execution.

11

Do not relabel failure as success without containing the debt

Representing an error payload as completed can restore delivery through a client branch, as the independent Apify record explains, but it changes the meaning of task status. Dashboards, queues and orchestration may count a failed business operation as successful unless every consumer also examines isError or another field.

If compatibility forces that workaround, version it, expose a separate business outcome, test every consumer, and set a removal condition. Prefer an explicit client recovery adapter or an SDK path that preserves both terminal failed state and underlying result semantics.

12

A production recovery adapter needs narrow authority

On failed, record protocol version, SDK version, server identity, task ID, status message, result-retrieval attempt and result digest. Permit tasks/result only against the same authenticated MCP server and task context. Validate the returned payload before using any field in planning or execution.

Classify errors into retryable, input-correctable, authorization-required, permanently rejected and unknown. Cap attempts and elapsed time. A retry-after hint can schedule consideration; it must not override tenant budgets, rate policy, human approval or an idempotency gate.

13

Test result fidelity, not just terminal status

Build a conformance matrix where the underlying operation returns a successful result, an isError tool result, a JSON-RPC error with code/message/data, malformed data, a large diagnostic, a sensitive diagnostic and no retained result. Exercise completed, failed and cancelled, both automatic and direct APIs, on the exact SDK and transport deployed.

Assert method sequence and payload fidelity. Then test expiry, reconnect, duplicate result retrieval, task lookup after restart, multi-tenant isolation and a result that arrives after the host’s deadline. A green “reached terminal status” check says nothing about whether the agent received the evidence needed for safe recovery.

14

What this run does not establish

The experiment did not run a language model, remote MCP server, real task store, HTTP or stdio transport, authentication, payment, external side effect, retry loop, v2 package, another language SDK or production concurrency. It tested one valid isError tool result, not a receiver-generated JSON-RPC error. The task specification remains experimental.

It also does not prove that every failed task has a useful stored result, that direct recovery is always authorized, or that the stable SDK branch is a security vulnerability. The independent reports corroborate the mechanism’s practical relevance but do not provide prevalence, exploitability or performance measurements.

15

Decision: deploy failed-task automation only with result-aware recovery

Deploy long-running MCP tools autonomously only when failed-state tests prove that the host can recover and validate the underlying result, preserve unknown outcomes, gate retries by idempotency and consequence, and distinguish protocol state from business outcome. Keep writes supervised when failure details are unavailable or result retention is uncertain.

Circuit-break when repeated generic failures hide root cause, tasks/result cannot be retrieved within retention, a compatibility workaround misreports failures as success, or retries can duplicate consequential work. Roll back the recovery adapter if an SDK upgrade changes event or result semantics until the full failure matrix passes again. “Task failed” is a state transition. It is not yet a recovery decision.

16

Copy-ready MCP failed-task recovery gate

Complete this record before a long-running AI tool may retry or compensate automatically.

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

Server, protocol, SDK, tool, result schema, task retention and supported terminal-result behavior.

Read/write class, external side effect, financial or data impact, reversibility and owner.

Task ID, terminal status, statusMessage, timestamps, poll sequence and transport/session identity.

Automatic behavior, direct tasks/result path, timeout, authorization, schema validation and retention deadline.

Retryable, input-correctable, approval-required, permanent or unknown; deterministic evidence for classification.

Idempotency key, execution receipt, side-effect lookup, attempt cap, backoff and business deadline.

Any failed-as-completed mapping, affected consumers, business-outcome field, telemetry correction and removal trigger.

Completed, failed and cancelled with success, isError, JSON-RPC error, malformed, expired and unavailable results.

Missing result, schema drift, tenant mismatch, duplicate side effect, status/result contradiction or unbounded generic errors.

Primary sources

  1. Tasks (protocol version 2025-11-25)Model Context Protocol Specification · Reviewed: Task creation, polling, terminal states, result retrieval requirements and task-related message association · Retrieved · Supports: The experimental task specification says tasks/result for a terminal completed, failed or cancelled task must return exactly what the underlying request would have returned, whether a success or JSON-RPC error.
  2. MCP schema at the 2025-11-25 specification commitModel Context Protocol · Reviewed: Task status, task object, task creation, tasks/get and tasks/result schemas · Retrieved · Supports: The pinned schema distinguishes failed from completed task status and defines tasks/result as the route to the underlying request result.
  3. TypeScript SDK release 1.30.0Model Context Protocol TypeScript SDK · Reviewed: Release identity, publication date, v1.x line and package changes · Retrieved · Supports: The official release record binds the tested package to SDK 1.30.0, published July 27, 2026 on the v1.x line.
  4. Automatic task stream at SDK 1.30.0Model Context Protocol TypeScript SDK · Reviewed: Task creation, status polling, completed, failed, cancelled and input-required branches · Retrieved · Supports: The pinned implementation requests tasks/result after completed and input_required, but the failed branch instead synthesizes an InternalError containing only the task ID and returns.
  5. @modelcontextprotocol/sdk 1.30.0 registry recordnpm Registry · Reviewed: Package identity, version, distribution integrity, repository and runtime requirements · Retrieved · Supports: The registry identifies the installable official SDK artifact used by the clean temporary reproduction.
  6. Client sees “Task <id> failed” instead of the real error/result on failed tasksModel Context Protocol TypeScript SDK issue tracker · Reviewed: Independent bug report, Apify MCP Server context, reproducer, expected behavior, maintainer-bot confirmation and closure metadata · Retrieved · Supports: An independent April 2026 report describes the same failed-task result loss in the then-main task manager and links it to production work on Apify MCP Server; the issue was closed April 20.
  7. Apify MCP Server PR 680: Set final statusMessage before storeTaskResultApify MCP Server · Reviewed: Problem statement, SDK workaround, outcome table, test plan and closure status · Retrieved · Supports: The independent implementation report says its client could not receive stored failed-task details, describes impact on payment retry and diagnostics, and records a workaround that represented error results as completed so clients would fetch them. The PR was closed without merge.

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, one working-to-failed transition and one stored isError CallToolResult containing synthetic 429 retry detail. It did not test a model, production agent, remote server, real tool or task store, HTTP or stdio transport, proxy, authentication, payment, external side effect, JSON-RPC error returned by tasks/result, result expiry, reconnect, concurrency, load, v2 package or another SDK. The protocol is experimental. The behavior is bound to two completed runs and one distributed source digest, not a prevalence estimate, performance benchmark or claim that every failed task retains a useful result.

Disclosures

AccessAllGPT created and ran only the synthetic local fixture in the checked-in harness. TypeScript SDK issue 1922 and Apify MCP Server PR 680 are independent public implementation records; their claims are attributed and were not reclassified as AccessAllGPT observations. No model, production client, third-party server, credential, endpoint, payment, external action or user data was used. The MCP project, issue reporter, SDK maintainers, Apify, 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, Apify, 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. An Optional MCP Task Schema Can Strand an Agent’s Completed Result
  3. A Lost MCP SSE Response Can Hold an AI Agent Until Its Timeout
  4. MCP v2 Can Return a Partial Tool List as Complete
  5. Where Human Approval Belongs in AI Automation
  6. AccessAllGPT Research methodology
  7. Publication disclosures

Continue the research

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