MCP OAuth: CLI does not try RFC 8414 path-insertion for authorization-server metadata; also ignores registration_endpoint
Summary
Claude Code's MCP OAuth discovery only probes the root /.well-known/oauth-authorization-server. When an MCP server is hosted under a path prefix and publishes its authorization-server metadata via the RFC 8414 path-insertion form, discovery never finds it and the connection times out.
Reproduced against a real, spec-compliant public server: Meta's MCP endpoint at https://mcp.facebook.com/devtools.
Measured with Claude Code 2.1.227 on 2026-08-11.
What is measured
| probe | result |
|---|---|
| GET https://mcp.facebook.com/.well-known/oauth-authorization-server (root) | 404 |
| GET https://mcp.facebook.com/.well-known/oauth-authorization-server/devtools (path-insertion, RFC 8414 §3.1) | 200 — full metadata, issuer=www.facebook.com, PKCE S256 |
| GET https://mcp.facebook.com/.well-known/oauth-protected-resource/devtools | 200 |
The CLI only issues the first request, gets 404, and stalls until a 30s timeout.
Three distinct problems
- Path-insertion not attempted. RFC 8414 §3.1 defines the path-insertion form for issuers with a path component. The server publishes it correctly; the CLI never requests it.
registration_endpointignored. Even once metadata is available, the CLI uses a hardcoded/registerinstead of theregistration_endpointfrom the discovered document (Meta's is/.well-known/register/<path>). This is the same class as #36743, which was closed — the CLI path still exhibits it.- Identity fields must align (secondary). The protected-resource metadata
resource/authorization_serversvalues and theresource_metadatapointer in the 401WWW-Authenticateheader must all resolve consistently. Rewriting only some of them producesFailed to connect: Protected resource ... does not match expected .... Related surface: #85563.
Reproduction
A minimal stdlib-only reverse proxy (195 LOC, binds 127.0.0.1 only) that (a) serves root AS-metadata from the path-insertion form, (b) rewrites registration_endpoint, the protected-resource resource / authorization_servers, and the WWW-Authenticate pointer to the proxy origin.
With the proxy in front, the CLI advances from Failed to connect to Needs authentication — i.e. OAuth discovery completes. That isolates discovery as the failing step.
<details>
<summary>meta_mcp_localhost_sim.py (195 LOC, stdlib only, no credentials)</summary>
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
META-MCP LOCALHOST KESIF-SIMI (is-emri master 1e8b12, 2026-08-11)
KOK (master + claude-code-guide olculdu): Meta MCP (mcp.facebook.com/devtools) SPEC-UYUMLU
(RFC-8414 PATH-INSERTION metadata 200-TAM) ama CLI 2.1.227 o formu denemiyor + bilinen bug
#36743 registration_endpoint'i yoksayip /register'i KOK-pathte hardcode'lar (Meta'ninki
non-standard .well-known/register/devtools). Sim bu iki-uyumsuzlugu localhost'ta koprular.
DORT-PARCA (is-emri):
1. /devtools* -> https://mcp.facebook.com/devtools* AYNEN gecir (header dahil).
2. /.well-known/oauth-authorization-server (KOK) -> Meta'nin PATH-INSERTION metadata'sini
(/.well-known/oauth-authorization-server/devtools) don AMA registration_endpoint'i
kendi simindeki /register'a cevir.
3. /register -> https://mcp.facebook.com/.well-known/register/devtools'a gecir (#36743 hardcode).
4. (kayit-degisimi ayri adim: profillerdeki .claude.json url -> http://127.0.0.1:<port>/devtools)
SINIRLAR (is-emri, MUTLAK):
· YALNIZ 127.0.0.1 dinler (asla 0.0.0.0 — LAN-maruziyeti yok).
· HICBIR header/token/deger LOGLANMAZ — yalniz "METOT YOL -> durum-kodu" satiri.
· Tek-dosya, koddan-okunur.
Calistirma: py meta_mcp_localhost_sim.py [--port 3477]
Saglik: GET /healthz -> 200 {"ok":true}
"""
import argparse
import json
import sys
import urllib.request
import urllib.error
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
UPSTREAM = "https://mcp.facebook.com"
# Meta'nin metadata'yi verdigi PATH-INSERTION formu (kok-path DEGIL):
META_AS_METADATA = "/.well-known/oauth-authorization-server/devtools"
META_REGISTER = "/.well-known/register/devtools"
# Istemciye/upstream'e tasinmayacak hop-by-hop basliklar (RFC-7230):
HOP = {"connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
"te", "trailers", "transfer-encoding", "upgrade", "host", "content-length"}
def _log(metot, yol, kod):
# SIR-GUVENLIGI: yalniz metot+yol+durum. Header/token/govde ASLA.
sys.stderr.write("%s %s -> %s\n" % (metot, yol, kod))
sys.stderr.flush()
class Handler(BaseHTTPRequestHandler):
server_version = "MetaMcpSim/1"
protocol_version = "HTTP/1.1"
def log_message(self, *a): # stdlib'in kendi log'unu SUSTUR (URL/UA sizdirabilir)
pass
def _kendi_port(self):
return self.server.server_address[1]
def _govde_oku(self):
try:
n = int(self.headers.get("Content-Length") or 0)
except ValueError:
n = 0
return self.rfile.read(n) if n > 0 else None
def _yanit_yaz(self, kod, govde: bytes, basliklar):
self.send_response(kod)
for k, v in basliklar:
if k.lower() in HOP or k.lower() == "content-length":
continue
self.send_header(k, v)
self.send_header("Content-Length", str(len(govde)))
self.end_headers()
if govde:
self.wfile.write(govde)
def _passthrough(self, ust_yol):
"""Istegi UPSTREAM'e AYNEN ilet (header+govde+metot), yaniti AYNEN dondur."""
gonderilecek = {k: v for k, v in self.headers.items() if k.lower() not in HOP}
gonderilecek["Host"] = "mcp.facebook.com"
govde = self._govde_oku()
req = urllib.request.Request(UPSTREAM + ust_yol, data=govde, method=self.command,
headers=gonderilecek)
try:
with urllib.request.urlopen(req, timeout=30) as r:
cevap, kod, basliklar = r.read(), r.getcode(), list(r.headers.items())
except urllib.error.HTTPError as e:
cevap, kod, basliklar = e.read(), e.code, list(e.headers.items())
except Exception as e:
cevap = json.dumps({"sim_error": "upstream unreachable: %s" % type(e).__name__}).encode()
kod, basliklar = 502, [("Content-Type", "application/json")]
# PARCA-1b: 401 WWW-Authenticate icindeki resource_metadata pointer'i mcp.facebook.com'u
# gosteriyor -> CLI onu takip edince upstream'e doner (localhost-kimligiyle celisir).
# Pointer'i KENDI protected-resource yoluma cevir (identity-rewrite butunlugu).
onek = "http://127.0.0.1:%d" % self._kendi_port()
yeni_basliklar = []
for k, v in basliklar:
if k.lower() == "www-authenticate" and "mcp.facebook.com" in v:
v = v.replace("https://mcp.facebook.com", onek)
yeni_basliklar.append((k, v))
_log(self.command, self.path, kod)
self._yanit_yaz(kod, cevap, yeni_basliklar)
def _as_metadata(self):
"""PARCA-2: kok-metadata sorulunca Meta'nin path-insertion formunu getir,
registration_endpoint'i KENDI /register'imiza cevir (bug #36743 koprusu)."""
req = urllib.request.Request(UPSTREAM + META_AS_METADATA, method="GET",
headers={"Host": "mcp.facebook.com", "Accept": "application/json"})
try:
with urllib.request.urlopen(req, timeout=30) as r:
meta = json.loads(r.read())
kod = r.getcode()
except urllib.error.HTTPError as e:
_log("GET", self.path, e.code)
self._yanit_yaz(e.code, e.read(), list(e.headers.items()))
return
except Exception as e:
_log("GET", self.path, 502)
self._yanit_yaz(502, json.dumps({"sim_error": type(e).__name__}).encode(),
[("Content-Type", "application/json")])
return
meta["registration_endpoint"] = "http://127.0.0.1:%d/register" % self._kendi_port()
cevap = json.dumps(meta).encode("utf-8")
_log("GET", self.path, "%s(metadata-koprulendi)" % kod)
self._yanit_yaz(200, cevap, [("Content-Type", "application/json")])
def _yonlendir(self):
yol = self.path.split("?")[0]
if yol == "/healthz":
_log(self.command, yol, 200)
self._yanit_yaz(200, b'{"ok":true}', [("Content-Type", "application/json")])
return
# PARCA-2: kok oauth-authorization-server (+openid-configuration ayni-koprü)
if yol in ("/.well-known/oauth-authorization-server",
"/.well-known/openid-configuration"):
self._as_metadata()
return
# PARCA-3: #36743 hardcode'u -> Meta'nin gercek non-standard register-path'ine
if yol == "/register":
self._passthrough(META_REGISTER)
return
# PARCA-1b (kabul-testi bulgusu, 08-11): protected-resource metadata'sinin `resource` ve
# `authorization_servers` alanlari mcp.facebook.com'u gosteriyordu; CLI 'Protected resource
# ... does not match expected http://127.0.0.1:.../devtools' diye REDDETTI. Bu metadata'yi
# da KOPRULE: resource + authorization_servers -> kendi localhost URL'im.
if yol.startswith("/.well-known/oauth-protected-resource"):
self._protected_resource_kopru()
return
# PARCA-1: /devtools* + diger her sey AYNEN gecir
self._passthrough(self.path)
def _protected_resource_kopru(self):
onek = "http://127.0.0.1:%d" % self._kendi_port()
req = urllib.request.Request(UPSTREAM + self.path, method="GET",
headers={"Host": "mcp.facebook.com", "Accept": "application/json"})
try:
with urllib.request.urlopen(req, timeout=30) as r:
meta = json.loads(r.read())
except Exception as e:
_log("GET", self.path, 502)
self._yanit_yaz(502, json.dumps({"sim_error": type(e).__name__}).encode(),
[("Content-Type", "application/json")])
return
if meta.get("resource"):
meta["resource"] = onek + "/devtools"
if isinstance(meta.get("authorization_servers"), list):
meta["authorization_servers"] = [onek] # kok-metadata'yi ben servis ediyorum (PARCA-2)
_log("GET", self.path, "200(protected-resource-koprulendi)")
self._yanit_yaz(200, json.dumps(meta).encode("utf-8"), [("Content-Type", "application/json")])
do_GET = _yonlendir
do_POST = _yonlendir
do_PUT = _yonlendir
do_DELETE = _yonlendir
do_PATCH = _yonlendir
def do_OPTIONS(self): # noqa: N802
self._yonlendir()
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--port", type=int, default=3477)
a = ap.parse_args()
# 127.0.0.1 SABIT (is-emri: LAN-maruziyeti YOK).
httpd = ThreadingHTTPServer(("127.0.0.1", a.port), Handler)
sys.stderr.write("META-MCP-SIM 127.0.0.1:%d (upstream=%s; header/token LOGLANMAZ)\n" % (a.port, UPSTREAM))
sys.stderr.flush()
try:
httpd.serve_forever()
except KeyboardInterrupt:
httpd.shutdown()
if __name__ == "__main__":
sys.exit(main())
</details>
Suggested fix
- Add the RFC 8414 path-insertion form to authorization-server metadata discovery, as a fallback when root discovery 404s.
- Honor
registration_endpointfrom the discovered metadata instead of hardcoding/register.
Related
- #83681 — same root cause on a different surface (claude.ai org connector); this report is the CLI.
- #36743 —
registration_endpointignored (closed; CLI path still affected). - #85563 —
WWW-Authenticateresource_metadatahandling.
Environment
- Claude Code 2.1.227, Windows 10
- Target:
https://mcp.facebook.com/devtools(public, spec-compliant)