MCP Specification 1.2 — Remote Servers and Authentication
Complete reference guide to MCP Spec 1.2's remote server support with standardized OAuth 2.1 authentication. Covers the auth flow, migration path from local to remote servers, Streamable HTTP transport, and implications for agent architecture.
What Changed in MCP Spec 1.2
The defining change in MCP Specification 1.2 (ratified June 2026) is standardized remote server support with OAuth 2.1-based authentication. Before 1.2, every MCP server shipped as a local subprocess — a stdio pipe from your AI client. This worked for development and single-user setups but fell apart at production scale: no access control, no multi-tenancy, no way to share a server across a team or expose internal APIs to AI agents safely.
Spec 1.2 fixes this by defining how an MCP server communicates over HTTP, how clients discover and authenticate to remote servers, and how authorization flows work end-to-end. The result is that MCP servers can now deploy as network services — same protocol semantics, same tool/resource/prompt abstractions, but accessible over the wire with proper auth.
Key Changes at a Glance
| Component | Before 1.2 | After 1.2 |
|---|---|---|
| Transport | stdio (local subprocess) or HTTP+SSE (two endpoints) | Streamable HTTP — single endpoint, POST for requests, optional SSE for streaming |
| Authentication | None (local stdio) or ad-hoc API keys | OAuth 2.1 + PKCE — mandatory for HTTP transports |
| Client Registration | Manual API key provisioning | Dynamic Client Registration (RFC 7591) |
| Token Binding | None | Resource indicators (RFC 8707) — tokens bound to specific server URIs |
| Discovery | None | Authorization Server Discovery via RFC 9728 / RFC 8414 metadata endpoints |
| Scope Management | None | Scoped permissions — read, write, admin per tool/resource |
| Session Handling | None (single-request) | Mcp-Session-Id header with Last-Event-ID resumption |
Streamable HTTP — The Transport That Makes Remote Possible
Spec 1.2 replaces the old dual-endpoint transport (one SSE stream for server-to-client, one POST endpoint for client-to-server) with Streamable HTTP, a single MCP endpoint at a well-known path.
How It Works
POST https://mcp.mycompany.com/mcp
Authorization: Bearer ***
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "query_database",
"arguments": { "sql": "SELECT count(*) FROM users" }
},
"id": 1
}
The server responds with either:
- A single JSON response (Content-Type:
application/json) for synchronous operations - An SSE stream for operations that produce streaming output (progress notifications, partial results, the final response)
Clients open an SSE channel via GET to the same endpoint for server-initiated messages. Sessions are tracked via the Mcp-Session-Id header, and clients use Last-Event-ID to resume after network drops.
Migration Path from SSE to Streamable HTTP
If you're running the legacy HTTP+SSE transport, the migration is straightforward:
- Add the single MCP endpoint at a path like
/mcpor/that accepts POST and GET - Support both transports during transition — the spec includes a backwards-compatibility section allowing servers to host both
/sse(old) and/mcp(new) - Negotiate protocol version — clients send
MCP-Protocol-Version: 2025-06-18(or later); fall back to SSE if the server returns a 4xx - Drop legacy endpoints once all your clients have updated
Every major client now negotiates the Streamable HTTP protocol by default. SSE-only servers will drop off the long tail by end of 2026.
The Authorization Flow End-to-End
Spec 1.2 mandates OAuth 2.1 with PKCE for all HTTP-based MCP transports. Here's how it works in practice, step by step.
Step 1: Authorization Server Discovery
Before any authentication happens, the client discovers the server's authorization configuration. The MCP server exposes a Protected Resource Metadata endpoint (RFC 9728) that includes the authorization server URL:
GET https://mcp.mycompany.com/.well-known/oauth-protected-resource
Response:
{
"authorization_servers": ["https://auth.mycompany.com"],
"resource": "https://mcp.mycompany.com"
}
The client then fetches the authorization server's metadata (RFC 8414):
GET https://auth.mycompany.com/.well-known/oauth-authorization-server
Response:
{
"issuer": "https://auth.mycompany.com",
"authorization_endpoint": "https://auth.mycompany.com/authorize",
"token_endpoint": "https://auth.mycompany.com/token",
"registration_endpoint": "https://auth.mycompany.com/register",
"scopes_supported": ["read", "write", "admin"],
"response_types_supported": ["code"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"]
}
This discovery step is what makes the flow zero-config from the user's perspective — the client learns everything it needs from the server's metadata.
Step 2: Dynamic Client Registration (RFC 7591)
MCP clients (Claude Desktop, Claude Code, Cursor, VS Code, ChatGPT) cannot pre-register with every authorization server in the world. Dynamic Client Registration (DCR) solves this by letting the client register itself programmatically on first contact:
POST https://auth.mycompany.com/register
Content-Type: application/json
{
"client_name": "Claude Desktop",
"redirect_uris": ["http://localhost:8400/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
The server responds with a client_id:
{
"client_id": "abc123",
"client_id_issued_at": 1718496000,
"client_secret": "",
"client_secret_expires_at": 0
}
DCR is the mechanism that makes MCP servers plug-and-play. Without it, every server would require manual OAuth app creation — the exact friction MCP exists to remove.
Caveat for implementers: Dynamic registration means any client can register with your server. For internal servers, restrict registration to known origins or require a pre-shared registration token. For public servers, scope enforcement is your safety net.
Step 3: Authorization Request with PKCE
With a client_id in hand, the client starts the authorization code flow. PKCE (Proof Key for Code Exchange, RFC 7636) is mandatory for all clients — public and confidential alike.
The client generates a code_verifier and derives a code_challenge:
import hashlib
import base64
import secrets
code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()
Then redirects the user to the authorization endpoint:
GET https://auth.mycompany.com/authorize?
response_type=code
&client_id=abc123
&redirect_uri=http://localhost:8400/callback
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&resource=https%3A%2F%2Fmcp.mycompany.com
&state=xyz789
Note the resource parameter — that's RFC 8707, binding the token to this specific MCP server. This prevents token replay against a different server.
The user authenticates (login page, SSO redirect, or web3 wallet), and the authorization server redirects back with a code:
HTTP/1.1 302 Found
Location: http://localhost:8400/callback?code=***&state=xyz789
Step 4: Token Exchange
The client exchanges the authorization code for an access token and refresh token:
POST https://auth.mycompany.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=http://localhost:8400/callback
&client_id=abc123
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&resource=https%3A%2F%2Fmcp.mycompany.com
The server verifies the code_verifier against the stored code_challenge, validates the resource parameter matches the MCP server URI, and returns tokens:
{
"access_token": "eyJhbG...NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"scope": "read write"
}
Step 5: Making Authenticated Requests
Every subsequent MCP request includes the access token as a Bearer token:
POST https://mcp.mycompany.com/mcp
Authorization: Bearer ***
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}
Step 6: Token Refresh
When the access token expires (typically after one hour), the client uses the refresh token to get a new pair:
POST https://auth.mycompany.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
&client_id=abc123
&resource=https%3A%2F%2Fmcp.mycompany.com
The spec requires refresh token rotation for public clients — each refresh invalidates the previous token and issues a new one. This limits the damage window if a refresh token leaks.
Scoped Permissions
MCP Spec 1.2 introduces standard OAuth scopes to gate access to specific tools and resources. The spec doesn't prescribe a naming convention, but the ecosystem has converged on:
| Scope Pattern | Purpose |
|---|---|
read | Read-only tool access (queries, listing) |
write | Mutating operations |
admin | Configuration, user management, destructive operations |
read:<resource> | Tool-specific read scopes |
write:<resource> | Tool-specific write scopes |
Scope Enforcement at the Server
When an MCP server receives a request, it validates the token's scopes before executing the tool. A token with only read scope calling a write tool should get an insufficient_scope error:
{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "Insufficient scope",
"data": {
"required_scope": "write",
"token_scopes": ["read"]
}
},
"id": 1
}
Step-Up Authorization
If a client holds a read token but attempts a write operation, the server can challenge for elevated scopes. The client responds by initiating a step-up authorization flow — the user re-authenticates with the authorization server to grant additional scopes.
Security Considerations
Remote MCP servers introduce a fundamentally larger attack surface than local subprocesses. These are the threats the spec addresses and the ones that still need your attention in implementation.
What the Spec Gets Right
- Audience binding (RFC 8707): Tokens are bound to a specific MCP server URI, preventing token replay across servers
- PKCE is mandatory: Even public clients (desktop apps) must use proof keys, preventing authorization code interception
- No implicit grant: The
implicitflow and resource owner password credentials are explicitly forbidden - No bearer tokens in query strings: Token transmission is restricted to the
Authorizationheader - Refresh token rotation: Limits exposure from leaked refresh tokens
- Prohibits token passthrough: The spec states verbatim: "MCP servers MUST NOT pass through the token it received from the MCP client"
What You Still Need to Handle
Token passthrough is the #1 implementation mistake. The most common error is receiving a client's Bearer token and forwarding it to a downstream API (Slack, GitHub, Stripe). This breaks audience binding and creates a confused-deputy problem — the downstream API trusts the token because it's signed, not realizing it was minted for an entirely different resource. Issue your own token at the MCP server boundary. Validate it locally, then exchange for a separate downstream token using your own credentials.
Prompt injection surface grows with remote tools. Every remote tool you expose becomes an injection vector. An attacker who convinces an agent to call your tool with crafted arguments can exploit SQL injection, command injection, or SSRF vulnerabilities on your server. Apply the same input sanitization and parameterization you would for any public API. Treat every tool argument as untrusted user input.
Audience-claim laxness. If your server validates JWT signatures but not the aud (audience) claim, an attacker can present a token issued for other-server.example.com and gain access. Validate aud, iss, and exp on every request.
Origin-header DNS rebinding. A local MCP server that binds to 0.0.0.0 without Origin validation can be exploited by a malicious website through DNS rebinding. The spec mandates Origin validation and localhost-only binds for local servers.
Revocation propagation. The spec defines token revocation endpoints, but the semantics are soft: when a user revokes an app from the IDP's consent screen, the refresh token is invalidated immediately, but the existing access token keeps working until it expires. For instant revocation, you need short token TTLs (measured in seconds) or token introspection (RFC 7662) on every request.
Migration Guide: Local to Remote MCP Server
The most common deployment scenario in mid-2026 is converting an existing local MCP server to a remote one. Here's the checklist.
Prerequisites
- An MCP server that currently runs as a stdio subprocess
- A deployment target (your own infrastructure, Cloudflare Workers, Railway, Fly.io)
- An OAuth 2.1 provider (Auth0, Okta, Keycloak), or a managed auth service (Cloudflare workers-oauth-provider, ScaleKit, Stytch)
- TLS certificate for your deployment domain
Migration Steps
1. Switch transport from stdio to Streamable HTTP. Wrap your existing tool handlers behind an HTTP server that accepts POST requests to a single /mcp (or root) endpoint. Your tool logic stays the same — only the I/O layer changes.
2. Add the metadata endpoints. Serve /.well-known/oauth-protected-resource (RFC 9728) and link to your authorization server's metadata. This is what makes discovery work without manual configuration.
3. Configure Dynamic Client Registration. Set up the /register endpoint that accepts client registration requests. Your provider library (see options below) handles this for you.
4. Implement the authorization code flow. If you're not using a managed provider, implement:
/authorize— renders a login/consent screen, returnscode/token— exchanges codes for tokens, handles refresh/.well-known/oauth-authorization-server— metadata document
5. Add token validation middleware. Before your tool handlers execute, validate the Bearer token: check signature, issuer, audience, expiration, and scopes. Reject unauthenticated requests with 401 Unauthorized.
6. Add scope checks to each tool. Review each tool handler and annotate it with the minimum scope required. Enforce at runtime.
7. Deploy with TLS. Remote MCP servers must run behind HTTPS. Tokens in cleartext are a compliance violation.
8. Update your clients. Point clients at https://mcp.mycompany.com/mcp instead of mcp-server --port 3100. The first connection triggers the auth flow.
Reference Implementations
If you don't want to build the OAuth plumbing from scratch:
- Cloudflare workers-oauth-provider (workers-oauth-provider) — the OAuth 2.1 provider library used by Cloudflare's own remote MCP examples. Stores secrets only as hashes, wraps your fetch handler so your tool code receives an already-authenticated user.
- Sentry MCP server (sentry-mcp) — open-source TypeScript Streamable-HTTP server with OAuth, deployed to
mcp.sentry.dev. Good reference for how a production SaaS MCP server is structured. - FastMCP (gofastmcp.com) — Python framework with built-in
OAuthProviderthat handles metadata endpoints, DCR, token validation, and scope enforcement.
Implications for Agent Architecture
MCP going remote changes how architects think about tool distribution.
Centralized vs. Distributed Tool Servers
Before 1.2, every MCP server was a local process. An agent's config file listed a dozen mcpServers, and each one spawned as a child process. This worked but created per-client overhead — every instance of every agent had its own copy of every server.
With remote MCP, you can deploy a single shared tool server that multiple agents access simultaneously. This means:
- Unified access control — manage who can call which tools from one place
- Centralized audit logging — every tool invocation routes through your auth layer
- Reduced memory overhead — one server instance instead of N copies
- Consistent tool versions — update once, all agents get the new version
The Gateway Pattern
The MCP ecosystem is converging on a gateway architecture where a single ingress point handles auth, routing, and load balancing for a fleet of MCP servers:
User Agent → MCP Gateway (auth, routing) → Tool Server A
→ Tool Server B
→ Tool Server C
Cloudflare's workers-oauth-provider, ScaleKit, Stytch Connected Apps, and Composio Strata all follow this pattern. The gateway offloads the OAuth 2.1 + DCR + metadata plumbing so individual tool servers only ship business logic.
When to Use Each Approach
| Scenario | Approach | Why |
|---|---|---|
| Single-user developer tools | Local stdio | Zero latency, no auth overhead |
| Team sharing internal tools | Remote, single server | Centralized access, shared state |
| SaaS product exposing APIs via MCP | Remote, gateway-delegated auth | Multi-tenant, scoped, auditable |
| Enterprise with 10+ MCP servers | Remote, gateway + fleet management | OAuth config complexity grows with N servers |
Comparison with Other Remote Tool Protocols
| Dimension | MCP Spec 1.2 | OpenAI Function Calling | Anthropic Tool Use |
|---|---|---|---|
| Transport | Streamable HTTP (single endpoint) | HTTPS POST to /v1/chat/completions | HTTPS POST to /v1/messages |
| Auth | OAuth 2.1 + PKCE (mandatory) | API key (Bearer token) | API key (x-api-key header) |
| Client registration | Dynamic (RFC 7591) | Static API key provisioning | Static API key provisioning |
| Token binding | Resource indicators (RFC 8707) | None (same key for all endpoints) | None |
| Scope model | OAuth scopes (read/write/admin) | No formal scope model | No formal scope model |
| Refresh | Refresh token rotation | API key rotation (manual) | API key rotation (manual) |
| Server-initiated messages | Yes (SSE on GET) | No | No |
| Tool discovery | tools/list (JSON-RPC) | Schema passed in chat request | Schema passed in chat request |
| Multi-tenancy | Built-in (OAuth per user) | Manual (per-user API keys) | Manual (per-user API keys) |
| Revocation | Token endpoint revocation | Key invalidation | Key invalidation |
MCP Spec 1.2's advantage is that it treats remote tool servers as first-class network services rather than thin wrappers around existing APIs. The OAuth 2.1 integration provides enterprise-grade auth that scales from single-user to multi-tenant deployments. The tradeoff is implementation complexity — OpenAI and Anthropic's approaches are simpler because they defer auth and scope management to the provider's API key model.
Pitfalls
DCR without validation. Dynamic client registration means anyone can register. If you're deploying an internal MCP server, add a whitelist or pre-shared registration token. Without this, your server is open to any client that discovers the endpoint.
Token passthrough to downstream APIs. The spec forbids it. The mistake is so common that nearly every MCP security audit in 2026 finds at least one server doing it. Validate the token locally, then issue a separate call to any downstream service.
Clock skew between auth server and MCP server. JWT validation checks exp and nbf. If your auth server and MCP server have drifting clocks (common in containerized deployments), valid tokens get rejected. Run NTP everywhere and verify.
Redirect URI handling for desktop clients. Desktop apps can't receive HTTP redirects to a public URL. The standard approach is starting a temporary local HTTP server on localhost:8400 (or a random port), using that as the redirect URI, and shutting it down after receiving the callback. Handle port conflicts gracefully.
Token storage across sessions. Users don't want to re-authenticate every time they restart their MCP client. On macOS, use Keychain. On Windows, use Credential Manager. On Linux, the situation is less standardized — some clients fall back to encrypted files in the home directory. Get this right early because users will notice immediately if they have to log in every time.
Scope naming consistency. Teams with multiple MCP servers end up with ad-hoc scope names (read:database, db-read, query). Adopt a naming convention before your second server deploys. The emerging standard is resource-type:scope (e.g., database:read, database:write, admin:*).
Quick Reference: Auth Endpoints
| Endpoint | Purpose | Spec Reference |
|---|---|---|
/.well-known/oauth-protected-resource | Protected resource metadata | RFC 9728 |
/.well-known/oauth-authorization-server | Authorization server metadata | RFC 8414 |
/register | Dynamic client registration | RFC 7591 |
/authorize | Authorization code request | OAuth 2.1 Section 4.1 |
/token | Token exchange and refresh | OAuth 2.1 Section 4.1.4 |
/revoke | Token revocation | RFC 7009 |
/mcp (or /) | Streamable HTTP MCP endpoint | MCP Spec 1.2 Transports |
Related Articles
RabbitMQ MCP Server
RabbitMQ MCP server enables AI models to interact with RabbitMQ message brokers, providing capabilities for queue management, message operations, system monitoring, and broker administration through the RabbitMQ HTTP Management API.
Database and Storage MCP Servers
Explore seamless integration with leading database systems and storage solutions through our Database & Storage category. From SQL to NoSQL, cloud to local storage, these integrations enable robust data management, persistence, and scalability for your AI-powered applications.
Supavec MCP Server: Vector Database for AI Applications
Supavec MCP servers enable AI models to interact with vector databases, providing capabilities for storing, searching, and managing vector embeddings for AI applications.