Skip to main content

URL-mode elicitation: the hosted capture page

Some tool calls need a sensitive value from a human — a payment-mandate confirmation token, an OAuth authorization code, an API key. Asking your MCP client (Claude Desktop, Cursor, or any agent harness) to render that value in its own chat window would hand the client, its logs, and its transcript a plaintext copy of the secret. The Orbit MCP server instead uses the MCP specification’s URL-mode elicitation so the value goes straight to Orbit and never transits the client at all.

In-band vs URL-mode elicitation

MCP elicitation has two modes, and this server uses both:
  • Form mode (in-band): the client renders a schema-defined form inline. Orbit uses this for simple yes/no confirmations before high-risk tools (the ones that send, spend, or delete) — see the client setup guide for the budget and allow-list knobs that sit alongside it.
  • URL mode (out-of-band): the tool call fails with a structured JSON-RPC error, code -32042 (UrlElicitationRequired), whose payload carries a URL. The client opens that URL for the human instead of collecting the value itself. Orbit uses this whenever the requested input is a credential.
A spec-compliant client discovers the URL from the error itself — no prior capability negotiation is needed. A client that predates elicitation simply sees the error message and can relay the link to the operator.

How the capture page is reached

When a tool needs a sensitive value, it registers a pending elicitation and throws the -32042 error. The error payload carries one entry:
Error payload shape
The URL points at a public Orbit page — /en/mcp/elicit/<elicitationId> — rendered by the same hosted app as your dashboard. The page asks for exactly one value and explains why: it goes straight to Orbit, never to the agent’s host app.

What the user sees

  1. The capture page renders a single masked input (a password-style field with a show/hide toggle) and a Submit button.
  2. On submit, the page POSTs the value to POST /api/mcp/elicitations/<id>, which relays it server-side to the MCP server.
  3. The server single-uses the elicitation id — the first successful submission resolves it; any replay, expiry, or unknown id is refused.
  4. The page confirms “Sent” and tells the human to return to their agent. The next attempt of the same tool call picks up the resolved value and proceeds.
The submitted value never appears in the MCP client’s messages, logs, or conversation transcript — only in the HTTPS request from the browser to Orbit.

Security notes

  • The URL is a capability token: the elicitationId is a random UUID, single-use and time-limited, so possession of the link is what authorizes exactly one submission.
  • Pending elicitations expire after 15 minutes — a stale link cannot be resolved later.
  • The page is deliberately noindexed (robots: noindex, nofollow) so the link never shows up in search results, and carries no canonical or Open Graph tags.
  • Don’t forward the link. Whoever opens it and submits supplies the value the tool will receive — share it only as you would an OAuth consent link.
  • Elicitation is an additive safety layer. Server-side controls — scoped API keys, the MCP_TOOLS_BUDGET_LIMIT_USD cap, allow/deny lists, and the audit log of every chargeable call — still apply underneath it.

Worked example: payment-mandate confirmation

Consider a tool that charges a card on file and requires a payment-mandate confirmation token:
  1. Your prompt asks the agent to run the charge.
  2. The tool has everything except the mandate token, so it returns the -32042 error with a capture URL. Your client surfaces: “This tool needs a secret to continue — complete it here:” plus the link.
  3. You open the link, paste the mandate token, submit, and see Sent.
  4. Re-run the prompt (or let the client retry). The tool fetches the resolved value and completes the charge — and at no point did the token pass through the client’s chat window.
The same flow fits any secret-shaped input: an OAuth authorization code during a third-party connect, an address-capture value you don’t want sitting in a transcript, or an arbitrary one-field form the tool declares.

Troubleshooting

See also