MCP URL-Mode Elicitation
Keeps downstream credentials out of the agent and the model.
Flows / Human in the loop
A tool needs the user to sign in somewhere else. It hands back a link, the user completes the flow in a browser, and the secret never touches the agent.
An MCP server often sits in front of someone else’s API. The user has authorized their client to use the MCP server, but the server still needs the user’s permission at the third party: their code host, their CRM, their bank.
The wrong ways to get it are familiar. Ask the user to paste an API key into the chat, where the model and every log will see it. Or have the client forward its own token, which is token passthrough. URL-mode elicitation is the protocol’s answer: in the middle of a tool call the server replies “I need the user to visit this URL.” The client shows the link, the user opens it in a real browser, and the server completes an ordinary OAuth flow with the third party on its own. The credential is stored by the server and is never seen by the client or the model.
The same mechanism covers anything sensitive that should bypass the agent: entering a key, confirming a payment, approving a specific action on a page the server controls.
12 steps between 4 parties. Each step shows the message and who holds which credential afterward. Use ←→ or select any arrow.
Drag to see every party and step. Tap a step to jump to its explanation.
The client is already authorized to the MCP server. Its bearer token identifies the user to the server.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The server looks up the user (from the sub of the MCP access token) and finds no stored token for the third-party service. It must not try the client’s token there.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Instead of a tool result, the server returns an InputRequiredResult containing a URL-mode elicitation. requestState is an opaque value the client will echo back; the server must treat it as attacker-controlled and protect its integrity.
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"resultType": "input_required",
"inputRequests": {
"connect_tracker": {
"method": "elicitation/create",
"params": {
"mode": "url",
"url": "https://mcp.example.com/connect/tracker",
"message": "Connect your issue tracker to continue."
}
}
},
"requestState": "v1.9f2c…"
}
}Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The client must show the complete URL, must not fetch it itself, and must not open it without explicit consent. It opens the link in a browser context the model cannot read.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The recommended design is a “connect” URL on the server’s own domain rather than a direct link to the third party. That page checks that the browser belongs to the same user who made the tool call, which blocks an attacker from sending their own link to a victim.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Here the MCP server is an ordinary OAuth client of the third party, with its own client ID, redirect URI and PKCE.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The consent screen names the MCP server’s application, not the AI client. The user is granting access to the server.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The browser is redirected back to the MCP server, which redeems the code at the third party’s token endpoint.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Stored server-side, keyed to the user. They never transit the MCP client and are never visible to the model.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
There is no completion callback in the current revision. The client simply retries, echoing requestState and an inputResponses entry. An accept response means only that the user agreed to open the link, so the server checks for itself whether the connection now exists.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Using the token it obtained, addressed to the third-party API.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
From the agent’s point of view a tool call took a detour through the user. It holds exactly what it held before.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
See the full tracker for status and links.
Keeps downstream credentials out of the agent and the model.
The most widely implemented authorization profile written specifically for agents and tools.
The baseline MCP profiles. Worth knowing that the normative base of most agent authorization is still, formally, a draft.
How an MCP client that has never seen a server before discovers where to sign in, identifies itself, and gets a token good for that server alone.
The agent never holds the secret. A proxy outside the sandbox adds it to outgoing requests, and only for the hosts it was meant for.
The agent asks the authorization server to go and find the user. Approval arrives as a push notification, not a redirect.
Start with a weak token. When the agent reaches something sensitive, the API says so and the user is asked for exactly that.