Approval Flow
Agents increasingly take consequential actions, so they need a clean way to pause and ask a human — confirm, approve — and then continue. MCP’s URL-mode elicitation can push a native “open this link?” prompt to the host, but host support for it is inconsistent.
The idea
Section titled “The idea”Write the approval skill once; the server adapts. Your skill always makes the
same calls — elicit_approval, then elicit_await, then elicit_result.
Underneath, the server pushes a URL-mode elicitation prompt only to hosts that
advertise support for it; every host reaches the same outcome through the same
long-poll.
Two modes
Section titled “Two modes”elicit_approval takes either (or both) of two inputs, and the review page adapts:
- JSON mode — pass
context, a read-only payload. The reviewer sees it and approves or rejects it as-is;elicit_resulthands it back verbatim, so the human approves exactly what runs. - Form mode — pass a
requestedSchema(the flat MCP elicitation subset: strings, numbers, booleans, single/multi-select enums). The review page renders a validated web form; the reviewer’s submission comes back asformSubmission. Use form mode to collect a decision and structured input — a rollback plan, an amount, sign-off initials — in one approval step. Form mode must not request secrets or credentials (per the MCP spec).
Pass both to show read-only context above a form. Whichever you use, the pause/resume flow below is identical.
The tools
Section titled “The tools”Four tools drive the flow — each has its own reference page with exact input/output shapes:
elicit_approval— record the request and (best-effort) push a URL-mode prompt; always returnsreviewUrl.elicit_await— bounded long-poll until the request is decided or the wait bound elapses.elicit_status— non-blocking status read.elicit_result— fetch the reviewer’s decision plus the approved data (idempotent, every fetch recorded).
How a request plays out
Section titled “How a request plays out”The skill’s column never changes. The URL push is the only capability-dependent
step, and both paths converge on the same elicit_await wake.
The long-poll loop
Section titled “The long-poll loop”elicit_await is bounded so it always returns before the host’s tool-call
timeout. Two ways it returns:
- A decision landed — the reviewer approved or rejected; the call wakes immediately with that status.
- The bound elapsed — it returns
"pending"; callelicit_awaitagain to keep waiting.
That single loop works everywhere: on a URL-capable host the reviewer likely
acts on the pushed prompt; on any other host they open reviewUrl — either way
the same wake resolves the same loop.
Expiry
Section titled “Expiry”Every request carries a TTL (timeoutSeconds, default 1 hour, clamped 60 seconds to
24 hours). Once it passes, the request is expired — a terminal state.
elicit_await and elicit_status both report "expired" at the deadline,
and the review page refuses a late decision. Ask the agent to resend if that
happens.