Remote MCP, OAuth, and Enterprise Auth
Protected remote MCP: authorization, then tool traffic
protected-resource metadata → authorization-server metadata
Find the resource's authorization server, then its endpoints and supported features.
pre-registration · client metadata document · optional registration
Use a client identification method the authorization server supports; dynamic registration is optional.
authorization code request + PKCE challenge + resource + state
The user signs in with the authorization server and consents to the requested access.
POST token endpoint: code + verifier + resource
The authorization server validates the code and PKCE verifier before issuing an access token.
initialize request → initialize response → initialized notification → tools/list
MCP requests carry the bearer token. Tool visibility may vary; permission checks still run on calls.
POST /mcp: JSON-RPC tools/call request
The HTTP response can be JSON or an SSE stream. SSE is not required for every call.
The previous MCP post introduced tools, resources, prompts, and a JSON-RPC exchange with a local subprocess. A remote server keeps that protocol while changing who operates the service and how clients connect to it.
This post follows a protected MCP server over HTTP: transport, authorization, token handling, and permission checks. HTTP authorization is optional in MCP; the OAuth flow applies when the server requires delegated access.
Hosting MCP over HTTP
Local stdio connects a host to a child process. Installing a binary does not make it trustworthy, and a subprocess is not automatically sandboxed: absent isolation, it commonly inherits the user’s permissions and configured credentials.
A stdio server can call cloud APIs, including GitHub or a company service. Hosting the MCP server remotely is a different deployment choice: it centralizes operation, updates, and access control rather than requiring every client to run an adapter.
A hosted MCP server can support:
- Multi-tenancy. One server, many users, each seeing only their own data.
- Hosted SaaS. The tool is owned by a vendor (GitHub, Atlassian, Linear). You don't run their backend; you call it.
- Serverless tools. The operator wants scale-to-zero. Spinning up a local subprocess per request isn't the right shape.
- Enterprise control. Security teams want to revoke access centrally, audit every call, and enforce scopes. A hosted server makes these controls easier to centralize, though local adapters can also use scoped and revocable cloud credentials.
The same JSON-RPC exchange over two transports
Both examples start after initialization. An empty tool list is a valid response.
Local subprocess · stdio
Host launches: python calc_server.py
stdin → {"jsonrpc":"2.0","id":1,"method":"tools/list"}
stdout ← {"jsonrpc":"2.0","id":1,"result":{"tools":[]}}
One JSON object per line.The host manages a process and its pipes. Credentials and sandbox permissions are deployment choices; the process can itself call remote APIs.
Remote endpoint · Streamable HTTP
POST /mcp
Authorization: Bearer <access token>
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2025-11-25
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
HTTP/1.1 200 OK
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}This protected endpoint requires a token. If initialization issued a session ID, include MCP-Session-Id too. The server can return JSON or an SSE stream; authorization is optional in MCP.
Prereqs
MCP: A Cross-System Standard for Tool Integration introduces the JSON-RPC methods used here, including tools/list, tools/call, and resources/read.
In function calling as structured generation, the model emits a tool_use block, the host's runtime catches it, and something runs the tool. In the local-stdio case, that "something" is a subprocess. In the remote case, it's an HTTP request to a server we've authenticated against.
Two transports, same protocol
The MCP spec defines two transports. stdio is what we covered last post: a server launched as a subprocess, JSON-RPC over its stdin/stdout. Streamable HTTP carries those messages over HTTP:
- Client-to-server is an ordinary HTTP
POSTwith a JSON-RPC envelope in the body. Single request, single response, for most calls. - Server-to-client streaming uses Server-Sent Events. When a tool call will take a while, or when the server wants to send progress notifications, the server can respond with
Content-Type: text/event-streamand the server emits a sequence ofevent: messageframes, each containing a JSON-RPC object.
Streamable HTTP supports JSON or SSE responses to POST requests and an optional GET stream for server messages. SSE uses ordinary HTTP, but proxy buffering and idle timeouts still need configuration. Clients must send the required Accept headers and handle session and protocol-version headers when applicable.
The MCP methods, schemas, and tool definitions remain the same over either transport. Moving a server to HTTP also requires handling network sessions and, for protected resources, authorization.
The auth model: scoped bearer tokens
Once MCP is on HTTP, the authentication question is the same one every other HTTP API faces: how do we know which user is making this call, and what are they allowed to do?
For a server that supports MCP authorization, the client sends an access token in the Authorization: Bearer header. The server validates the token’s issuer, audience, expiry, and permissions. Authentication of a token and authorization of a particular operation are separate checks.
For interactive access, the client obtains that token through the OAuth authorization-code flow with PKCE.
OAuth 2.1 + PKCE: obtaining authorization
OAuth is about delegated access. You have an account on GitHub. You want Claude Code to act on your GitHub account without handing it your password. OAuth is the protocol that lets you grant it a scoped, revocable, time-limited capability to do that.
The MCP authorization framework builds on OAuth 2.1 and related standards. The interactive flow binds the authorization request to token exchange with PKCE:
Discovery. The client finds the MCP server’s protected-resource metadata, often through a resource_metadata pointer in a 401 challenge. That metadata identifies authorization servers. The client then retrieves the selected issuer’s OAuth or OpenID Connect metadata. The authorization server may be on a different origin from the MCP server.
Client identity. Depending on the deployment, the client uses preregistration, a supported client ID metadata document, or dynamic client registration. Dynamic registration is optional, so a client cannot assume that every issuer exposes a registration endpoint.
PKCE setup. The client generates a random code_verifier (a secret it keeps), computes code_challenge = BASE64URL(SHA256(code_verifier)), and remembers both. At token exchange, this verifier binds the code to the client that initiated authorization, including public clients that cannot keep a client secret.
At token exchange, the server hashes the supplied verifier and compares it with the challenge from authorization. The intercepted code cannot be redeemed with the attacker's different verifier.
Authorize. The client opens the user's browser to the authorize endpoint with the challenge, requested scopes, redirect URI, a fresh state, and the MCP resource URI. The client verifies the returned state and redirect before accepting a code. The user signs in with the authorization server, sees the scopes spelled out, and clicks allow.
Redirect with code. The authorization server redirects the browser to the client's registered redirect URI with a short-lived code. Redeeming it requires the matching verifier.
Token exchange. The client POSTs the code, the MCP resource URI, and the original code_verifier to the token endpoint. The server checks that BASE64URL(SHA256(verifier)), without padding, matches the challenge it saw at authorize time, and if it does, issues an access_token (and possibly a refresh_token).
Authenticated calls. From here on, the client just sticks Authorization: Bearer <token> on every MCP request.
code_verifier = random_urlsafe(64) code_challenge = BASE64URL(SHA256(code_verifier))
PKCE binds this authorization code to the client’s verifier. A stolen code alone is insufficient to redeem the token; the authorization server also checks the verifier. Redirect validation and the other OAuth checks still apply.
Follow which messages go to the MCP resource server and which go to its authorization server.
The native-client example below uses a loopback redirect: the client listens on a loopback address (http://127.0.0.1:<random_port>/cb) and the browser redirects there. RFC 8252 is the spec for this pattern, and it is one documented native-app redirect option. Some constrained clients can't run a local server; some can use the device code grant (RFC 8628) when the issuer and integration support it, where the client displays a user code and the user types it into a separate browser.
The runtime: what actually happens at call time
Walk through an illustrative session. The server and scope-to-tool policy here are examples, not GitHub’s published permission mapping. You open Claude Code in a repo and type "file an issue on the dependency upgrade branch."
- Claude Code needs a remote MCP server for GitHub. It checks its config, sees
https://github.mcp.example.com, and discovers its protected-resource metadata and then the advertised authorization server. - No access token yet, so Claude Code starts the authorization flow. Your browser pops open, you see "Claude Code wants repo, read:user access on GitHub," you click allow. A loopback redirect delivers the code, Claude Code exchanges it for a token. The client stores the resulting credentials securely and handles expiry or revocation.
- After initialization, Claude Code calls
tools/listwithAuthorization: Bearer <token>. In this example, the server filters the response to tools available under the token’s scopes.create_issueis in there.delete_repois not, because you didn't grantadmin. - The model emits a
tool_useforcreate_issue. The runtime packs that into a JSON-RPCtools/call, POSTs it to/mcp. The server streams back progress events over SSE while it talks to the GitHub API, then sends a finalresult. - The runtime appends the tool result to the transcript and calls the model again. The model writes a sentence to you.
stop_reason = end_turn. Done.
The authenticated session runs the MCP lifecycle inside the agent loop from earlier in the arc. Tokens expire, can be revoked, and may need additional consent for broader scopes.
Scopes and the trust boundary
With a remote MCP server, the client presents a token for a particular protected resource. The server enforces permissions on every operation. Filtering tools/list by scope can improve usability, but it is a server policy, not the authorization boundary; an unlisted or previously listed tool must still reject unauthorized calls.
This toy server filters tools/list by scope. Filtering is optional; every invocation still requires server-side authorization. The scope names and mappings here are illustrative, not GitHub’s permission specification.
Central token issuance, revocation, and audit logs can help an organization manage access. They still need implementation: self-contained tokens may remain usable until expiry unless the resource server checks revocation or another policy signal.
Enterprise auth, briefly
An enterprise deployment must connect MCP authorization to its identity and access policies. Possible integration points include:
- SSO via OIDC. The authorization server integrates sign-in with the company's identity provider (Okta, Entra, Google Workspace). The user's corporate account is the one granting the scopes. Deprovisioning must be connected to session and token revocation; already-issued tokens do not necessarily stop working immediately.
- SAML federation. Same idea, older protocol. Still common in big enterprises.
- Service accounts. For server-to-server, non-interactive callers (a CI job calling an MCP-backed deploy tool), you want a client-credentials-style grant or signed JWTs, not an interactive OAuth flow.
- API keys. A possible choice for a single-user server. Key systems can support scopes and rotation, but delegated user consent and identity-provider integration require additional design.
The core MCP authorization contract covers discovery, audience binding, token handling, and interactive delegation. Enterprise identity policies and optional authorization extensions supply additional behavior. Check the version and extensions supported by both peers.
Implementation sketch
A complete OAuth client needs discovery, secure callback handling, state validation, token storage, refresh, and audience binding. The official SDK’s OAuth examples cover those pieces. Here is the smaller transport half after a valid token has been obtained for this server; it lists tools without invoking a mutating operation.
# pip install 'mcp>=1,<2' httpx
import asyncio
import os
import httpx
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
async def main():
endpoint = os.environ["MCP_URL"] # HTTPS endpoint for the protected resource
access_token = os.environ["MCP_ACCESS_TOKEN"] # issued for this resource
async with httpx.AsyncClient(
headers={"Authorization": f"Bearer {access_token}"},
timeout=30.0,
) as http:
async with streamable_http_client(endpoint, http_client=http) as streams:
read, write, _ = streams
async with ClientSession(read, write) as session:
# Includes initialize + notifications/initialized.
await session.initialize()
result = await session.list_tools()
print([tool.name for tool in result.tools])
asyncio.run(main())The SDK handles JSON versus SSE responses, message correlation, and the MCP lifecycle. The caller must supply a valid token and handle refresh.
Misconceptions
"OAuth setup repeats on every tool call." Consent and token acquisition happen outside the model’s tool loop. The client then reuses a valid access token and refreshes or reauthorizes when necessary. Discovery helps interoperability, but client registration and organizational policy still vary.
"API keys and OAuth solve the same problem." Keys can be appropriate for a service or personal integration. OAuth standardizes delegated access and consent. Choose based on who grants access, which resource receives the token, and how credentials are scoped and revoked.
"The model handles auth." The model emits a tool_use block. The host's MCP client is the one that holds the token, attaches the bearer header, handles refresh, and hides the whole thing from the model. The model may see a filtered tool list, but the resource server must enforce permissions even if the model requests a forbidden call.
"Remote MCP means the tool runs in the cloud." Not necessarily. A remote MCP server is just an HTTP endpoint that speaks MCP. What happens behind that endpoint is up to the operator. An HTTP server could forward authenticated work to a local agent on the user’s laptop or to a dedicated VM. The transport says nothing about where the work runs.
What's next
The next post covers planning and reasoning in agent loops: how intermediate work, reasoning budgets, and explicit search affect the choice and ordering of tool calls.
Additional reading (and watching)
-
Model Context Protocol. (2025). Transports. The MCP spec's transports section, defining stdio and streamable HTTP.
-
Model Context Protocol. (2025). Authorization. The MCP spec's authorization section; defines optional HTTP authorization based on OAuth, with protected-resource discovery and audience-bound access tokens.
-
IETF. (2024). The OAuth 2.1 Authorization Framework (draft). The consolidation draft; mandates PKCE, kills the implicit and password grants.
-
Jones, M. et al. (2018). RFC 8414: OAuth 2.0 Authorization Server Metadata. The well-known discovery document spec.
-
Sakimura, N., Bradley, J., Agarwal, N. (2015). RFC 7636: Proof Key for Code Exchange by OAuth Public Clients. PKCE, short.
-
Denniss, W., Bradley, J. (2017). RFC 8252: OAuth 2.0 for Native Apps. The loopback-redirect pattern for CLI and desktop clients.
-
Anthropic. (2025). Remote MCP servers. Anthropic's documentation for the hosted remote MCP feature in Claude.ai and the API.
-
Cloudflare. (2025). Build remote MCP servers on Cloudflare. Cloudflare's guide to running an MCP server on Workers, with OAuth and streamable HTTP.
-
OpenAI. (2025). Responses API: MCP tool type. The Responses API's native support for calling remote MCP servers as first-class tools.
-
Hardt, D. (2012). RFC 6749: The OAuth 2.0 Authorization Framework. The original OAuth 2.0 RFC; background reading for what 2.1 is cleaning up.
-
Richer, J., Jones, M. B., Bradley, J., Machulak, M., & Hunt, P. (2015). RFC 7591: OAuth 2.0 Dynamic Client Registration Protocol. The dynamic client registration spec used by MCP clients that register themselves on first run.
-
Model Context Protocol. Python SDK. Streamable HTTP sessions and OAuth client examples.