ConduitMcp.Cancellation (ConduitMCP v0.9.5)

Copy Markdown View Source

Cooperative request cancellation for long-running tools.

MCP clients can abort an in-flight request by sending notifications/cancelled with the original request's id. Because ConduitMCP is stateless and each HTTP request runs in its own Bandit process, the notification (which arrives on a different request) cannot directly preempt the in-flight handler. Instead, the handler records the cancellation in a shared ETS table and tool code polls this module to decide whether to abort.

Tool integration

Inside a long-running tool, periodically check the conn's request id:

def my_long_tool(conn, params) do
  ConduitMcp.Cancellation.cancelled?(conn)
  |> case do
    true -> {:error, %{"code" => -32800, "message" => "Request cancelled"}}
    false -> continue_work(...)
  end
end

Or use the Process.exit/2 pattern for hard abort. Tools that complete in tens of milliseconds typically do not need cancellation at all — the client's notification will arrive after the response has already been sent.

Lifecycle

  • The handler stashes the request id into conn.assigns[:mcp_request_id] before dispatching to the server callback.
  • On notifications/cancelled arrival, the cancellation flag is set with cancel/2.
  • When a request completes (success or error), the handler calls clear/1 to release the flag so the entry does not linger.
  • For pathological cases (cancel arrives after completion has already cleared the entry, or the server crashes before clear runs), cleanup/1 can be called periodically to prune stale entries by age. There is no janitor wired by default — cancellation entries are bounded by the rate of in-flight cancellations.

Emits [:conduit_mcp, :request, :cancelled] telemetry on cancellation with metadata %{request_id: id, reason: reason}.

Summary

Functions

Marks a request id as cancelled with an optional reason.

Returns true when the given request has been cancelled.

Removes cancellation entries older than ttl_ms milliseconds. Defensive — the handler clears entries inline when a request completes; this is for the rare case where a cancel arrived after a crash or after the response was already sent.

Clears a request id from the cancellation set. Idempotent.

Returns the cancellation reason recorded for the request, or nil.

Functions

cancel(request_id, reason \\ nil)

Marks a request id as cancelled with an optional reason.

cancelled?(request_id)

Returns true when the given request has been cancelled.

Accepts a request id directly, or a Plug.Conn whose assigns contains :mcp_request_id (set by ConduitMcp.Handler before dispatch).

cleanup(ttl_ms)

Removes cancellation entries older than ttl_ms milliseconds. Defensive — the handler clears entries inline when a request completes; this is for the rare case where a cancel arrived after a crash or after the response was already sent.

Returns the number of entries removed.

clear(request_id)

Clears a request id from the cancellation set. Idempotent.

reason(request_id)

Returns the cancellation reason recorded for the request, or nil.