Skip to content

Cloudflare native elicitation hangs and is reported as user cancellation #1521

Description

@dmmulroy

Summary

On the self-hosted Cloudflare Worker host, an approval-gated tool call with ?elicitation_mode=native hangs until the MCP client times out. The server's elicitation/create request never reaches the client request handler.

Executor then converts the transport failure into action: "cancel", so the resulting error incorrectly says the request was cancelled by the user.

Environment

  • Executor self-hosted on Cloudflare Workers / Durable Objects
  • Console reports v1.5.35
  • Pinned source commit: b6bb4969a663e6a21ce251b76ba6bb79df482c55
  • MCP transport: Streamable HTTP through the Cloudflare host
  • Endpoint: /mcp?elicitation_mode=native
  • MCP client SDK: @modelcontextprotocol/sdk 1.29.0
  • Client: dmmulroy/pi-mcp

Reproduction

  1. Connect an MCP client that advertises both form and URL elicitation capabilities and registers an ElicitRequestSchema handler before client.connect(...).
  2. Connect to the self-hosted Cloudflare endpoint with ?elicitation_mode=native.
  3. Call Executor's execute tool with code that invokes any tool annotated requiresApproval: true (for example, an OpenAPI POST operation).
  4. Wait for the native elicitation.

A minimal standalone client using the same pi-mcp manager as the interactive client produced:

CONNECT_STATUS {"status":"connected"}
MCP error -32001: Request timed out

The deterministic onElicitation callback was never invoked.

The same client repository's local and remote Streamable HTTP elicitation smoke tests pass, including form elicitations. This also reproduces outside the Pi TUI, ruling out its UI/context integration.

Expected behavior

Executor sends elicitation/create to the MCP client. The client responds with accept, decline, or cancel, and the tool call continues accordingly.

Actual behavior

No elicitation arrives at the client. The outer tools/call waits until the client timeout.

Cloudflare Worker logs then show:

[executor:quickjs] tool dispatch defect
Tool "...createTransaction" requires approval but the request was cancelled by the user.
cause._tag = "ElicitationDeclinedError"
cause.action = "cancel"

No upstream mutation was committed.

Relevant source path

packages/hosts/mcp/src/tool-server.ts:

  • Native mode calls engine.execute(..., { onElicitation: makeMcpElicitationHandler(...) }).
  • makeMcpElicitationHandler awaits server.server.elicitInput(...).
  • Its catch block maps every transport/protocol failure to { action: "cancel" }.

That fallback explains the misleading user-cancelled error but obscures the underlying native-elicitation transport failure.

The Cloudflare host builds native mode through:

  • apps/host-cloudflare/src/mcp/agent-handler.ts
  • apps/host-cloudflare/src/mcp/session-durable-object.ts

I could not find a Cloudflare-host end-to-end test covering native elicitation. Existing native tests appear to exercise the generic/in-process MCP server path.

Additional observations

At session construction, Worker logs include:

{
  "clientCapabilities": null,
  "elicitationSupport": { "form": false, "url": false },
  "elicitationMode": "native",
  "resumeEnabled": false
}

That snapshot may be expected before MCP initialization, but it could be relevant if the Cloudflare session/server instance handling the later call does not retain the initialized client capabilities or request channel.

elicitation_mode=browser and elicitation_mode=model avoid this specific native request path and are usable workarounds.

Suggested regression coverage

Add a host-cloudflare integration/e2e test that:

  1. starts a Streamable HTTP MCP session in native mode;
  2. initializes a client with elicitation capabilities and a request handler;
  3. invokes an approval-gated tool;
  4. asserts the handler receives elicitation/create and the accepted/declined response reaches the execution engine;
  5. asserts protocol/transport failures are surfaced distinctly rather than reported as user cancellation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions