Key takeaways
- The experimental SDK 1.30.0 wrapper declares resultSchema optional, while the underlying protocol method requires a schema for response parsing.
- The synthetic task reached completed state and retained its result; omitting the schema made the awaited getTaskResult promise reject with “Cannot read properties of undefined (reading _zod).”
- Supplying CallToolResultSchema returned the stored text and matching related-task ID, isolating the failure to client-side result parsing rather than task execution or storage.
- The public issue says the throw occurs outside the awaited path; in this exact run it was catchable with try/catch and produced zero uncaughtException events. Failure routing must be measured, not copied from a report.
- Pin an explicit result contract for every task-bearing operation, catch retrieval errors, preserve task IDs and make recovery replayable before allowing autonomous long-running agent work.
The sharp question: can an agent recover work that already finished?
MCP Tasks separate a long-running operation from the connection that started it. That is useful for AI agents running builds, batch analysis or approval-bound work: the server can return a durable task ID, complete later and let the client retrieve the result. The reliability boundary is not only whether the work ran. It is whether the client can turn the stored response back into an application result.
In my fixture, the server completed the task and stored “synthetic task complete.” The experimental client obtained a valid task ID. Yet the shortest call allowed by the public wrapper—getTaskResult(taskId), with no second argument—rejected before returning that stored value. Passing CallToolResultSchema made the same retrieval succeed.
Chronology: durable tasks arrived with an experimental contract seam
MCP began as a synchronous request-and-response protocol for connecting AI applications to tools and data. Long-running actions make an open request fragile: intermediaries time out, clients restart and human approval may take longer than a connection can survive. The Tasks work added a durable handle, lifecycle states and a separate result-retrieval operation.
SDK 1.30.0 was released on July 27, 2026 on the v1.x line with experimental task APIs. On August 31, issue 2742 reported that getTaskResult failed when its optional result schema was omitted; a separate commenter reproduced the source-level mismatch on the current v1.x branch. On September 1, this AccessAllGPT probe reproduced the TypeError against the published 1.30.0 artifact and added a control that recovered the same stored result with an explicit schema.
The extension’s promise makes recovery part of correctness
The current Tasks overview explains that a task ID survives a dropped connection and that clients poll status before retrieving the final result. This makes tasks a recovery protocol, not merely a progress UI. A completed operation whose output cannot be decoded is operationally different from a failed operation: repeating it may duplicate an expensive or consequential side effect.
For agent hosts, that distinction has to survive into state machines and incident records. “Task completed, result decode failed” must not collapse into “task failed” or “nothing happened.” The host needs the task ID, terminal state, operation identity and output-contract version before deciding whether to retry retrieval, compensate, inspect manually or start new work.
The public wrapper and protocol method disagree about optionality
At tag 1.30.0, ExperimentalClientTasks.getTaskResult declares resultSchema?: T. Its implementation forwards taskId, resultSchema and options through a cast to the underlying client. At runtime, calling the method with only taskId therefore forwards undefined as the schema.
The protected Protocol.getTaskResult method has a different contract: resultSchema is required. It passes that value to request so the returned tasks/result payload can be validated. Type checking does not repair a value that the public wrapper has explicitly made optional; the mismatch reaches the response parser only after the server has answered.
Undefined reaches the schema detector, not a default result contract
The pinned Zod compatibility helper first checks whether a supplied schema is Zod 4 by reading its _zod property. If not, it treats the value as a Zod 3 schema and calls safeParse. Undefined can satisfy neither path. The observed exception—“Cannot read properties of undefined (reading _zod)”—matches the first property access.
The neighboring getTask and listTasks methods use fixed default result schemas. The task-result wrapper does not choose CallToolResultSchema or a generic ResultSchema when its optional argument is absent. This article does not claim which default maintainers should adopt: task result shape can depend on the original operation, so making the schema mandatory may also be a coherent API decision.
The fixture completed one inert tool task before retrieval
The checked-in audit creates the official SDK Client, Server and InMemoryTaskStore, joined by the SDK’s linked in-memory transport. The server exposes one zero-argument synthetic tool named delayed_summary. Its handler creates a task, stores a completed CallToolResult-shaped object containing one text item and returns the task handle.
The client consumes callToolStream until it receives a task ID. No model chooses the tool, no remote transport is opened and no external side effect occurs. This isolates task creation, storage and client parsing without claiming anything about production scheduling, persistence or authorization.
Omitting the schema rejected the awaited promise
The first retrieval called client.experimental.tasks.getTaskResult(taskId) exactly as the optional signature permits. It rejected with TypeError and the expected _zod message. The stack included isZ4Schema and safeParse. Assertions also verified that a non-empty task ID had already been obtained.
This matters because the operation had not vanished. The server-side task store still held a completed result. The failure occurred when the client validated the tasks/result response. An agent orchestrator that interprets this exception as permission to rerun the tool could duplicate work that already succeeded.
The catchability result narrows the public report
Issue 2742 says the TypeError happens inside the transport message handler and is awkward to catch from application code. In this exact Node v24.10.0, SDK 1.30.0 and linked in-memory transport run, ordinary try/catch around the awaited promise captured it. A temporary uncaughtException listener observed zero events.
That is an original negative finding, not a claim that the reporter was wrong in every environment. Event scheduling, transport implementation, SDK build or runtime can change how a parser exception is routed. The bounded conclusion is that the value failure reproduced while the stronger uncaught-path behavior did not reproduce here. Production tests should assert both rejection and process-level error routing on the exact transport in use.
An explicit CallToolResultSchema recovered the same stored output
The control repeated retrieval for the same task ID and supplied the SDK’s CallToolResultSchema. It resolved with one text content item, “synthetic task complete,” and _meta carried a related-task taskId equal to the original handle. Assertions bound the content type, value and identity relationship.
This control rules out a missing task, failed task, bad stored payload and generic transport outage in the fixture. It does not prove that CallToolResultSchema is correct for every task-bearing request. The client must bind each task to the result schema of the originating operation rather than attach one global schema to all completed work.
The reproducible command installs no application dependency tree
Run node scripts/audit-mcp-task-result-schema.mjs from the repository. The harness creates a temporary package, installs only @modelcontextprotocol/sdk@1.30.0 and zod@3.25.76 with npm install --ignore-scripts --no-audit --no-fund, executes the probe, prints JSON and removes the sandbox in a finally block.
The September 1 run reported Node v24.10.0 on darwin/arm64, SDK 1.30.0, Zod 3.25.76, a rejected omitted-schema promise, zero uncaught exceptions, a resolved explicit-schema control and fifteen passing assertions. The distributed experimental client file hashed to bfa97c01d4394fc53f9c659c7e156f96b1727b6d0052b0db52969251b9afe2cd.
Durability needs an operation-to-schema ledger
Persist more than taskId. Record server identity, authenticated principal, originating MCP method, tool name when applicable, request digest, SDK and protocol versions, expected result-schema identity and digest, creation time, terminal state, idempotency policy and last retrieval outcome. Without that mapping, a restarted client may possess a durable handle but not the contract needed to decode it safely.
Treat schema selection as trusted application configuration, not model output. A model should not choose an arbitrary parser for a pending result. On upgrade, either retain the decoder that matches the task’s creation version or perform a tested migration that preserves old in-flight task contracts.
Separate retrieval retry from operation retry
A parsing exception after terminal completion should first trigger bounded retrieval recovery: inspect task status, verify server and principal, reload the pinned schema, retry tasks/result with an attempt cap and preserve the raw protocol error under the application’s data policy. It should not automatically call the original tool again.
Only rerun the operation when its execution state is known and the operation’s idempotency or compensation contract allows it. For deployments, payments, messages or access changes, an unknown outcome is a stop condition. Human review should see the exact task, operation, result-retrieval evidence and proposed recovery action—not a generic “retry?” prompt.
Compatibility tests must cross restart and transport boundaries
The in-memory fixture proves one local response path. A production suite should create tasks through each operation type, disconnect after handle receipt, restart the client, restore schema bindings and retrieve completed, failed and cancelled results. Repeat through the deployed transport with timeouts, malformed payloads, unknown task IDs, expired tasks and server upgrades.
Assert that parser errors reject the awaited operation, reach the intended telemetry path, do not crash the process, do not lose the task record and never cause an implicit operation replay. Test Zod and SDK upgrades against old pending tasks. A happy-path stream that returns immediately does not exercise the reason durable tasks exist.
What failed, and what remains unknown
The omitted-schema path failed exactly at the client parser. The issue’s claimed uncaught routing did not reproduce: try/catch worked and the process-level listener remained at zero. I did not suppress that contradiction or turn one environment into a universal statement. The independent issue and comment remain external reports, not substitutes for this run.
The probe did not test a language model, production host, v2 split client, remote HTTP or stdio, persistent task store, reconnect, process restart, authentication, authorization, expiry, cancellation, concurrent retrieval, malformed server output, other task-bearing methods, Zod 4, another language SDK, real side effects or user data. It establishes one API-contract mismatch and one successful explicit-schema recovery path, not prevalence or exploitability.
Decision: approve durable agent tasks only with replay-safe result recovery
Deploy MCP Tasks for autonomous AI work only when every task-bearing operation has a pinned result schema, durable operation-to-schema mapping, catchable retrieval failures, bounded status and result retries, exact-once or compensating action rules, and restart tests on the deployed SDK and transport. Constrain tasks to read-only or draft-producing work while those controls are incomplete.
Reject or roll back when a completed task can lose its decoder, a parser exception can crash the host, retrieval failure triggers an unguarded tool replay, task identity is not scoped to server and principal, or upgrades cannot recover pre-existing work. A durable task ID is necessary recovery state. This experiment shows that it is not sufficient by itself.
Copy-ready MCP task-result recovery gate
Complete this record for each task-bearing operation before an autonomous agent can start long-running work.
Entries stay in this browser tab and are not submitted to AccessAllGPT. Blank responses are copied as [Unresolved].
Server, principal, tenant, MCP method, tool or request type, request digest, consequence and idempotency class.
SDK package/version/digest, protocol or extension version, transport, task-store implementation and deployment commit.
Schema name, version and digest; owning operation; decoder retention period; migration and backward-compatibility evidence.
Task ID, creation and expiry, status, related request, last poll, last retrieval attempt and terminal receipt.
Awaited rejection, process-level error routing, telemetry, raw-response handling, task preservation and no implicit operation replay.
Status check, schema reload, bounded result retry, transport reconnect, operator escalation and evidence retained at each step.
Exactly-once evidence, idempotency key, compensation, unknown-outcome rule and actions that must never be automatically repeated.
Completed, failed, cancelled, expired and malformed tasks across restart, deployed transport, SDK/Zod upgrades and old pending records.
Eligible operations, authority ceiling, task count, duration, spend, monitoring, owner and automatic reduction to read-only or draft-only.
Lost decoder, uncatchable parser error, task/result identity mismatch, unauthorized retrieval or unguarded replay; kill switch and cleanup.
Primary sources
Browse the publication-wide evidence index →
- Tasks: asynchronous task execution for long-running MCP operationsModel Context Protocol · Reviewed: Task purpose, non-blocking rationale, crash resilience, lifecycle, polling, result retrieval, client guidance and extension status · Retrieved · Supports: The current Tasks extension describes a task ID as a durable handle and result retrieval as the step that returns a completed long-running operation after polling or reconnecting.
- TypeScript SDK release 1.30.0Model Context Protocol TypeScript SDK · Reviewed: Release identity, tag, publication date, v1.x branch and changed-package record · Retrieved · Supports: The official release record binds the tested @modelcontextprotocol/sdk package to version 1.30.0, released July 27, 2026 on the v1.x line.
- Experimental task client at SDK 1.30.0Model Context Protocol TypeScript SDK · Reviewed: ExperimentalClientTasks.getTaskResult signature, optional resultSchema parameter, cast and delegation to the underlying Protocol method · Retrieved · Supports: The pinned public wrapper types resultSchema as optional and forwards its runtime value, including undefined, to the underlying getTaskResult method.
- Task-result request and Zod compatibility paths at SDK 1.30.0Model Context Protocol TypeScript SDK · Reviewed: Protocol.getTaskResult required schema parameter, request dispatch, response safeParse path, zod-compat isZ4Schema and safeParse implementation · Retrieved · Supports: The pinned protocol method requires a result schema and passes it into response parsing; zod-compat checks schema._zod before falling back to a Zod 3 safeParse call, so an undefined schema cannot be parsed.
- @modelcontextprotocol/sdk 1.30.0 registry recordnpm Registry · Reviewed: Package identity, version, distribution integrity, publication metadata, repository and runtime requirements · Retrieved · Supports: The npm registry identifies the installable official SDK 1.30.0 artifact used in the clean sandbox and binds it to distribution metadata.
- getTaskResult throws when the optional result schema is omitted (issue 2742)Model Context Protocol TypeScript SDK issue tracker · Reviewed: Independent report, reproduction, expected and actual behavior, stack, workaround, environment and independent branch-level follow-up reproduction · Retrieved · Supports: An independent contributor reported the optional-schema TypeError on August 31, 2026; a second contributor reported reproducing it on the current v1.x branch. These reports prompted but did not validate the AccessAllGPT run.
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 official linked in-memory transports, one synthetic tool, one completed in-memory task and one CallToolResult-shaped text payload. It did not test a model, production agent, v2 split SDK, remote or stdio transport, persistent storage, restart, reconnect, authentication, expiry, cancellation, concurrency, malformed responses, other task-bearing requests, Zod 4, another SDK, external effects or real data. It demonstrates that omitting the publicly optional result schema rejected the awaited promise in the tested path while an explicit CallToolResultSchema recovered the same stored result. It does not measure prevalence, prove exploitability, determine the maintainers’ preferred API fix or establish failure routing on other transports and runtimes.
Disclosures
AccessAllGPT created and ran only the synthetic local fixture in the checked-in harness. Public issue 2742 prompted the question; its reporter and commenter supplied no private information and did not validate this run. No model, production client, third-party MCP server, credential, external endpoint or user data was used. The MCP project, issue participants, 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
- MCP v2 Can Return a Partial Tool List as Complete
- MCP Pagination Can Evict Earlier Tool Validators
- MCP Output Validation Depends on Tool Discovery
- MCP’s readOnlyHint Is Not a Permission Boundary
- MCP Token Passthrough Needs an Audience Boundary
- Where Human Approval Belongs in AI Automation
- AccessAllGPT Research methodology
- Publication disclosures
Continue the research
Get evidence-led updates for teams making production AI decisions.