TL;DR: MCP's spec makes authorization optional, and splits it by transport: stdio pulls credentials from the environment, HTTP-based servers follow OAuth 2.1. A personal access token is a separate, simpler mechanism, a static bearer credential you generate once. Neither is more "correct"; they fit different situations, and a server must never accept a token that wasn't issued for it.
If you've set up more than one MCP server, you've probably hit both patterns without anyone explaining why. One server sends you to a browser tab to sign in. Another hands you a token to paste into a config file yourself. Neither is a shortcut or a fallback for the other. They're two different answers to the question of who is making the request, built for two different situations, and the spec that defines MCP treats them as separate concerns from the start.
What does MCP actually authenticate, and at what layer?
MCP's authorization specification is explicit that this is a transport-level concern, not a universal rule every server must implement. The spec states that authorization is optional for MCP implementations, and only kicks in once a server chooses to support it. From there, the two standard transports get different treatment entirely.
A stdio server is a local process the client starts and owns directly, no network hop involved. The spec directs implementations using an stdio transport to skip its OAuth flow and retrieve credentials from the environment instead. A server reached over Streamable HTTP is a different situation: it's a genuine network endpoint, so the spec says HTTP-based implementations should conform to the authorization flow it defines.
That split matters because it tells you where to even look for a problem. A stdio MCP server failing to authenticate is an environment-variable problem: a missing key, a wrong path, a shell that didn't inherit the variable you set elsewhere. An HTTP-based MCP server failing to authenticate is a genuine network auth problem, and everything in the rest of this post is about that case specifically.
How does the OAuth flow actually work for an MCP server?
The spec builds its OAuth flow on OAuth 2.1 rather than inventing something new. A protected MCP server acts as an OAuth 2.1 resource server, capable of accepting and responding to protected resource requests using access tokens, while the MCP client plays the role of the OAuth 2.1 client, and a separate authorization server issues the tokens and handles the user-facing sign-in step. That authorization server can be run by the same operator as the MCP server or by someone else entirely; the spec treats its implementation as out of scope.
Two mechanisms handle a client proving who it is before any of that starts. Client ID Metadata Documents, sometimes shortened to CIMD, are the direction the spec favors going forward. Dynamic Client Registration, the older mechanism, is explicitly marked as deprecated: the spec states that Dynamic Client Registration is deprecated and retained for backwards compatibility with authorization servers that do not support Client ID Metadata Documents. If you're implementing an MCP client from scratch today, that's a real signal about which one to build against first.
Scopes get their own discipline, too. A server can return a scope value in its WWW-Authenticate challenge when it rejects an unauthenticated request, telling the client exactly what it needs to ask for next rather than making it guess:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"
The spec's guidance here follows the principle of least privilege: request only the scopes the current operation needs, and treat whatever scope the challenge names as authoritative for that request, rather than assuming it lines up with the server's full list of supported scopes. Needing more access later, a "step-up" request, is meant to add scopes incrementally rather than re-requesting everything up front.
Two more pieces sit underneath all of this, and they're why an MCP client can't skip straight to putting up a login screen. Every MCP server must implement OAuth 2.0 Protected Resource Metadata, and every client must use that same metadata for authorization server discovery, so a client never has to be told out-of-band which authorization server to trust for a given MCP server. And because the spec requires OAuth 2.1 outright, PKCE is mandatory for every client, not an optional hardening step reserved for public clients without a client secret. PKCE binds the authorization code to the specific request that generated it, which is what actually stops a stolen or intercepted code from being redeemed by anyone other than the client that started the flow.
What is a personal access token, and why would you choose one over OAuth?
A personal access token skips the entire flow above. You generate it once, from wherever the server's own dashboard exposes token management, and it works as a plain bearer credential from then on: no browser step, no consent screen, no redirect URI to register. Prompt Architects' own integrations page frames the tradeoff directly: OAuth handles itself end-to-end, while personal access tokens give you a long-lived header for headless setups.
That's the honest tradeoff. OAuth buys you automatic token refresh and a revocable, time-bounded credential without you managing any of it yourself. A personal access token buys you a setup that works anywhere you can set an environment variable, including a CI runner, a shared dev box, or a scripted job with no browser attached at all, at the cost of a credential that stays valid until someone manually revokes it.
OAuth vs personal access tokens: which one fits your setup?
| Feature | OAuth 2.1 | Personal access token |
|---|---|---|
| Setup step | Sign in via browser, consent once | Generate once from a dashboard, paste into config |
| Needs a browser | ||
| Refresh | Automatic | None — same token until revoked |
| Best fit | Claude Desktop, Claude.ai, an interactive editor session | CI, a headless server, a shared dev box |
| Revocation | From the client or the server's dashboard | From the server's dashboard, immediately |
| Where it lives | Handled by the client, not typed anywhere | An environment variable or a gitignored config file |
The pattern worth internalizing: pick based on whether a human is present to click through a browser tab. If yes, OAuth is almost always the better default, since there's nothing left for you to manage. If no, a personal access token is not a compromise, it's the mechanism actually built for that case.
Not every MCP server offers both. The spec makes authorization optional in the first place, and a personal access token specifically isn't a spec-defined mechanism at all, it's a server operator's own choice to offer a simpler bearer credential alongside whatever OAuth flow the spec describes. Before assuming a headless setup is possible, check the specific server's own documentation for a token or API-key path; a server that only implements OAuth genuinely has no equivalent to fall back to; a browser-based sign-in is not optional there no matter how badly a CI job would prefer otherwise.
What can actually go wrong with MCP authentication?
The spec's own security documentation names the failure mode that matters most here directly, and calls it token passthrough: a server accepting a credential without checking that the credential was actually issued for it, then forwarding that same token downstream unmodified. The consequence is stated as a flat rule rather than a recommendation: MCP servers must not accept any tokens that were not explicitly issued for the MCP server.
Skipping that check breaks more than one thing at once. It defeats whatever rate limiting or request validation the downstream API relies on tokens to enforce, since those controls assume the token was actually meant for the caller presenting it. It also wrecks the audit trail: a downstream service's logs end up showing requests that look like they came from a different identity than the MCP server actually forwarding them, which makes an incident genuinely harder to investigate after the fact. And if one connected service is ever compromised, an attacker holding a passthrough-accepted token can potentially reuse it against every other service that trusts the same MCP server, rather than being contained to the one it leaked from.
A related failure mode, the confused deputy problem, shows up specifically in an MCP proxy server sitting in front of a third-party API that only supports a single, static client ID rather than per-client dynamic registration. Once one user has consented, the third-party server sets a consent cookie tied to that shared static ID, in the user's browser, not the proxy. An attacker who separately registers their own client with the MCP proxy, supplying their own redirect URI, can then send that same user a crafted link: the browser still carries the earlier consent cookie, the third-party server skips its consent screen entirely, and the proxy, having never checked per-client consent on its own side, hands the resulting MCP authorization code to the attacker's registered redirect URI instead of the legitimate client's. The documented fix is for the MCP proxy to maintain its own per-client consent registry and check it before ever starting the third-party flow, regardless of what the third-party server's cookie says.
How does the Prompt Architects MCP server handle this, concretely?
Prompt Architects runs its own MCP server, and its integrations page documents both authentication paths directly rather than leaving you to guess. OAuth is the default: paste the server URL, sign in through a browser tab, and consent once, with the page noting tokens auto-refresh with no expiry headaches. For a headless or CI setup, the alternative is generating a pa_live_… token in Settings → MCP, then passing it as an Authorization: Bearer … header.
Both paths get the same storage treatment on the server side. The page states plainly that tokens, OAuth and personal access alike, are stored hashed, and raw values are shown exactly once, at creation, with revocation from the dashboard taking effect immediately. On the privacy side, the same page states the MCP server never reads your chat history and is stateless beyond per-session memory, for whatever that's worth when you're deciding what to connect it to.
The client list is worth being precise about, because it's easy to over-generalize from a marketing line. The integrations page names six specific setup guides: Claude Desktop, Claude.ai, Cursor, Claude Code, Codex, and Codex CLI. Windsurf is not one of the six, and nothing about the OAuth flow described above changes that; a shared protocol doesn't guarantee a shared client integration exists for a tool the vendor hasn't documented.
Stop rewriting prompts. Start shipping.
Works with ChatGPT, Claude, Gemini, Grok, Midjourney, Ideogram, Veo3 & Kling. 4.8★ on the Chrome Web Store.
Create An AccountHow do the actual config files differ across clients?
This is where OAuth vs personal access token stops being abstract and starts being a specific file you're editing, and the shape of that file depends entirely on which client you're configuring, not on MCP itself. Claude Desktop and Claude Code both read a JSON config where a server entry is just a url field, with OAuth handled behind the scenes once you add the server; setting up MCP in Claude Desktop and using MCP inside Claude Code both cover that shape in full, including where personal-access-token setups differ from the OAuth default.
Codex, by contrast, stores servers in config.toml, and its bearer-token path uses a bearer_token_env_var key pointing at an environment variable name rather than a raw value in the file, keeping the actual secret out of anything you'd commit. If you're setting up Codex or Codex CLI specifically, Codex prompt templates and AGENTS.md examples covers the rest of what's worth configuring in that same config.toml file once the connection itself is working.
None of that is MCP-specific behavior; it's each client's own configuration format wrapped around the same underlying OAuth-or-bearer-token choice this post has been describing. Learning one client's shape doesn't transfer the field names to another, but it does transfer the decision: a human in front of a browser gets OAuth, and a script or a shared machine gets a token, revoked the moment you no longer trust where it's sitting.
What should you actually do with this?
Default to OAuth wherever a human is present to click through it, since it's genuinely the lower-maintenance option once it's set up. Reach for a personal access token only where OAuth can't reach: CI runners, headless servers, shared dev boxes with no browser attached. Keep that token in an environment variable, never typed into a file that reaches a shared repository, and treat revocation as a five-second action you take the moment you're unsure, not a task you schedule for later. The spec's one hard rule, that a server must never accept a token issued for something else, is enforced on the server side; the token discipline on your side is the part that's actually your responsibility.