Authorization, and where the server runs

Lesson 3 of 5 in Building with MCP: Servers, Clients, and the Install Review.

Start with the sentence that surprises people: authorization is OPTIONAL in MCP. The specification defines an authorization flow for HTTP-based transports, and says stdio implementations SHOULD NOT follow it — they retrieve credentials from the environment instead.

That single normative split explains most of MCP’s security surface. A local server launched over stdio inherits whatever the environment hands it: an API token in a config file, an AWS_PROFILE, a live database URL, your kubeconfig. There is no negotiation, no scope, no audience — the process is trusted because you launched it. A remote server over Streamable HTTP has the opposite problem and the opposite tooling: it is reachable by strangers, so the spec gives it a full OAuth-based flow with discovery, scopes, and audience binding.

Both are legitimate. They are simply different threat models with different controls, and the mistake is carrying the habits of one into the other.

Key terms: stdio transport, Streamable HTTP, OAuth 2.1, resource indicators, least privilege, egress control

The discovery dance a client walks the first time it meets a protected server

  1. Client calls the MCP endpoint with no token
  2. 401 + WWW-Authenticate

    The header carries the resource_metadata URL, and SHOULD carry the scopes the client will need.

  3. Fetch Protected Resource Metadata (RFC 9728)

    A server MUST publish this and a client MUST use it. This is how the client learns which authorization server governs this resource — instead of guessing or being told out of band.

  4. Discover the authorization server (RFC 8414 or OIDC Discovery)

    The AS MUST provide at least one; the client MUST support both.

  5. Does the client have a client_id here?

    Three mechanisms exist: OAuth Client ID Metadata Documents (an HTTPS URL as the client_id — SHOULD support as of 2026-07-28), pre-registration, and Dynamic Client Registration (RFC 7591, now deprecated).

  6. Obtain one: CIMD URL, pre-registration, or DCR
  7. Authorization request with the resource parameter

    RFC 8707 Resource Indicators. The resource parameter names the MCP server this token is for.

  8. Token request, also with the resource parameter

    Both requests carry it. The result is a token minted for one audience rather than a bearer token good against anything.

  9. Server: was this token issued for me?

    Audience validation is a server MUST. It is the defence against token passthrough and confused-deputy attacks — a token harvested for service A is useless at server B.

  10. Authorized — tools/call proceeds
  11. Rejected — 401, no matter how valid the token looks elsewhere

Now the deployment question, which is really the same question asked about topology: who launches this process, and who can reach it?

“MCP server” names the program serving context, not where it lives. A local server typically speaks stdio — newline-delimited JSON-RPC over the standard streams of a subprocess the client launched — and serves exactly one client. A remote server speaks Streamable HTTP, where each message is an HTTP POST to a single MCP endpoint and the reply is either a JSON object or a request-scoped SSE stream, and it typically serves many clients at once.

Local stdio vs remote Streamable HTTP — the trade-offs that actually bite
DimensionLocal, stdioRemote, Streamable HTTP

Who starts the process

The client launches it as a subprocess, on the user’s machine, as the user.

You do — it runs as a service, deployed and versioned like any other HTTP service.

Credentials

From the environment. The spec says stdio implementations SHOULD NOT use the OAuth flow. Scope is whatever the env grants.

OAuth-based, audience-bound per RFC 8707, with scopes and a discoverable authorization server.

Blast radius of a compromise

One machine — but that machine holds a developer’s credentials, SSH keys and source. Local does not mean low-stakes.

One service — but potentially every tenant on it. Multi-tenancy makes isolation a design requirement, not a preference.

Network exposure

None by default. If you also bind a port for convenience, you have quietly become a remote server without the controls.

Deliberate. Origin validation, TLS, rate limits and egress rules are all now yours to get right.

Upgrades and revocation

Per user, per machine. Rolling out a fix means every user updating a package — and there is no central kill switch.

Central. Ship once, revoke once, and every client sees the change on the next call.

Observability

Whatever the user’s host logs, plus your stderr. Protocol logging was deprecated in 2026-07-28 in favour of stderr and OpenTelemetry.

Server-side traces, metrics and audit logs across all users — the only place a fleet-wide picture is possible.

Best fit

Access to local state: the filesystem, a local database, a dev toolchain, anything that must run as this user on this box.

Shared systems of record — ticketing, CRM, cloud APIs — where one governed integration should serve everyone.

Interactive checkpoint quiz (2 questions) — open this page in a browser to take it.