Vai al contenuto

MCP

Governare i server MCP con RFC 9728 e RFC 8707: cosa funziona davvero

Scritto da Olivares AI 11 min di lettura

Connetti Claude Code a un server MCP. Il server richiede l’autenticazione. Il tuo cliente invia una richiesta; il server risponde con un’intestazione 401 Unauthorized e WWW-Authenticate: Bearer che contiene un URL resource_metadata. Quello che succede dopo è un flusso OAuth 2.1 definito da tre RFC e una serie di estensioni specifiche per MCP che, prese insieme, risolvono uno dei problemi più difficili nella sicurezza dell’agente: assicurarsi che un token ottenuto per parlare con questo server MCP non possa essere riprodotto contro quello.

Questo post traccia l’intero flusso: cosa dicono le specifiche, cosa forniscono effettivamente le RFC e dove si nascondono le insidie ​​della sicurezza nella pratica.

Il flusso: da 401 a un token legato alle risorse

Il modello di autorizzazione MCP (revisione 2025-11-25, mantenuta invariata nella release candidate — 2026-05-21 congelato — per le specifiche finali previste per 2026-07-28) è un processo in due fasi. La fase 1 è il rilevamento: il 401 che trasporta WWW-Authenticate: Bearer resource_metadata="..." comunica al client che questo server è protetto da OAuth e dove trovare i relativi metadati. La fase 2 è l’accesso autorizzato: consumare i metadati, scoprire il server di autorizzazione, ottenere un token associato a questo server specifico e utilizzarlo.

I passi concreti:

  1. 401 + WWW-Authenticate: il server MCP rifiuta una richiesta non autenticata. Il parametro resource_metadata nella sfida punta al documento Metadati delle risorse protette del server.

  2. Recupero PRM (RFC 9728): il client OTTIENE l’URL /.well-known/oauth-protected-resource. La risposta è un documento JSON che dichiara l’URI canonico della risorsa del server, i server di autorizzazione che lo proteggono e gli ambiti supportati dalla risorsa. Il client verifica che il campo resource del documento corrisponda al server che intendeva raggiungere: una mancata corrispondenza è un segnale di rappresentazione e deve essere rifiutata.

  3. Rilevamento AS (RFC 8414): utilizzando l’URL dell’emittente dall’array authorization_servers di PRM, il client esamina i candidati noti (/.well-known/oauth-authorization-server, quindi /.well-known/openid-configuration) per recuperare i metadati del server di autorizzazione. L’emittente nel documento restituito deve essere byte identico all’emittente per cui è stato recuperato, non “equivalente dopo la normalizzazione”, non “abbastanza vicino”. Byte identico.

  4. Acquisizione token (RFC 8707): il client richiede un token dall’endpoint token di AS, incluso resource=<canonical server URI> sia nelle richieste di autorizzazione che in quelle di token. Ciò vincola il pubblico del token a questo specifico server MCP. AS emette un token il cui pubblico è quella risorsa e il client lo presenta al server.

  5. Accesso autorizzato: il client utilizza il token per chiamare i metodi di introspezione di sola lettura del server MCP (tools/list, resources/list, ecc.). Il token dimostra che il client è autorizzato; l’indicatore delle risorse dimostra che il token è stato coniato per questo server.

Cosa offre effettivamente RFC 9728

RFC 9728 (Protected Resource Metadata) è un meccanismo di rilevamento. Risponde: “quale server di autorizzazione protegge questa risorsa e cosa si aspetta?” Non autentica il server, convalida il token o applica il controllo dell’accesso. Queste sono preoccupazioni separate.

Il documento PRM ha un campo obbligatorio (resource) e una serie di campi facoltativi, di cui authorization_servers è quello più importante. La specifica MCP restringe l’opzionalità della RFC: è richiesto almeno un server di autorizzazione. Un documento PRM che non ne elenca nessuno viene considerato un errore.

Il campo resource è l’URI canonico della risorsa protetta. Il client deve confrontarlo con il server che intendeva contattare e rifiutare una mancata corrispondenza:

// RFC 9728 §3.3: il valore della risorsa PRM DEVE identificare l'oggetto protetto
// risorsa a cui si rivolge il client: semplice confronto di stringhe con
// l'indicatore di risorsa a cui questo client associa i suoi token.
if prm.Resource != c.resource {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: protected resource metadata declares resource %q, "+
        "expected %q (RFC 9728 §3.3 reject)", prm.Resource, c.resource)
}

Questo controllo impedisce una classe di attacco in cui un documento PRM dannoso o mal configurato afferma di parlare per una risorsa diversa. Il confronto non è normalizzato: si tratta di una corrispondenza diretta di stringhe con l’URI canonico della risorsa calcolato dal client per l’URL del server che gli è stato fornito.

Indicatori di risorse RFC 8707: perché l’associazione dei token è importante

Senza indicatori di risorse, un token di accesso ottenuto da un AS può essere potenzialmente utilizzato su qualsiasi server di risorse protetto da AS. Se disponi di due server MCP, ad esempio un server di documentazione di sola lettura e un sandbox di esecuzione del codice, entrambi dietro lo stesso provider di identità, un token ottenuto per uno potrebbe essere presentato all’altro. Questo è un problema da vice confuso.

RFC 8707 risolve questo problema aggiungendo un parametro resource alle richieste di autorizzazione e token. AS emette un token il cui pubblico è esplicitamente l’URI della risorsa. Un server di risorse che convalida correttamente rifiuta un token il cui pubblico non corrisponde alla propria identità.

In pratica, la richiesta del token prevede accanto al contributo l’indicatore della risorsa:

form := url.Values{
    "grant_type": {"client_credentials"},
    "resource":   {c.resource}, // RFC 8707 — vincola il token all’audience
}

Questo viene visualizzato in ogni tipo di concessione utilizzata dal connettore: credenziali client, riscatto del codice di autorizzazione e rotazione del token di aggiornamento. L’indicatore della risorsa non è facoltativo: è presente in ogni richiesta di token, quindi ogni token è vincolato al pubblico per costruzione.

Il controllo dell’emittente identico al byte

La convalida più critica per la sicurezza nell’intero flusso è anche la più semplice da dichiarare e la più facile da sbagliare: il valore dell’emittente nel documento di metadati AS deve essere identico in byte all’emittente utilizzato dal client per costruire l’URL noto.

Non uguale senza distinzione tra maiuscole e minuscole. Non equivalente dopo la normalizzazione dello schema. Non è la stessa cosa dopo aver rimosso una barra finale. Byte identico. La sezione 3.3 di RFC 8414 è esplicita al riguardo e la specifica MCP eredita il requisito.

// discoverASMetadata: RIFIUTA un documento il cui issuer non è
// BYTE-IDENTICO all’issuer per cui è stato recuperato (RFC 8414 §3.3).
// Un documento non corrispondente è un segnale di impersonificazione.
if as.Issuer != issuer {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: AS metadata at %s declares issuer %q, "+
        "expected %q (RFC 8414 §3.3 reject)", cand, as.Issuer, issuer)
}

Perché così severo? Perché un utente malintenzionato che controlla il DNS o si trova sul percorso di rete può fornire un documento di metadati che punta al proprio endpoint token sostenendo di essere un emittente legittimo. Se il client normalizzasse l’emittente prima del confronto, https://auth.example.com e https://AUTH.example.com corrisponderebbero e il documento dell’aggressore verrebbe accettato. Il confronto byte identico chiude questo.

La stessa disciplina si applica a RFC 9207 (convalida dell’emittente della risposta di autorizzazione). Quando il client avvia un flusso di codice di autorizzazione, registra l’emittente dai metadati convalidati AS. Nel reindirizzamento, il parametro iss nella risposta deve corrispondere al valore registrato (ancora una volta, byte per byte, senza normalizzazione) prima che il codice di autorizzazione venga riscattato. Questa è la difesa contro la confusione: senza di essa, un AS dannoso potrebbe intercettare il codice e chiedere al client di riscattarlo presso l’endpoint del token dell’aggressore.

Identificazione cliente: CIMD sostituisce DCR

La specifica MCP definisce un ordine di priorità per il modo in cui un client si identifica nel server di autorizzazione:

  1. Credenziali preregistrate: l’operatore fornisce in anticipo uno client_id e uno client_secret
  2. CIMD (Client ID Metadata Documents): il client ospita un documento JSON su un URL HTTPS; quell’URL è client_id
  3. Registrazione dinamica del client (RFC 7591): il client si registra all’endpoint di registrazione di AS
  4. Avvisa l’utente: non applicabile agli agenti headless

DCR è deprecato nella release candidate per la specifica finale prevista per 2026-07-28, a favore di CIMD. Il motivo è operativo: DCR crea uno stato client persistente sul server di autorizzazione. Ogni agente che si registra lascia dietro di sé una coppia client_id/client_secret che AS deve archiviare e nessuno la tiene traccia o la revoca. Per una flotta di agenti, si tratta di un’espansione incontrollata delle credenziali.

CIMD inverte il modello. Il client ospita un documento in un URL che controlla. AS recupera il documento quando deve convalidare il client, lo memorizza nella cache in base alle intestazioni della cache HTTP e non memorizza nulla in modo permanente. La rotazione delle chiavi è un aggiornamento del documento. La disattivazione di un client comporta la rimozione del documento. Nessuna registrazione orfana si accumula in AS.

Il compromesso: CIMD richiede che il client esegua un endpoint HTTPS. Per un piano di governance self-hosted, questo è naturale: il piano esegue già servizi HTTPS. Per uno strumento CLI su un laptop per sviluppatori, lo è meno, motivo per cui le credenziali preregistrate rimangono la prima opzione nell’ordine di priorità.

Un’identità CIMD non può contenere un segreto condiviso (l’URL del documento è pubblico: un segreto al suo interno costituirebbe una fuga di credenziali). L’autenticazione del client utilizza private_key_jwt (RFC 7523): il client firma un JWT di breve durata con una chiave privata la cui controparte pubblica è pubblicata nel campo jwks del documento CIMD.

La regola del “mai passthrough”.

Una difesa strutturale che è facile trascurare: il connettore utilizza sempre e solo un token che ha ottenuto da solo, per un server specifico, attraverso il flusso di scoperta sopra descritto. Non accetta mai un token da terze parti e lo inoltra a un server MCP. Non legge mai un token da una richiesta in entrata e lo passa attraverso.

Questa è la difesa del vice confuso a livello di protocollo. Se un client inoltrasse i token ricevuti, un utente malintenzionato potrebbe presentare un token con ambito a una risorsa con privilegi bassi e chiedere al client di inoltrarlo a una risorsa con privilegi elevati (o viceversa: estrarre un token con privilegi elevati inducendo il client a presentarlo a un server controllato dall’utente malintenzionato). Ottenendo i propri token e non toccando mai quelli di nessun altro, il connettore non può essere fatto funzionare come un relè di token.

Il passthrough dei token non è solo scoraggiato: è strutturalmente impossibile. Il client HTTP del connettore per i flussi OAuth è separato da qualsiasi gestore di richieste in entrata. Non esiste un percorso di codice che legga un token al portatore da una richiesta in entrata e lo scriva in una richiesta in uscita.

SSRF: la difesa che ricollega il DNS

Ogni URL dell’endpoint dei metadati e del token recuperato dal connettore è protetto da SSRF a due livelli. Il primo è un controllo pre-flight: l’URL deve essere HTTPS (eccetto il loopback per lo sviluppo locale) e non deve essere un indirizzo IP riservato letterale.

Il secondo è un controllo in tempo di chiamata che chiude il TOCTOU di riassociazione DNS. Un nome host che si risolve in un IP pubblico durante il controllo pre-flight potrebbe ricollegarsi a un IP privato nel momento in cui viene composto il socket. Il client HTTP del connettore installa una funzione net.Dialer.Control che ispeziona l’IP risolto concretamente al momento della connessione e rifiuta qualsiasi indirizzo riservato. Questo è il controllo autorevole: il pre-volo è un rapido rifiuto per i casi ovvi, ma il controllo del tempo di chiamata è quello che conta.

Aumento della portata

Un server MCP può rispondere a una richiesta con WWW-Authenticate: Bearer error="insufficient_scope" scope="mcp:tools:list mcp:resources:read". La specifica (SEP-835/SEP-2350) definisce il modo in cui il client gestisce questa operazione: calcola l’unione degli ambiti richiesti in precedenza e gli ambiti appena contestati dal server, quindi acquisisce nuovamente un token con quel set espanso. L’unione conserva le autorizzazioni concesse in precedenza e aggiunge quelle nuove. Lo step-up avviene una volta: non viene ritentata una seconda verifica con ambito insufficiente sulla stessa richiesta, per evitare cicli infiniti.

Il server può essere apolide nella sua sfida: nomina solo gli ambiti di cui ha bisogno l’operazione corrente, non l’insieme completo che il client potrebbe aver richiesto in precedenza. L’accumulo lato client fa sì che tutto ciò funzioni senza che il server tenga traccia della cronologia dell’ambito per client.

Cosa significa in pratica

Il flusso OAuth per i server MCP è ben specificato e, se implementato rigorosamente, affronta le reali superfici di attacco: riproduzione dei token tra server, rappresentazione dei metadati, SSRF riassociazione DNS ed espansione incontrollata delle credenziali client. Il controllo dell’emittente identico al byte, l’indicatore della risorsa in ogni richiesta di token e la regola strutturale di non passaggio sono le difese principali. Non sono funzionalità che puoi implementare a metà: ognuna è un controllo difficile che fallisce.

Il problema più difficile è operativo. I team che distribuiscono server MCP devono:

  • Pubblica un documento PRM valido su /.well-known/oauth-protected-resource con un campo resource che corrisponda all’URI canonico del server
  • Utilizzare un AS che supporti gli indicatori di risorse: molti provider di identità ancora non lo fanno oppure trattano il parametro resource come consultivo anziché vincolante per il pubblico
  • Spostare da DCR a CIMD prima che la deprecazione diventi una rimozione o preregistrare esplicitamente le credenziali
  • Aggiungi credenziali a un emittente per impedire il riutilizzo silenzioso tra più emittenti quando la topologia AS cambia

La documentazione del connettore MCP copre la configurazione operativa e il modello di sicurezza descrive come il token associato alle risorse si inserisce nella mappa di accesso più ampia.

Articoli correlati

Domande frequenti

RFC 9728 garantisce che il server MCP con cui sto parlando sia legittimo?

No. RFC 9728 consente al client di scoprire quale server di autorizzazione protegge una risorsa e quali ambiti richiede, ma si tratta di metadati sulla risorsa, non di prova dell'identità della risorsa. Un certificato TLS dimostra il nome host del server; PRM ti dice come autenticarti. I due sono complementari: PRM senza verifica TLS viene scoperto rispetto a un host non verificato e TLS senza PRM lascia al client il dubbio su come ottenere un token.

Perché la registrazione dinamica del client è deprecata nelle specifiche MCP se funziona ancora?

DCR crea uno stato client persistente sul server di autorizzazione: client_id e client_secret devono essere archiviati e gestiti da AS. Per una flotta di agenti senza testa questo diventa un problema di espansione incontrollata delle credenziali: ogni agente si registra e nessuno tiene traccia o ruota tali registrazioni. CIMD (Client ID Metadata Documents) lo sostituisce con un documento ospitato controllato dal client e AS recupera su richiesta: nessuno stato persistente su AS, nessuna registrazione orfana e la rotazione delle chiavi è un aggiornamento del documento anziché una nuova registrazione.

Scopri cosa possono raggiungere i tuoi agenti

Olivares AI è la piattaforma aperta e in self-hosting per il tuo parco AI. Distribuiscila sulla tua infrastruttura e ottieni la mappa degli accessi che i tuoi team security e platform richiedono.