Security
16 min read
74 views

MCP Auth Gets Real: What the 2026-07-28 Spec Means for Local MCP Servers Behind a Tunnel

The 2026-07-28 MCP spec makes remote servers OAuth 2.1 resource servers. See how CIMD, DCR deprecation and .well-known discovery work behind a tunnel.

IT
InstaTunnel Team
Published by the InstaTunnel team | Editorial policy
MCP Auth Gets Real: What the 2026-07-28 Spec Means for Local MCP Servers Behind a Tunnel

Quick answer

MCP Auth 2026-07-28 Spec: OAuth for Tunneled Local Servers: quick comparison answer

Choose the tunnel tool based on the network model: public HTTPS URLs for webhooks and demos, private mesh access for internal apps, and managed infrastructure when policy controls matter most.

Which tunnel tool is best for public webhook testing?

Use a public HTTPS localhost tunnel with stable URLs. InstaTunnel focuses on webhook testing, demos, OAuth callbacks, and MCP endpoint workflows.

When should I choose a private network tool instead?

Choose a private mesh or Zero Trust tool when every user and service should stay inside a controlled private network.

Authentication is the part of MCP that tends to get postponed. A May 2026 measurement study put it plainly: many remote servers treat it as a deployment add-on rather than a security boundary. The 2026-07-28 revision of the Model Context Protocol specification tightens the rules for any server that does use authorization. It keeps OAuth 2.1 resource-server behavior, pins down how a client discovers your authorization server, formally deprecates Dynamic Client Registration (DCR), and makes Client ID Metadata Documents (CIMD) the recommended way for an app to identify itself.

That matters even when your MCP server runs on a laptop. Hosted clients such as claude.ai and ChatGPT connect from the vendor’s cloud, so a local server needs a tunnel before they can reach it. Once you add one, you have a remote MCP server in the eyes of the spec. Your public tunnel URL becomes your token audience, the identifier every access token is bound to. The /.well-known/oauth-protected-resource document has to resolve correctly through the tunnel, or no compliant client will get past the first request.

This guide stays on OAuth discovery and client registration. It covers what changed, what a tunnel must preserve, and how to verify the chain with curl.

What changed in the 2026-07-28 revision

The maintainers locked the release candidate on May 21, 2026, and called it the largest revision of the protocol since launch. The final specification shipped on July 28, 2026. Most of the headline work is outside authorization: a stateless protocol core with no initialize handshake or Mcp-Session-Id, required Mcp-Method and Mcp-Name headers, and a formal extensions framework.

Several things often credited to this revision are older. MCP servers have been OAuth resource servers since mid-2025. The 2025-11-25 revision added CIMD as a recommended registration mechanism, made the WWW-Authenticate header optional with a well-known fallback, and added OpenID Connect discovery. Scalekit’s summary is that the server-side rules have held steady and what moved was client registration and discovery.

What 2026-07-28 itself adds on the authorization side:

  • DCR (RFC 7591) is deprecated. It stays available for authorization servers that lack CIMD support.
  • Issuer validation (RFC 9207). Authorization servers should return an iss parameter in authorization responses. Clients must compare a present iss to the issuer they recorded before redeeming the code. If the server advertises authorization_response_iss_parameter_supported: true and the response has no iss, the client must reject it.
  • Credentials are bound to their issuer. Clients must key persisted credentials by the authorization server’s issuer. They must not reuse them with another server and must re-register if it changes.
  • application_type is required for DCR. Clients that still register dynamically must send an appropriate value.
  • Step-up scope rules. Servers should list every scope an operation needs in a single challenge. Clients should request the union of previously granted and newly challenged scopes.
  • A refresh-token section. Clients must keep refresh tokens confidential and must not assume one will be issued.
  • A formal feature lifecycle policy. It sets a minimum twelve-month window for deprecated features.

Carried forward and still binding: servers must implement Protected Resource Metadata (RFC 9728) and clients must use it for discovery. Clients must send the RFC 8707 resource parameter on both the authorization and token requests, whether or not the authorization server advertises support. Servers must accept only tokens issued for themselves, and must not pass the client’s token through to an upstream API.

One caveat belongs up front: authorization itself is still optional in the spec. HTTP-based transports should conform to it. STDIO servers should not use it and should take credentials from the environment instead. A stdio server on your machine needs no OAuth flow. A tunneled server that you want hosted clients to connect to almost certainly does.

Discovery: how a client finds your authorization server

A compliant client starts with nothing but your server URL. Everything else comes from a fixed discovery sequence, and every step that touches your server travels through your tunnel.

Step 1: The 401 challenge

The client sends an unauthenticated request. Your server answers 401 Unauthorized with a WWW-Authenticate header that points to its metadata:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp-dev.example.com/.well-known/oauth-protected-resource/mcp",
                         scope="files:read"

The scope parameter is a SHOULD, but worth including because it tells the client exactly what to request. If you omit it, clients fall back to scopes_supported from your metadata. Clients must use the resource_metadata URL when the header is present. When it is absent, they must try well-known locations in order: the path-suffixed form (/.well-known/oauth-protected-resource/mcp) first, then the root (/.well-known/oauth-protected-resource).

Claude is stricter than the spec here. It needs a real 401 to start sign-in and ignores a WWW-Authenticate header on a 200 response. Claude’s documentation also notes that the resource_metadata URL doesn’t have to sit on your MCP server’s origin. That helps when a tunnel or edge platform only routes a path prefix and can’t serve /.well-known/* at the root.

Step 2: Protected resource metadata

The document the client fetches (RFC 9728) must list at least one authorization server:

{
  "resource": "https://mcp-dev.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["files:read"],
  "bearer_methods_supported": ["header"]
}

Note the resource field. It must identify your server by the same public URL the client used to reach it. Claude’s documentation says it must equal the URL exactly as the user enters it, including any path. This is the first place a tunnel can quietly break things. Your framework may build the value from the request host it sees locally (localhost:3000) rather than the public hostname.

Step 3: Authorization server metadata

Next, the client fetches the authorization server’s own metadata. For an issuer with a path component such as https://auth.example.com/tenant1, it tries three URLs in order. They are the RFC 8414 form (/.well-known/oauth-authorization-server/tenant1), then two OpenID Connect discovery variants. For an issuer with no path, it tries the RFC 8414 URL and then the OpenID Connect one. The issuer in the returned document must be identical to the issuer used to build the URL, or the client must not use the metadata.

Two details matter. The client records that issuer so it can validate iss later. If code_challenge_methods_supported is missing, the client must refuse to proceed, because it cannot confirm PKCE support. The client then uses the S256 challenge method when it can. The authorization server is usually a separate service, so your tunnel normally only needs to front the resource server. If you do tunnel the authorization server too, the same discovery rules apply to it.

Your tunnel URL is now your token audience

Here is the core shift for local development. Under 2026-07-28, an MCP server must validate that an access token was issued specifically for it, and must reject tokens that don’t name it as the audience. The client binds a token to a server by sending the resource parameter, which must be the server’s canonical URI.

The spec’s canonical URI rules are strict. https://mcp.example.com, https://mcp.example.com/mcp, and https://mcp.example.com:8443 are valid. A bare hostname with no scheme is invalid, and so is any URI with a fragment. The spec prefers the form without a trailing slash.

Now consider a throwaway tunnel URL. A client completes OAuth and the token’s audience is that URL. You restart the tunnel and the hostname changes. Every issued token is now bound to an address that no longer maps to your server, and you re-authorize on every restart.

Whether your hostname survives a restart depends on the tunnel:

  • Cloudflare: quick tunnels get a random trycloudflare.com hostname per cloudflared process, and the URL changes on restart. Named tunnels bind a hostname you choose on a zone you control, and Cloudflare recommends them for anything like OAuth callbacks.
  • ngrok: the free plan includes one automatically assigned dev domain. Check your plan’s terms on whether it persists. Custom domains are a paid feature.
  • InstaTunnel: the CLI accepts a --subdomain flag and has an --mcp mode for MCP servers.

Treat a reserved hostname like a production domain: pick it once, put it in your authorization server’s resource configuration, and keep it. There is one more reason to prefer a domain you control. Claude’s documentation notes that Microsoft Entra ID accepts an MCP server URL as an Application ID URI only on a domain your tenant has verified. A shared tunnel hostname you cannot verify is likely to hit the same wall.

Registration: CIMD replaces DCR

Once the client knows where to authenticate, it needs a client_id. The spec’s order of preference for clients that support every option is:

  1. Pre-registered client credentials, if the client already has them for this authorization server
  2. Client ID Metadata Documents, if the server advertises client_id_metadata_document_supported
  3. Dynamic Client Registration, if the server exposes a registration_endpoint
  4. Prompting the user to enter client details manually

How CIMD works

With CIMD, the client_id is an HTTPS URL. The authorization server fetches the JSON document at that URL, validates it, and caches it according to HTTP cache headers. The requirements are tight:

  • The URL must use https and contain a path component.
  • The document must include client_id, client_name, and redirect_uris.
  • The client_id inside the document must match the URL exactly.
  • The authorization server must validate the redirect URI in each request against the document and clearly display the redirect URI’s hostname during consent. { "client_id": "https://agent.example.com/oauth/client.json", "client_name": "Example MCP Client", "redirect_uris": ["https://agent.example.com/callback"], "grant_types": ["authorization_code"], "response_types": ["code"], "token_endpoint_auth_method": "none" }

VS Code, for example, hosts its client metadata document at vscode.dev/oauth/client-metadata.json, so no registration endpoint is involved. CIMD client IDs are also portable. Nothing needs re-registering when the authorization server changes.

What happened to DCR

DCR is deprecated, not removed. It remains for backward compatibility with authorization servers that lack CIMD support. Under the lifecycle policy adopted in the same release, a deprecated feature stays for at least twelve months. Removal is eligible no earlier than the first revision on or after 2027-07-28. If you still use DCR, clients must now send an appropriate application_type. The spec says native for desktop, CLI, and localhost apps and web for remote browser-based ones. Omitting it defaults to web under OpenID Connect, which collides with a native redirect URI.

DCR also has an operational cost. Anthropic’s documentation says Claude registers a new client on every fresh DCR connection, which can leave very large numbers of client records on your authorization server. For high-traffic servers it recommends CIMD or Anthropic-held credentials.

One maturity note. The spec cites draft-ietf-oauth-client-id-metadata-document-00, while the IETF draft had reached -02 by July 6, 2026. The spec also cites OAuth 2.1 draft -13, and OAuth 2.1 is still an Internet-Draft, with -16 published in September 2026. Pin your reading to the dated spec revision.

Where tunnels break discovery

Most failures here are not OAuth bugs. They are transport and routing bugs that look like OAuth bugs.

Symptom Likely cause Fix
Client says “no authorization server found”, or Claude reports it “couldn’t reach the MCP server” while your authorization server sees no traffic /.well-known/* isn’t routed through the tunnel, returns HTML or a 404, and the 401 has no resource_metadata pointer Route the whole well-known prefix, or serve metadata at a path you do route and point resource_metadata at it
Sign-in never starts in Claude Your server returns 200 with a WWW-Authenticate header Return a real 401
resource_metadata URL contains localhost or http:// App builds URLs from the local request and ignores forwarded headers Configure a fixed public base URL, or trust X-Forwarded-Host and X-Forwarded-Proto from the tunnel (for example Express’s trust proxy setting or Uvicorn’s --proxy-headers)
Metadata fetch returns 401 or a login page A tunnel-level password or access gate sits in front of discovery Exempt /.well-known/ and unauthenticated probes of /mcp, because the client has no token yet
Metadata fetch returns an HTML warning page Free-tier ngrok injects an interstitial in front of HTML browser traffic Programmatic API clients aren’t affected, but browser-based tests are. Send an ngrok-skip-browser-warning header, use a non-standard User-Agent, or upgrade
Requests rejected only through the tunnel Dev-server host allowlists or DNS-rebinding protection don’t recognize the tunnel hostname Add the tunnel hostname to the allowlist
resource mismatch error Metadata says /mcp but the client called a different path Match the canonical URI to the exact URL users paste into the client
Token rejected after restart Tunnel URL changed, so the audience no longer matches Use a reserved hostname
Intermittent connection failures Claude gives discovery, registration, and token endpoints 10 seconds (30 for refresh); a slow tunnel path or cold start can exceed it Keep endpoints fast and avoid buffering behind upstream work
Discovery works from your laptop but fails from the client A WAF or conditional-access rule blocks the vendor’s egress traffic Allowlist the vendor’s published range (Anthropic lists 160.79.104.0/21) on your MCP server and your identity provider
Authorization server cannot fetch your CIMD Document is on a private address or behind a redirect Host it on a public HTTPS URL that responds directly with a 200

Gating a tunnel behind a login is a fine development habit, but discovery is designed to be reachable without credentials. Enforce authentication on the MCP endpoint through token validation, not on the discovery documents. Also make sure the tunnel forwards the Authorization, Mcp-Method, Mcp-Name, and MCP-Protocol-Version headers unchanged.

If you are developing a client, note that Scalekit’s reading of the current IETF draft is that authorization servers should not follow redirects or fetch private addresses, because fetching a client-chosen URL is a server-side request forgery risk. Serve your metadata document directly from a public HTTPS URL. Some hosted servers go further. Medusa’s cloud MCP server only fetches metadata documents from hosts it has approved and does not follow redirects. The spec also states that CIMD alone cannot prevent localhost redirect-URI impersonation. Authorization servers should show extra warnings for localhost-only redirect URIs.

Verify the chain with curl before you test a client

Run these against your public tunnel hostname, not localhost. They mirror what a compliant client does.

# 1. Unauthenticated MCP request: expect 401 + WWW-Authenticate
curl -i -X POST https://mcp-dev.example.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/list" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'

# 2. Fetch the metadata URL from that header: expect JSON
curl -s https://mcp-dev.example.com/.well-known/oauth-protected-resource/mcp | jq

# 3. Confirm the authorization server is reachable and advertises PKCE, CIMD and iss support
curl -s https://auth.example.com/.well-known/oauth-authorization-server | jq \
  '{issuer, code_challenge_methods_supported, client_id_metadata_document_supported,
    token_endpoint_auth_methods_supported, authorization_response_iss_parameter_supported}'

# 4. (Client developers) Check your CIMD answers 200 directly, with no redirect
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://agent.example.com/oauth/client.json

What you are checking:

  1. Step 1 returns 401 with a resource_metadata URL on your public https:// hostname. The 2026-07-28 spec replaced the old HTTP GET endpoint, so a bare GET to /mcp may not return what you expect. The 401 should come back before the server looks at the body.
  2. Step 2 returns JSON whose resource exactly equals the URL clients use.
  3. Step 3 shows S256 in code_challenge_methods_supported. For Claude’s CIMD path, it must also show client_id_metadata_document_supported: true and "none" in token_endpoint_auth_methods_supported.
  4. Step 4 prints 200 and an empty redirect URL.

What real clients do with all this

Client behavior is uneven. This table reflects each vendor’s own documentation as of November 2026. The VS Code row relies on secondary write-ups of its CIMD support.

Client Registration Notes
Claude (claude.ai, Desktop, mobile) CIMD, DCR, Anthropic-held credentials, or a client the customer enters Uses CIMD only when your metadata advertises both client_id_metadata_document_supported: true and "none" in token_endpoint_auth_methods_supported, otherwise DCR. Uses only the first entry in authorization_servers. Redirect URI is https://claude.ai/api/mcp/auth_callback.
Claude Code CIMD or DCR, or pre-configured credentials Uses a loopback redirect on an ephemeral port, so match localhost and 127.0.0.1 without the port.
ChatGPT CIMD preferred; DCR and predefined OAuth clients supported Requires S256 in code_challenge_methods_supported. Sends resource on both the authorization and token requests. Supports RFC 9207 issuer identification.
VS Code CIMD (client document at vscode.dev/oauth/client-metadata.json) or a configured oauth.clientId Redirects to a loopback address.
Cursor DCR, or a static client ID and secret in mcp.json Uses the fixed redirect URL cursor://anysphere.cursor-mcp/oauth/callback.

Vercel’s guidance is blunt: client adoption of the 2026-07-28 spec lags its release. Keep DCR enabled for now, default to CIMD, and list your primary authorization server first. That last point matters because Claude ignores later entries. Clients that run on your own machine, such as Claude Code, can usually reach localhost without a tunnel. The tunnel is for hosted clients that connect from the vendor’s cloud.

Why this is not optional: the 40% problem

Why did the ecosystem need tighter rules? A May 2026 preprint by Zhou and colleagues at Fudan University and Central South University measured authentication across real-world remote MCP servers:

  • It identified 7,973 live remote MCP servers.
  • 3,233 of them (40.55%) exposed tools with no authentication.
  • 2,428 (30.45%) used OAuth, and 2,312 (about 29%) relied on static tokens or API keys.
  • The authors then tested 119 OAuth-enabled servers in depth. Every one had at least one flaw, 325 in total.
  • DCR-related flaws appeared in 96.6% of them, and consent-page bypass was confirmed on 72.

Three caveats apply. It is a preprint, not a peer-reviewed paper. It measured servers implementing revisions up to 2025-11-25, so its numbers predate this spec. And most of the unauthenticated servers the researchers sampled were test or demo deployments, though one was an internal CRM left open by mistake.

A server with no authentication is technically conformant, since authorization remains optional. But a tunnel makes the exposure concrete: you have published a tool-calling endpoint to the open internet.

Frequently asked questions

Is OAuth required for every MCP server?
No. Authorization is optional in the spec. HTTP-based transports should conform to it, while STDIO servers should not and should read credentials from the environment instead.

Is Dynamic Client Registration removed in MCP 2026-07-28?
No. It is deprecated, retained as a fallback for authorization servers without CIMD support, and not eligible for removal before the first revision on or after 2027-07-28.

What is the resource parameter, and can I skip it?
It is the RFC 8707 resource indicator naming the MCP server a token is for. Clients must send it on both the authorization and token requests, regardless of authorization server support. Your server must then reject tokens whose audience doesn’t identify it.

Do I need CIMD support to work with Claude?
Not strictly. Claude falls back to DCR when your metadata doesn’t advertise both CIMD values. For high-traffic servers, Anthropic recommends CIMD or Anthropic-held credentials, because DCR creates a new client record on every fresh connection.

The bottom line

The 2026-07-28 spec moves MCP authentication from “bring your own token” toward a defined, checkable protocol. For local servers behind a tunnel, that comes down to three habits:

  • A stable public URL that serves as your audience.
  • Discovery documents that resolve cleanly through the tunnel, with no login or interstitial in front of them.
  • A CIMD-first registration story, with DCR kept only as a fallback.

Get those right and clients like Claude, ChatGPT, and VS Code can connect without manual setup. Get them wrong and the failure shows up as a cryptic error before your server’s own code ever runs.


Sources

Continue from this article into the most relevant product guides and workflows.

Related Topics

#MCP authentication#MCP auth#MCP OAuth 2.1#MCP 2026-07-28 spec#Model Context Protocol#Model Context Protocol authentication#MCP server security#MCP server authentication#remote MCP server#local MCP server#local MCP server tunnel#MCP server behind tunnel#expose local MCP server#MCP tunnel#MCP OAuth#OAuth 2.1 resource server#OAuth 2.1#OAuth resource server#OAuth protected resource metadata#.well-known/oauth-protected-resource#oauth-protected-resource#OAuth discovery#OAuth authorization server metadata#Client ID Metadata Documents#CIMD#Dynamic Client Registration#DCR deprecated#OAuth client registration#token audience#audience validation#resource indicators RFC 8707#PKCE#MCP client registration#MCP authorization spec#MCP spec update#MCP security best practices#unauthenticated MCP servers#MCP tool security#MCP server vulnerabilities#secure MCP server#MCP authorization#stable public URL#static tunnel URL#persistent tunnel URL#localhost tunneling#localhost tunnel#ngrok alternative#Cloudflare Tunnel#tunneling tools#HTTPS tunnel#reverse proxy tunnel#developer infrastructure#AI agent security#AI agent authentication#LLM tool authentication#DevOps security#API authentication#cloud-native security#zero trust access

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles