MCP Elicitation Sensitive Data Boundaries: Forms, URL Navigation, and Proof of Completion
URL mode accept only means that the user agreed to navigate; it does not prove that authorization or payment was completed. This article separates form data from sensitive credentials and binds user identity, target domains, completion notifications, and retries of the original request.
Key Points
- Topic Focus: AI Agent、MCP、DevOps
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
A Server needs a third-party API key, so it puts a password field in form mode. Another implementation switches to URL mode but marks the original tool call as successful as soon as the user clicks “Open.” More dangerously, an attacker sends their own elicitation link to a victim to complete OAuth, after which the Server binds the victim's token to the attacker. The interactions appear complete, but their identity and completion semantics are entirely misaligned.
MCP 2025-11-25 divides Elicitation into form and URL modes. MCP Authentication Boundaries addresses identity when a Client accesses a Server, while SSO and OAuth explains conventional authorization. URL Elicitation is used when a Server connects a user to a third-party service; the two sets of tokens and principals must not be conflated.
Choose the Channel Based on Data Sensitivity First
During initialization, the Client must declare at least one of the elicitation.form and elicitation.url capabilities. An empty object exists only for compatibility and is equivalent to form. The Server must not send a mode that was not negotiated. A form requestedSchema supports only a flat object and restricted primitive/enum values, so it is unsuitable for nested business objects. Authorization secrets such as passwords, API keys, access tokens, and payment credentials must never pass through form. Before rendering, the Client applies a schema allowlist and classifies sensitive fields; the Server validates them again after receipt. Although the protocol does not categorically prohibit ordinary contact information, users must still have opportunities to review, modify, decline, and cancel.
URL mode stores elicitationNavigation.v1: the elicitationId, Server and verified user subject, original-request digest, target scheme and full host, allowed redirect policy, purpose, creation and expiration times, status nonce digest, and session generation. The URL must not contain credentials, personal information, or a pre-auth bearer; production environments must allow HTTPS only. The Client must not prefetch the URL or metadata or open it automatically. It must first display the complete URL, emphasize the domain, and identify suspicious addresses such as Punycode domains. The opening mechanism must prevent the MCP Client and LLM from reading the page or the user's input. Web CORS/CSP Security cannot replace this isolation boundary.
CREATED -> USER_REVIEW -> DECLINED | CANCELLED | NAVIGATION_ACCEPTED
NAVIGATION_ACCEPTED -> OUT_OF_BAND_PENDING
OUT_OF_BAND_PENDING -> COMPLETION_NOTIFIED -> REQUIREMENT_VERIFIED
OUT_OF_BAND_PENDING -> EXPIRED | USER_RETRY
REQUIREMENT_VERIFIED -> NESTED_SERVER_CONTINUES -> ORIGINAL_RESPONSE_ACCEPTED
REQUIREMENT_VERIFIED -> ERROR_RETRY_ELIGIBLE -> OPTIONAL_CLIENT_RETRY -> RETRY_RESPONSE_ACCEPTED
An Elicitation response has three states: accept, decline, and cancel. A form accept carries validated content. A URL accept carries no content and proves only that the user agreed to navigate, not that OAuth, payment, or credential entry was completed. notifications/elicitation/complete is also only an optional signal. The Client must ignore unknown or already completed IDs and retain manual retry and cancellation controls. The Server should bind external state to the genuine user subject established through authorization, not merely to the MCP session ID or an email address reported by the Client. The person opening the link must also be the same principal who initiated it.
URL mode has two distinct causal chains. If the Server sends a nested elicitation/create while still processing the original request, the Client can only wait after responding with its navigation decision. Once the external condition has been verified, the Server continues the same in-flight request, and the Client must not retry it independently. Only if the original request has already ended with URLElicitationRequiredError (-32042) may the Client mark the requirement as retry eligible and MAY issue one optional retry using the same original-request digest and a new retry generation. The two chains are not interchangeable; otherwise, a second effect can occur while the nested request is still running.
The sole acceptance point for the original business operation is acceptOriginalOperation, not accept or the complete notification. It must either match the original JSON-RPC identity of the nested request, the requirement receipt, and the final response, or match the -32042 error receipt, original-request digest, retry generation, and retry response, before atomically writing one unique business receipt. For OAuth flows, the Server must also validate state/PKCE, the redirect URI, user binding, and token audience in the callback, and store third-party credentials only in a Server-side secret store. Other payment or data-entry flows must retain equivalent completion evidence. The Client bearer must not be sent to the third party, and the third-party token must not be returned to the Client. Short-Lived Capability Leases can restrict retry eligibility, while Human Review Queues can handle high-risk confirmations.
Fault injection should cover a form requesting a password, a URL containing a token, a confusable Unicode domain, a cross-domain redirect, Client prefetch consuming a one-time nonce, an attacker forwarding a link, a subject mismatch, no completion after accept, a duplicate or early complete, a nested flow retried by the Client, repeated retries of a -32042 flow, a notification from an old session, OAuth state/PKCE errors, and credentials leaking into logs. Assert that the number of sensitive fields allowed through form is 0, the number of unknown completion notifications that advance state is 0, the number of cross-user bindings is 0, and the original business operation is accepted at most once in total across both lineages.
Release gates require a 100% block rate for a malicious URL corpus, a false-rejection rate below 0.1% for legitimate domains, explicit consent receipts for 100% of URL navigations, all third-party tokens stored only in secret storage, completion-reconciliation p99 below two minutes, and manual recoverability for 100% of expired flows. For rollout, enable form schema auditing first, then open URL mode to a single internal HTTPS domain. During rollback, stop issuing new elicitations, revoke unfinished nonces, and retain the query page and reconciliation ledger. Disabling the notification listener must not cause externally pending authorization to be treated as either failed or successful.
Related
Related Content
Article
From Copilot to Codex: AI Coding Tools Enter the Agent Era
Technical trends, engineering practices, and indie-developer perspectives around AI, AI Agents, and Vibe Coding.
Article
Why Does MCP Matter? Understanding the AI Tool Protocol
Technical trends, engineering practices, and an independent developer perspective on MCP and AI Agents.
Article
Core Architecture of AI Agents: Planning, Memory, Tools, and Execution
Technical trends, engineering practices, and independent developer perspectives around AI Agents and large language models.
Article
How Should RAG and Agents Work Together?
An independent developer's perspective on technical trends and engineering practices involving RAG and AI agents.