How MCP Tasks and Elicitations Invert Control Inside Claude Code

Back to blog
Mehran Mozaffari·

The Death of the Blocking Tool Call

Standard Model Context Protocol execution has hit a mechanical ceiling. When MCP was first deployed, tool interactions adhered strictly to a synchronous request-and-reply loop: the client dispatched a tools/call JSON-RPC message, the host process waited, and the server delivered a terminal output. That paradigm works cleanly for deterministic, sub-second operations like checking a git branch or inspecting a local file. The moment an agent steps into production engineering tasks—such as migrating a remote relational schema, building a multi-stage Docker container, or executing Terraform plans—that model falls apart.

Traditional tool calling collapses under real-world latency because the entire inference thread remains blocked while awaiting completion. In early agent patterns, developers worked around this by either setting aggressive timeout thresholds or forcing tools to fail deliberately when human verification was required. A tool would return a failure string asking the operator for missing confirmation or credentials, forcing the LLM to parse the message, generate a clarifying prompt, burn conversational context, and re-invoke the operation from scratch. This "halt-and-reprompt" pattern is wasteful: it discards in-flight process state, incurs redundant inference cycles, and leaves operations susceptible to mid-flight drift.

Anthropic’s introduction of Tasks and Elicitations within Claude Code fundamentally alters this topology. Rather than treating the external server as a passive dictionary of callable functions, the protocol shifts toward a stateful, bidirectional orchestration bus. Under this model, the client does not monopolize workflow management. Instead, an MCP server can expose long-running background tasks through managed identifiers, while maintaining the ability to pause execution and prompt the client for interactive parameters mid-stream.

The critical architectural debate here is whether orchestration state belongs inside the host framework or within the external tool server. Many popular application frameworks resolve latency by embedding checkpointers, graph nodes, and human-in-the-loop approval gates directly inside the client runtime. That approach forces every tool author to couple their business logic to that specific client’s execution model. By pushing task management and interaction down into the MCP layer, Claude Code treats external tools as autonomous microservices. I view pushing task state management into the server boundary as the only sustainable path for cross-client agentic tooling, because it decouples external resource state from the client's fragile conversational loop.

Following a Suspended Job Across the Wire

To see the operational contrast between basic tool calling and stateful protocol execution, consider an automated infrastructure deployment initiated by Claude Code. In a legacy setup, triggering a lengthy container build and cluster rollout forces the agent to either block the pipe until the command times out or spawn an untracked background shell detached from the supervisor. With MCP Tasks, the client initiates the execution through an explicit task-creation payload. The server immediately registers the job, returns a task identifier with an initial queued or running status, and frees the client thread to continue planning or monitoring without maintaining a locked transport socket.

As the background worker proceeds, execution status does not remain a black box. The server streams incremental progress updates back to the client over server-sent events or active process pipes. When the worker encounters an ambiguous branch or a destructive operation—such as demanding confirmation before routing live production traffic to a new ingress point—it does not abort. Instead, it dispatches an elicitation request back across the wire. Claude Code catches this request, presents a typed interactive prompt in the terminal, captures the operator's response, and transmits the input payload back to the awaiting worker. Intermediate state remains intact in memory on the server, avoiding any re-initialization.

sequenceDiagram
    autonumber
    actor Operator as Human Operator
    participant Agent as Claude Code
    participant Transport as Client Transport
    participant Server as MCP Server Worker

    Agent->>Transport: tasks/run (Deploy infra, env="staging")
    Transport->>Server: JSON-RPC tasks/create
    Server-->>Transport: Task Acknowledged (task_id: "task-849", status: "running")
    Transport-->>Agent: Task Created (task_id: "task-849")
    
    Server-)Transport: notifications/progress (35% - VPC provisioned)
    Transport-)Agent: Stream progress milestone
    
    Note over Server: Worker reaches destructive step (DNS cutover)
    Server->>Transport: elicitation/request ("Confirm target zone cutover? [y/N]")
    Transport->>Agent: Intercept elicitation prompt
    Agent->>Operator: Present parameter prompt in terminal
    Operator->>Agent: Submit interactive input ("y")
    Agent->>Transport: elicitation/response (selection: "y")
    Transport->>Server: Resolve elicitation RPC
    
    Note over Server: Worker resumes execution with user input
    Server-)Transport: notifications/progress (80% - Routing tables updated)
    
    Agent->>Transport: tasks/status (task_id: "task-849")
    Transport->>Server: Query execution state
    Server-->>Transport: Status: completed (artifacts: deployment logs)
    Transport-->>Agent: Final task output returned

What makes this wire exchange notable is the reversal of the caller-callee dynamic. During the elicitation phase, the MCP server temporarily acts as the caller, demanding resolution to a typed schema before proceeding. Claude Code acts as the proxy bridge between the server's input requirements and the operator's terminal session. By resolving this exchange at the protocol level, the agent avoids re-prompting the model to synthesize a new tool invocation. The worker process simply resumes its existing execution thread directly from memory once the parameter payload arrives, ensuring that ephemeral variables, network sockets, and temporary disk buffers remain fully preserved across the interaction boundary.

The Anatomy of Protocol-Level State Management

The operational difference between MCP Tasks and conventional IDE coding agents comes down to structured protocols versus heuristic terminal scraping. Tools like Cursor, Windsurf, or Aider typically run long jobs by launching a sub-shell and parsing raw text output with regular expressions. When an interactive prompt appears, the agent inspects terminal outputs to guess whether a command is awaiting input, prompting the user through custom UI overlays. This approach is notoriously fragile: minor changes in command formatting, escape sequences, or unexpected warnings frequently cause terminal scrapers to hang or misinterpret job completion.

In contrast, MCP formalizes these interactions through schema-driven JSON-RPC interfaces. When an external daemon implements task tracking and elicitation, it communicates operational state deterministically. As I have argued when examining why An Agent That Edits Its Own Harness Must Not Be Its Own Kernel, decoupling execution mechanics from the model's supervisory loop prevents internal tool faults from corrupting the agent's core context.

Protocol Primitive Execution Responsibility Transport Pattern Server-Side State Requirement
tasks/create Spawns background worker and registers job Client-to-server request / immediate response Allocates unique task identifier and state record in tracking table
notifications/progress Emits non-blocking milestone and percentage data Server-to-client unidirectional notification stream Reads worker execution metrics without halting execution thread
elicitation/request Suspends active operation to query parameters or credentials Server-to-client blocking RPC over open duplex channel Pauses execution routine and preserves in-memory worker context
tasks/status Reconciles execution health, artifacts, and exit state Client-to-server polling or terminal event Persists exit code, error traces, and generated artifact references
tasks/cancel Issues explicit termination to running workers Client-to-server priority control message Triggers worker process teardown and releases associated system resources

The clear advantage of this architecture is that schema-validated elicitations prevent inference roundtrips. When a tool needs an authorization key, the schema defines the expected shape, Claude Code collects it, and the server validates it immediately. However, this architectural choice introduces a strict dependency on stateful transport channels. Standard synchronous tool calling works over stateless HTTP request cycles or transient process launches. Asynchronous tasks and live elicitations require persistent bidirectional pipes, robust ping-pong keepalives, and resilient reconnection logic.

If an operator launches a task over a local process pipe and terminates their terminal session, the connection breaks. Without explicit lifecycle management, the server either drops into an orphaned background state or fails silently. To build reliable systems around these primitives, teams must design MCP servers with strict server-side progress aggregation and heartbeat timeouts, ensuring that progress notifications summarize milestones rather than flooding the agent's context with raw terminal text.

Testing the Claim: Can MCP Replace Workflow Engines?

A hasty reading of Anthropic's additions might suggest that embedding Tasks and Elicitations directly into the Model Context Protocol renders dedicated workflow engines obsolete. If an MCP server can expose long-running jobs, track status transitions, and ask the user for clarification mid-execution, why bother standing up an external Temporal cluster or orchestrating cycles through LangGraph state graphs?

That conclusion confuses an inter-process communication boundary with a distributed runtime. The protocol additions specify how a client and a tool exchange task metadata and route interactive prompts over JSON-RPC. They say nothing about durability, cross-node consensus, or distributed state persistence. If a host machine running an in-memory MCP server loses power mid-migration, or if a user kills the supervisor terminal, that task state evaporates unless the server author deliberately backed the worker with a database or durable event log. As I explored when analyzing How agentloop Enforces Run Bounds on LLM Agents Without a State Graph, runtime boundaries must be enforced by concrete state stores rather than optimistic conversation loops.

stateDiagram-v2
    [*] --> Queued: tasks/create
    Queued --> Running: Worker begins job
    Running --> PausedForElicitation: elicitation/request
    PausedForElicitation --> Running: Parameter response delivered
    Running --> Completed: Execution finished
    Running --> Failed: Uncaught worker error
    Running --> Cancelled: tasks/cancel
    Queued --> Cancelled: tasks/cancel
    PausedForElicitation --> Cancelled: Operator abort
    Completed --> [*]
    Failed --> [*]
    Cancelled --> [*]

To see where the boundary breaks, consider a straightforward failure test: run an MCP server locally that implements a sixty-second background task, initiate tasks/run from Claude Code, and sever the client process thirty seconds in. If your server stores worker state in a local dictionary, the orphaned process either crashes from a broken pipe or runs unmonitored to completion with no mechanism for a freshly launched Claude Code instance to re-attach. MCP standardizes the wire contract, not the execution durability, and treating it as a replacement for durable execution guarantees will leave your production pipelines littered with ghost jobs.

Failure Mode Analysis: Deadlocks, Zombies, and Secret Leaks

Moving from synchronous tool calls to stateful, bidirectional protocol flows introduces failure vectors that simply do not exist in stateless architectures. When a server transitions from a passive responder to an active orchestrator, standard timeout assumptions collapse.

Failure Mode Root Trigger Immediate System Impact Production Mitigation
Zombie Background Worker Client disconnects (Ctrl+C, network drop) without issuing tasks/cancel Severed stdio pipe leaves background compute, locks, or cloud resources running indefinitely Register OS termination traps and heartbeat monitors that automatically terminate child workers
Headless Terminal Deadlock Server issues elicitation/request inside an unattended CI/CD runner Execution blocks indefinitely awaiting user input on an unattached stdin stream Server inspects environment for non-interactive TTYs and triggers an immediate structured abort
Non-Idempotent Retry Storm Transport drops during status polling; agent re-invokes creation tool Duplicate invocations execute side-effecting operations (e.g., duplicate cloud resources) Mandate unique client-generated idempotency keys on every task creation payload
Transcript Credential Leak User submits sensitive credentials or tokens in response to an elicitation prompt Secrets are mirrored into conversational history and recorded in persistent CLI transcripts Tag sensitive elicitation schema fields with strict masking directives to suppress terminal logging

The most critical operational hazard is headless execution. Claude Code is routinely run in non-interactive continuous integration pipelines and automated scripting routines. If an attached MCP server hits an ambiguous condition and dispatches an elicitation, an automated runner with no operator attached will stall until the pipeline hits its maximum wall-clock timeout.

Similarly hazardous are automated agent retries. If Claude Code encounters a transient transport blip while querying a task's status, its baseline recovery pattern is to re-invoke the original tool. Without strict idempotency keys enforced by the server, an agent trying to recover a stalled payment or cloud provisioning task will execute duplicate mutations. Finally, operator input handling creates an insidious security leak: because elicitations are surfaced through the main terminal interface, terminal buffers will gladly log raw credentials to disk unless the server explicitly defines parameter masking.

Production Blueprints for Stateful Tool Servers

Hardening an MCP server for production requires treating every client connection as inherently unstable. To run background jobs reliably, server authors must build defensive lifecycle management directly into their JSON-RPC handlers.

First, require a mandatory idempotency key on all task registration calls. When the client dispatches a task initiation request, the server should evaluate this key against an active registry. If a connection drops and the client re-submits the job, the server returns the existing task identifier and its current progress metrics rather than spawning duplicate worker threads. Second, decouple transport keepalive timeouts from underlying job runtimes. A database maintenance task might run for twenty minutes, but the transport connection must verify health via lightweight heartbeat pings every few seconds. If the parent connection drops, the server's background supervisor must trigger a graceful cancellation routine to clean up locks and intermediate files.

Third, embed explicit execution checks for unattended environments. A server must inspect process streams to verify whether an interactive operator is present. When running in headless mode, any routine that would normally trigger an interactive query must either fall back to predefined environment parameters or fail immediately with an explicit error code.

flowchart TD
    Client[Claude Code Client] -->|tasks/run with Idempotency Key| Ingress{Server Ingress}
    Ingress -->|Key Exists| ReturnExisting[Return Active task_id]
    Ingress -->|New Key| Register[Allocate Task in Registry]
    Register --> Worker[Spawn Background Worker]
    Worker --> TTYCheck{Needs Input: Interactive TTY?}
    TTYCheck -->|Yes| Elicit[Dispatch elicitation/request]
    TTYCheck -->|No| FailFast[Fail with Structured Error]
    Elicit --> AwaitResp[Await Operator Parameter]
    AwaitResp --> Worker
    Worker --> Done[Emit Terminal Status]

These mechanics come alive when designing real-world tool servers. For instance, teams deploying an Interactive Multi-Stage Database Migration Daemon can connect Claude Code to a server where long-running DDL operations emit structured progress updates, pausing via elicitation when destructive alter statements require operator confirmation; the primary architectural trap to guard against is missing idempotency keys that risk running duplicate table alterations if transport hiccups cause the agent to retry.

Similarly, an Asynchronous Cloud Infrastructure Provisioner can launch container clusters in a detached daemon, monitoring rollouts via regular status polling while prompting the operator for dynamic environment variables on the fly. In that setup, developers must ensure the server detects unattended execution environments so automated CI runners do not stall indefinitely awaiting input. Finally, an Interactive Secret-Gated Security Scanner can audit code repositories, pausing mid-scan through an elicitation request when elevated audit tokens are needed for deep analysis; here, the essential requirement is ensuring those entered tokens are kept strictly within server memory and masked from local session transcripts. Building resilient tool servers requires explicit client-side idempotency keys and headless detection at every protocol junction.

Resources

Updated 2026-10-22 by Mehran Mozaffari.

Related posts