Your AI agent needs to search Gmail for that weekly report. You've built an MCP server, the tool definition, everything's wired up perfectly. One problem: there's no secure path in the protocol to get the OAuth 2.0 bearer token your agent needs to call the Gmail API.
This is the gap between MCP's design and production reality. While the protocol handles client-server authentication beautifully, it completely lacks a mechanism for servers to securely obtain third-party credentials. At Arcade.dev, we've been working to fix this fundamental limitation.
The Technical Problem: Credential Flow in Distributed Systems
Let's break down what's actually happening. Your MCP server needs to make authenticated requests to external APIs. But MCP has no secure credential gathering mechanism.
Current workarounds are all security anti-patterns for multi-user production systems:
- Service account tokens with excessive scopes
- Credentials hardcoded in server configs
- Passing tokens through the MCP client (violating the principle of least privilege)
- Client-side credential storage (hello, token exfiltration risks)
This isn't just bad UX—it's a fundamental security architecture flaw. MCP clients are often untrusted code running on user devices. They're OAuth 2.0 "public clients" that can't securely store secrets. Yet today, that's exactly what developers are forced to do.
Our Engineering Journey: Two Approaches to Secure Auth
PR #475: Adding User Interaction as a Client Capability
Our initial proposal introduced a new client capability for user interactions. The core insight: leverage the browser as a trusted security context, just like OAuth 2.0 has done successfully for 15+ years.
The implementation added a userInteraction capability:
interface UserInteractionRequest {
method: "userInteraction";
params: {
prompt: string;
url?: string;
timeout?: number;
};
}
This allowed servers to redirect users to secure endpoints for credential gathering, keeping sensitive data out of the client execution context entirely. The proposal sparked extensive discussion and security review with 50+ contributors examining attack vectors, CSRF protections, and state management.
But MCP 2025-06-18 shipped with elicitation, a client capability for dynamically rendering forms and gathering data from the user. Elicitation via forms doesn't work for credentials or sensitive data, but could it be extended to enable secure user interactions?
PR #887: Extending Elicitation with URL Mode
Rather than having two similar-but-different client capabilities, we evolved our approach. PR #887 extends the elicitation framework with a new mode:
interface UrlElicitation {
id: string;
mode: "url";
url: string;
message: string;
metadata?: {
oauth_provider?: string;
required_scopes?: string[];
state?: string;
};
}
This creates clear separation of concerns:
- Form mode: Client-rendered UI for non-sensitive data (preferences, parameters)
- URL mode: Direct browser navigation for sensitive flows (OAuth 2.0, payments, WebAuthn, SAML)
The security model is explicit: form elicitation flows data through the client, URL elicitation bypasses the client entirely. But why does that matter?
Deep Dive: Why URL Elicitation Matters
Proper OAuth 2.0 Implementation
Consider implementing GitHub integration. With URL elicitation, you get proper OAuth 2.0:
# Server initiates OAuth flow
def handle_github_tool_call(params):
if not has_valid_token(user_id):
state = generate_secure_state()
code_verifier = generate_code_verifier()
auth_url = build_oauth_url(
client_id=GITHUB_CLIENT_ID,
redirect_uri=CALLBACK_URL,
state=state,
code_challenge=hash_verifier(code_verifier),
scope="repo:read"
)
raise ElicitationRequired([{
"id": f"github-auth-{state}",
"mode": "url",
"url": auth_url,
"message": "Authorize GitHub access",
"metadata": {
"oauth_provider": "github",
"required_scopes": ["repo:read"]
}
}])
Or, something other than OAuth entirely: a redirect to a payment portal, enterprise IDP login page, etc. The client just opens the URL. For the client, that means no token handling, no state management, no security responsibilities.
Respecting Security Boundaries
URL elicitation enforces proper security boundaries:
- Client (untrusted): Facilitates navigation, handles retry logic
- Server (trusted): Manages tokens, validates state, enforces scopes
- Auth provider (trusted): Handles user authentication, consent
This mirrors established web security patterns. The MCP client never touches credentials, preventing entire classes of attacks:
- Token exfiltration via compromised clients
- Scope escalation through client manipulation
- The "confused deputy" problem
Real Implementation Benefits
From our research into production-ready MCP servers at Arcade:
// Before: Insecure token passing
const result = await mcp.callTool("search_gmail", {
query: "weekly report",
token: localStorage.getItem("gmail_token") // 🚨 Security nightmare
});
// After: Secure URL elicitation
try {
const result = await mcp.callTool("search_gmail", {
query: "weekly report"
});
} catch (e) {
if (e.code === "ELICITATION_REQUIRED") {
// Client opens URL, user auths, server stores token
window.open(e.elicitations[0].url);
// Retry after auth completes
}
}
This pattern mirrors what client apps already do to interact with services that require redirects for authorization.
Multi-Provider Authentication
Real agents need multiple auth providers. URL elicitation handles this elegantly:
// Server can request multiple authorizations
throw new ElicitationRequired([
{
id: "gmail-auth",
mode: "url",
url: getGoogleOAuthUrl(),
message: "Authorize Gmail access"
},
{
id: "slack-auth",
mode: "url",
url: getSlackOAuthUrl(),
message: "Connect Slack workspace"
}
]);
What This Enables
With proper authorization, MCP servers are one step closer to being production-ready:
- Scoped access: Request minimum necessary permissions
- Token refresh: Handle expiry without user intervention
- Audit trails: Track what actions were taken with which authorizations
- Revocation: Users can revoke access anytime through the provider
The technical foundations matter. This is the difference between a demo and production infrastructure.
Implementation Timeline
PR #887 is under active review. For early adopters:
- Today: Arcade.dev tools already implement these patterns
- Near term: URL elicitation standardizes the approach
- Future: The MCP ecosystem adopts these patterns in both clients and servers
Want to accelerate this?
- Review PR #887 and stress-test the security model
- Implement URL elicitation in your MCP server
Without a way to securely interact with the user, MCP is limited to servers that only connect to first-party APIs. By adding a mechanism that respects the security boundaries of the client (inspired by the battle-tested patterns used by OAuth), more powerful and interesting MCP servers are possible. We're excited about the future of MCP, what about you?
The Arcade.dev team has spent years hardening auth at Okta, Stormpath, and Redis. We're applying those lessons to make AI infrastructure production-ready.
Want to build AI agents that actually work in production? While we wait for authorization to land in MCP, Arcade already implements secure auth for 100+ integrations. No bot tokens, no security nightmares—just real OAuth flows that work.
Start building with Arcade → Sign Up.