Module 1.2 ended with a statement, not a solution: remote transport introduces an authentication requirement that local stdio avoids. This module is the solution. It is deliberately narrow — you will read an auth flow and trace it through a live implementation; you will not implement one. Auth implementation is Part 2 and services territory. Part 1’s job is to make you fluent enough in the flow that you can use a remote server correctly and recognize what its auth layer is doing.
Why local needed nothing
The local stdio server has no authentication because it does not need any. The process model is the gatekeeper: only the user who launched the agent host can reach the server, because the server is a child process of the host. There is no network socket to reach from outside. An attacker with access to your machine already has access to your processes; no server-level auth changes that.
Remote transport inverts this entirely. The server is at a URL. Anyone who can reach that URL can send it requests — which means the URL is a credential surface, and without auth, any caller gets the same capability access as a legitimate user. Auth is not an add-on to remote MCP; it is the premise.
The MCP auth model: OAuth 2.1 + PKCE
The MCP specification (rev 2025-03-26 — the revision the deployed mukadra-mcp-gateway implements, per the header comment of mukadra/infrastructure/cloudflare/mcp-gateway/src/index.ts) mandates OAuth 2.1 for remote servers that require authorization. Specifically, the authorization-code flow with PKCE (Proof Key for Code Exchange, RFC 7636, code_challenge_method: S256).
Why this combination rather than something simpler?
OAuth 2.1 is the current security best practice revision of OAuth 2.0 — it removes the implicit flow (where tokens appeared in URL fragments, exposable in browser history and server logs), tightens PKCE requirements, and mandates HTTPS. It is the industry-standard authorization framework for delegated access.
The authorization-code flow separates “is this caller authorized?” (the authorization step, done once in a browser or consent screen) from “give me a token” (the token step). This separation matters because it means access tokens never appear in URLs and the client never sees the user’s credentials.
PKCE protects the code exchange. When a client starts the flow, it generates a random code_verifier, hashes it to a code_challenge, and sends the challenge to the authorization endpoint. The token endpoint verifies that the code_verifier matches the original challenge. This prevents authorization code interception attacks — an attacker who intercepts the code cannot exchange it for a token without the code_verifier, which never left the legitimate client.
The live gateway implementation enforces this: mukadra/infrastructure/cloudflare/mcp-gateway/src/index.ts rejects any /authorize request where code_challenge_method !== "S256" with an invalid_request error redirect. PKCE is non-negotiable, not optional.
The gateway’s route table
The live Mukadra gateway (mukadra/infrastructure/cloudflare/mcp-gateway/src/index.ts) implements the full MCP auth model. Reading its route table gives you the concrete shape of what the spec mandates:
| Route | Method | What it does |
|---|---|---|
/.well-known/oauth-authorization-server | GET | Discovery. Returns the OAuth metadata document — all the endpoints and capabilities listed, so clients can configure themselves without hardcoding URLs. |
/.well-known/oauth-protected-resource | GET | Resource metadata. Tells clients which authorization servers protect this resource. |
/authorize | GET | Authorization. The client redirects here with response_type=code, client_id, redirect_uri, and the PKCE code_challenge. The gateway auto-approves (internal single-user audience; no consent UI needed) and redirects back with an auth code. |
/token | POST | Token exchange. The client sends the auth code plus the code_verifier. The gateway verifies SHA-256(code_verifier) == code_challenge, then issues an access_token and refresh_token. One-time use: the code is deleted from KV on successful exchange. |
/register | POST | Dynamic client registration (RFC 7591). Clients can register themselves programmatically; the gateway issues a client_id and stores the metadata. |
/mcp | POST | The actual MCP endpoint. Authenticated — requires a valid Bearer token (OAuth-issued, or the static API key for tooling like Claude Code .mcp.json). |
/health | GET | Health check. Also authenticated. Returns status of both backends (pm-synth, CKO). |
The authenticate() function is the gatekeeper: it accepts either an OAuth-issued access_token (looked up in KV) or the static MUKADRA_GATEWAY_API_KEY (for direct integrations like .mcp.json with an explicit Bearer). This two-path design lets the same Worker serve both interactive OAuth clients (claude.ai browser flow) and static-key clients (Claude Code running locally with the API key in env).
The flow, end to end
A new claude.ai session connecting to the gateway for the first time:
- Discovery. The client fetches
/.well-known/oauth-authorization-server. The metadata document tells it where/authorizeand/tokenare. - Authorization. The client generates
code_verifier, computescode_challenge = base64url(SHA-256(code_verifier)), and redirects the browser to/authorize?response_type=code&client_id=...&redirect_uri=...&code_challenge=...&code_challenge_method=S256. - Code issued. The gateway stores
{client_id, redirect_uri, code_challenge}in KV (60-second TTL) and redirects back toredirect_uri?code=<auth_code>. - Token exchange. The client POSTs to
/tokenwithgrant_type=authorization_code, thecode, theredirect_uri, and thecode_verifier. The gateway verifies PKCE, deletes the code, issuesaccess_token+refresh_token, stores the access token in KV (1-hour TTL). - MCP calls. Every subsequent request to
/mcpcarriesAuthorization: Bearer <access_token>. The gateway looks it up in KV; if present, the call proceeds to pm-synth or CKO. - Token refresh. When the access token expires, the client POSTs to
/tokenwithgrant_type=refresh_token. No browser redirect needed; the refresh token issues a new access token silently.
This is reading, not building. You will not implement this flow in Part 1. The gateway’s source code (mukadra/infrastructure/cloudflare/mcp-gateway/src/index.ts) is the canonical reference for when Part 2 asks you to understand the full auth layer from the author’s seat.
Lab: discovery-only — fetch the metadata and map it
This lab is pure reading — no code, no credentials, just a fetch. It verifies that you can navigate the OAuth metadata document and connect its fields to the flow steps above.
In your agent session, ask it to fetch the live gateway’s OAuth metadata endpoint. The URL is:
https://gateway.mukadra.com/.well-known/oauth-authorization-server
(This is the deployed Cloudflare Worker endpoint — no auth required for the discovery document, by spec.)
When the response arrives, map each field to the flow step it serves:
authorization_endpoint→ which step above?token_endpoint→ which step?registration_endpoint→ which step?code_challenge_methods_supported→ what does["S256"]enforce?grant_types_supported→ what does["authorization_code", "refresh_token"]tell a client?
You know it worked when: you can point to each field in the metadata response, name the flow step it corresponds to, and explain in one sentence why PKCE is required rather than optional. If you can do that, you have a working mental model of the auth layer — enough to use remote MCP servers correctly and to follow Module 1.3’s argument into Module 1.4.