Remote vs Local MCP Servers: OAuth, API Keys and stdio
When to run an MCP server locally over stdio and when to use a remote one with OAuth or an API key: security, privacy, performance and enterprise tradeoffs.
Cet article est en anglais.
TL;DR. A local MCP server is a process your client starts on your machine and talks to over stdin and stdout; it gets its credentials from environment variables. A remote MCP server is an HTTPS endpoint the vendor runs; it authenticates you with OAuth or, less often, an API key you paste into a header. Use local when the server needs your files, your browser or your shell. Use remote when the data already lives in a SaaS account and you want nothing to install, update or leak. Most hosted servers today use OAuth: of the 536 servers listed on mcp.tc on 4 October 2026, 366 were remote and 275 of those signed you in with OAuth.
The two shapes an MCP server comes in
The Model Context Protocol defines what a client and a server say to each other (JSON-RPC requests such as tools/list and tools/call) separately from how the bytes travel. The transports page of the specification calls the second part a binding and defines two standard ones.
With stdio, the client launches the server as a subprocess. The server reads one JSON-RPC message per line from stdin and writes one per line to stdout. It may log to stderr, and it must not write anything else to stdout. When the client closes the input stream, the server is expected to exit. This is what "local MCP server" means in practice: a command in your client config, usually npx, uvx or docker run.
With Streamable HTTP, the server is an independent process behind a single HTTP endpoint, for example https://mcp.sentry.dev/mcp. The client sends every request as its own POST, and the server answers with a JSON object or, for longer work, a Server-Sent Events stream carrying progress notifications before the final response. This is the "remote MCP server". Streamable HTTP replaced the older HTTP+SSE transport in protocol version 2025-03-26; HTTP+SSE is now listed in the deprecated features registry, so a server that still only offers an /sse endpoint is on borrowed time.
The current protocol version is 2026-07-28. That revision also removed the server-assigned Mcp-Session-Id and the standalone GET stream from Streamable HTTP, so a modern remote server is stateless per request and there is no session to pin behind a load balancer.
Nothing stops a server from offering both. GitHub runs a hosted endpoint at https://api.githubcopilot.com/mcp/ and also publishes a Docker image you can run over stdio with a personal access token. Brave Search ships as an npm package that defaults to stdio but can start in HTTP mode with --transport http.
How credentials work on each side
Local servers: environment variables
The authorization section of the spec is explicit that it covers HTTP transports, and that "implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment". So a local server that needs a key reads it from a variable such as BRAVE_API_KEY or GITHUB_PERSONAL_ACCESS_TOKEN, and your client puts that variable into the process environment when it spawns the server.
In Claude Code that looks like this:
claude mcp add --env BRAVE_API_KEY=your-key --transport stdio brave-search \
-- npx -y @brave/brave-search-mcp-serveror, in a project's .mcp.json, with the secret kept out of the file:
{
"mcpServers": {
"brave-search": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@brave/brave-search-mcp-server"],
"env": { "BRAVE_API_KEY": "${BRAVE_API_KEY}" }
}
}
}The ${VAR} expansion is a Claude Code feature; other clients have their own way to reference secrets, and some want the literal value in the config file.
Not every local server needs a key. On mcp.tc, 109 of the 170 local listings (on 4 October 2026) run with no credentials at all, because what they expose is already yours: the Filesystem server takes the directories it may touch as command-line arguments, Playwright drives a browser on your machine, Git works on a repository path. Eleven local listings sign you in with OAuth anyway. Some wrap a vendor CLI that already has a login command (Azure, Snyk, Fly.io), which opens a browser once and caches the token; others, such as Google Workspace and Microsoft 365, run a browser sign-in the first time they start.
Remote servers: OAuth 2.1
For HTTP transports the specification adopts OAuth 2.1 and assigns the roles clearly: the MCP server is the resource server, the MCP client is the OAuth client, and a separate authorization server issues tokens. The flow, trimmed to what you see as a user:
- The client sends a request without a token. The server answers
401 Unauthorized, usually with aWWW-Authenticateheader that points at its protected resource metadata (RFC 9728). Every MCP server that requires authorization must publish that document, either at the URL in the header or at a well-known path. - The client reads that document to find the authorization server, then fetches the authorization server's own metadata.
- The client opens your browser. You sign in with the account you already have and approve the scopes shown.
- The client exchanges the code for an access token (with PKCE, and with a
resourceparameter naming the MCP server as required by RFC 8707) and sends it asAuthorization: Bearer ...on every request. Tokens must never go in the query string.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"If you build or evaluate servers, note that the server must check that a token was issued for it specifically, and must not accept or forward tokens meant for another service; the security best practices document calls forwarding "token passthrough" and forbids it. Client identification changed too: Client ID Metadata Documents (the client identifies itself with an HTTPS URL that serves its metadata) have been the recommended mechanism since 2025-11-25, and 2026-07-28 deprecated Dynamic Client Registration.
From the user's chair, all of that collapses into "a browser tab opened and I clicked Allow". In Claude Code, /mcp lists the servers that still need that step; in Claude Desktop and claude.ai it is the Connect button under Customize, Connectors. The connect guide walks through each client.
Remote servers: API keys in a header
The spec does not define API-key authentication for HTTP, and on 4 October 2026, 23 remote servers on mcp.tc took a key or token in a header anyway, among them Google's Maps Grounding Lite endpoint and Weights & Biases. The client sends the key as a header on each request; this is the command from W&B's docs, with the key read from an environment variable:
claude mcp add --transport http wandb https://mcp.withwandb.com/mcp \
--header "Authorization: Bearer $WANDB_API_KEY"This is simpler to set up and harder to live with. The key is long lived, carries whatever permissions the vendor attached to it, and sits in a config file on every machine that uses the server. OAuth tokens expire, are bound to the MCP server by audience, and can be revoked from the account page.
When local is the right call
Pick a local server when the capability is on your machine. A remote server cannot read ~/projects/app/src, click through a page in your logged-in browser, or run kubectl against the context in your kubeconfig. Local servers do those things because they run as you, with your privileges.
Local is also the right call when the data must not leave the laptop: a database server pointed at a staging instance on a private network, a notes vault, a repository under NDA. Nothing goes to a third party except the API calls the server itself makes. And for a solo developer who already has the key, npx -y plus an environment variable takes thirty seconds.
The cost is maintenance and trust. Each local server is a dependency you install on every machine, and npx -y package@latest downloads whatever the registry currently has. The spec's security document spells out the risk: a one-click install can embed a startup command that exfiltrates ~/.ssh/id_rsa, which is why clients must show "the exact command that will be executed, without truncation" and ask before running it. Prefer pinned versions or Docker images, and read the command before you approve it.
When remote is the right call
Pick a remote server when the data already lives in a SaaS account and the vendor runs an endpoint. Sentry's hosted server is a good example: all connections use OAuth, there is nothing to install, and the vendor recommends scoping the URL to a project, which limits the tools to that project. Linear, Notion, Stripe, Figma and Supabase follow the same pattern, as do 24 of the 29 hosted servers in the developer-tools category.
Remote wins when more than one person or runtime needs the same tools. A cloud agent, a scheduled job or a colleague on a different OS pastes one URL and signs in with their own account. Permissions follow the account, so an engineer who loses access to a Sentry org loses the MCP tools with it. Updates happen on the vendor's side. And with OAuth the client never holds a long-lived secret, while scopes limit what a stolen token can do; compare that with a local server holding a full-access personal access token in .mcp.json.
The cost is that the vendor, and any intermediary, sees your requests. That is fine when the vendor already holds the data. It is a real decision when a hosted server proxies something it does not own, such as a search endpoint that sees every query your assistant sends.
Performance
A stdio server costs almost nothing per call: a line written to a pipe. Its cost is at startup, when npx or uvx resolves and possibly downloads the package, which can take seconds the first time and on cold caches. Clients keep the process alive for the session, so you pay once.
A remote server adds a network round trip per tool call, plus the vendor's own latency and the occasional cold start. For long operations the SSE response stream carries notifications/progress so the client is not left staring at a spinner. Server operators should set X-Accel-Buffering: no on streams, as the spec recommends, or an nginx in front will buffer the events and the "streaming" arrives in one lump at the end.
The model's own round trip dwarfs both, so the difference is felt mostly on servers called in tight loops, such as a filesystem server during a refactor, where local is noticeably snappier.
Enterprise considerations
Audit and identity are the main reasons large teams push toward remote. With OAuth, every call carries a token tied to a named user, scopes are visible in the consent screen, and the vendor's logs show who did what. A local server with a shared API key gives you one identity for the whole team.
Gateways are easier with the 2026-07-28 revision. Every POST now carries Mcp-Method and, for tool calls, Mcp-Name headers mirroring the body, so a proxy can route, rate limit or deny a specific tool without parsing JSON. The server must reject requests whose headers do not match the body, so a gateway and the server cannot disagree about what was called.
Egress control matters in both directions. The OAuth discovery steps have the client fetch URLs supplied by the server, which a malicious server can point at 169.254.169.254 or an internal admin panel; the spec asks server-side clients to block private ranges and route through an egress proxy. On the local side, any website a user visits can reach a local HTTP server through DNS rebinding, whatever address it binds to, which is why Streamable HTTP servers must validate the Origin header; binding to 127.0.0.1 rather than 0.0.0.0 also keeps other machines on the network out.
Supply chain is the local server's weak point. npx -y some-server@latest on two hundred laptops means two hundred unpinned installs. If you must run local, mirror the package, pin the version and ship it through the same channel as any other internal tool. Is this MCP server safe? has a checklist for vetting a package before it reaches those laptops.
If you build a server for others, pick the transport by what the server touches: a wrapper around a SaaS API belongs on Streamable HTTP with OAuth and a scoped token, a server that needs the user's files, shell or browser belongs on stdio. The server-owner docs explain what the mcp.tc checker reads from your package and endpoint, and verified listings carry a checkmark once you prove the domain.
Quick decision table
| Question | Local (stdio) | Remote (Streamable HTTP) |
|---|---|---|
| Needs my files, shell or browser | Yes | No |
| Data stays on my machine | Yes, apart from the server's own API calls | No, the vendor sees requests |
| Credentials | Environment variables | OAuth 2.1 (or an API key header) |
| Setup on a new machine | Install runtime and package | Paste one URL, sign in |
| Updates | You reinstall | Vendor deploys |
| Identity in audit logs | Whatever the key represents | The signed-in user |
| Per-call latency | Pipe write | Network round trip |
FAQ
Is a remote MCP server less secure than a local one?
Not in general; the risks sit in different places. A remote server sees your requests and holds the data on the vendor's side, but with OAuth your client never stores a long-lived secret and a stolen token is scoped and revocable. A local server keeps data on your machine but runs with your full privileges and often holds a static API key in a config file.
Can I use an API key with a remote MCP server?
Yes, if the vendor supports it. The specification defines OAuth 2.1 for HTTP transports and does not describe API keys, but clients such as Claude Code let you attach a header to any remote server. Prefer OAuth when both are offered.
Does OAuth apply to stdio servers?
No. The specification says stdio implementations should not follow the HTTP authorization flow and should read credentials from the environment. A few local servers open a browser on first run because they reuse a vendor CLI's login, but that is the CLI's own mechanism.
What happened to SSE servers?
The HTTP+SSE transport from protocol version 2024-11-05 was replaced by Streamable HTTP in 2025-03-26 and is now deprecated. Clients that support older servers fall back to it when a POST fails (Claude Code and VS Code both do), so old servers keep working, but new servers should expose a single MCP endpoint that accepts POST.
How do I tell which kind a listed server is?
Every listing on mcp.tc has a badge next to the name: No sign-in, Sign-in, API key or Sign-in optional for a remote server (Unconfirmed when our checker couldn't tell), Local for one you run yourself. The page also has the URL or install command and the setup steps for each client. Browse the directory and filter by what you need.
Sources
Les pages citées dans cet article. Nous rédigeons les articles avec l’aide de l’IA à partir des sources qu’ils citent, et nous les vérifions sur ces sources avant de les publier. Les chiffres sur l’annuaire viennent de la base de données de mcp.tc.
- modelcontextprotocol.io/specification/2026-07-28/basic/transports
- modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio
- modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
- modelcontextprotocol.io/specification/2026-07-28/basic/authorization
- modelcontextprotocol.io/specification/2026-07-28/deprecated
- modelcontextprotocol.io/specification/versioning
- modelcontextprotocol.io/specification/latest/basic/security_best_practices
- datatracker.ietf.org/doc/html/rfc9728
- www.rfc-editor.org/rfc/rfc8707.html
- datatracker.ietf.org/doc/html/rfc6750
- code.claude.com/docs/en/mcp
- github.com/github/github-mcp-server
- mcp.sentry.dev/
- github.com/brave/brave-search-mcp-server
- docs.coreweave.com/products/wandb/platform/ai-assistants