Come un server MCP chiama la tua API per conto degli utenti: una strategia di token in produzione
Spiega la strategia di token in uscita per i server MCP: perché il passaggio di token e l'M2M falliscono, e come lo scambio di token mantiene i permessi allineati con quelli degli utenti.
YijunDeveloper
Smetti di sprecare settimane sull'autenticazione degli utenti
Lancia app sicure più velocemente con Logto. Integra l'autenticazione degli utenti in pochi minuti e concentrati sul tuo prodotto principale.
Come un server MCP chiama la tua API per conto degli utenti: una strategia di token in produzione#
Nel nostro articolo precedente, abbiamo condiviso la nostra esperienza generale di costruzione del server MCP remoto di Logto. Questo articolo approfondisce il design dell'architettura e il flusso OAuth nei dettagli.
Con il server MCP come confine, l'autenticazione per un server MCP remoto ha due gambe: in entrata e in uscita.
In entrata: il client MCP (VS Code, Cursor, ecc.) esegue il sign-in tramite OAuth, ottiene un token di accesso e lo utilizza per accedere al tuo server MCP
In uscita: durante la gestione delle chiamate agli strumenti, il server MCP richiede la tua API di business per conto dell'utente
La parte in entrata è ben definita nelle specifiche MCP, con molte discussioni e implementazioni nella community. Abbiamo anche scritto una guida all'implementazione in precedenza. La parte in uscita riceve molta meno attenzione: il server MCP possiede solo un token per accedere a sé stesso. Come può chiamare la tua API di business per conto dell'utente?
Questa è la domanda con cui abbiamo lottato durante la costruzione del server MCP di Logto. Logto Cloud è un tipico prodotto multi-tenant B2B: un utente può appartenere a più tenant, e i permessi derivano dal ruolo dell'utente in ciascun tenant. L'IA non solo deve agire come l'utente, ma deve anche operare sul tenant corretto.
Questo articolo segue il nostro vero processo decisionale:
Perché il server MCP dovrebbe essere distribuito indipendentemente, come risorsa protetta separata
Due approcci in uscita che abbiamo scartato (passaggio di token e M2M) e i loro problemi
Il design finale: scambio di token più subject token, incluso come i token di organizzazione gestiscono la multi-tenancy
Alcuni principi da seguire se costruisci qualcosa di simile
Il problema centrale: il doppio ruolo di un server MCP#
Dal punto di vista di OAuth, un server MCP remoto svolge due ruoli contemporaneamente:
Per il client MCP, è un resource server: il client deve presentare un token per accedervi
Per la business API, è un client: porta un token per accedere a qualcos'altro
In altre parole, il server MCP svolge un ruolo diverso su ciascun lato, e ciascun lato usa un token diverso. Il token che il client MCP ottiene tramite OAuth ha il server MCP come audience, quindi la business API lo rifiuterà quando ne verifica l'audience.
Quindi la parte in uscita è essenzialmente un problema di delegazione: come fa il server MCP a chiamare le API downstream come l'utente, nei limiti dei permessi dell'utente, senza possedere le credenziali dell'utente?
Decisione architetturale: il server MCP come servizio standalone#
Integrare l'endpoint MCP all'interno del servizio API di business, condividendo lo stesso processo e lo stesso stack di autenticazione, sembra l'opzione più diretta. Dopo aver valutato questa possibilità, abbiamo scelto la distribuzione standalone:
Isolamento del rischio: quando abbiamo preso questa decisione, l'SDK ufficiale MCP non era pronto per la produzione e il protocollo stesso stava evolvendo rapidamente (il passaggio del trasporto da SSE a Streamable HTTP è un esempio). Logto è un servizio IAM e la disponibilità del percorso di sign-in è il requisito minimo. Con la distribuzione standalone, se il server MCP si rompe, va giù solo l'accesso tramite AI. Il servizio principale rimane intatto.
Iterazione indipendente: l'ecosistema MCP cambia ogni settimana e problemi di compatibilità con i client richiedono hotfix in qualsiasi momento, mentre il servizio core ha un processo di rilascio rigido con regression test. Le distribuzioni separate evitano rallentamenti reciproci.
Libertà di runtime: un servizio standalone può scegliere il runtime che preferisce. Il server MCP di Logto gira su Cloudflare Workers: stateless, scala per richiesta e quasi senza operazioni manuali. L'opzione embedded non offre questa possibilità.
Il server MCP viene distribuito su un proprio dominio, mcp.logto.io, senza coupling privato con il servizio principale. Se volessimo open-sourcarlo in futuro, nulla lo impedisce.
Anche l'opzione embedded non risolve il problema dei token: l'endpoint MCP e la business API condividerebbero lo stesso identificatore di risorsa, quindi il token ottenuto dal client MCP avrebbe pieni permessi API. La domanda "con quali permessi si esegue questa chiamata" passerebbe dallo scambio di token tra servizi al passaggio di permessi in memoria, ma il problema rimarrebbe. La distribuzione standalone ci costringe a progettare esplicitamente il confine dei permessi, che è il tema del resto dell'articolo.
Il server MCP deve avere il suo identificatore di risorsa#
La discussione in uscita parte da una domanda ancora più basilare: qual è l'audience del token ottenuto dal client MCP?
Riutilizzare l'identificatore di risorsa della business API è la soluzione più diretta: la Management API di Logto è già una risorsa protetta standard di OAuth, quindi il client MCP potrebbe richiedere direttamente il suo token e il server MCP lo inoltrerebbe così com'è. Molte prime implementazioni di server MCP hanno fatto proprio questo.
Il costo: se l'audience del token MCP è la business API, il confine dei permessi non esiste più.
I tuoi strumenti MCP diventano solo decorativi: il token stesso può chiamare l'intera API senza passare dai tuoi strumenti
Il rischio di un leak del token passa da "alcune operazioni controllate esposte dal server MCP" a "tutta la Management API"
Audit indipendenti, rate limiting e definizione specifica dei permessi per gli scenari MCP non avrebbero una base su cui poggiare
La nostra prima decisione è stata quindi: il server MCP è una risorsa protetta separata, con il suo identificatore (https://mcp.logto.io) e il suo scope.
Questa decisione rende il confine dei permessi ben definito, e rende anche concreta la domanda in uscita: il token può accedere solo al server MCP, quindi cosa usa il server MCP per chiamare la business API?
Logto Console è una SPA. Il modo in cui chiama la Management API è semplice: l'utente fa il sign-in tramite OAuth nel browser, ottiene un token con la Management API come audience e il frontend la chiama direttamente con quel token.
Quindi il server MCP può funzionare come una specie di Console? Basta far richiedere al client MCP il token della business API al sign-in, e il server MCP lo inoltra così com'è:
Il vantaggio di questa soluzione è la sua estrema semplicità: il server MCP si limita ad inoltrare i token. Ma ci sono problemi evidenti:
Primo, va direttamente contro la decisione sul confine dei permessi sopra descritta. Il passaggio di token richiede che il client MCP possieda il token della business API, che è proprio ciò che volevamo evitare.
Secondo, il server MCP non è più una vera risorsa protetta. L'audience dei token che riceve non è sé stesso, quindi la validazione dell'audience è inutile e si riduce a "verifica della firma e dell'issuer". Questo non rispecchia come la specifica MCP definisce l'autorizzazione (il server MCP dovrebbe agire come resource server e dichiararsi tramite RFC 9728 Protected Resource Metadata). È come camuffare un servizio backend da SPA nel browser.
Terzo, i client MCP non collaboreranno. Un client MCP conforme alle specifiche richiede i token seguendo la Protected Resource Metadata e l'audience sarà il server MCP. Non esiste uno standard che consenta di fargli richiedere il token della business API, quindi questa strada non funziona dal lato client.
Se il token dell'utente non va bene, che dire dell'identità del server MCP?
Dare al server MCP un'applicazione M2M (machine-to-machine), ottenere un token tramite client credentials e chiamare la business API con quello. Questa è anche la prassi tra servizi interni.
L'autenticazione in entrata ora funziona: il token del client MCP ha come audience il server MCP, il server lo valida normalmente, poi svolge le operazioni con il proprio token M2M.
Il problema fatale è che i permessi del token M2M non hanno nulla a che vedere con i permessi dell'utente:
Permessi eccessivi: i permessi del token M2M rappresentano "cosa può fare il server MCP", non "cosa può fare quell'utente". Un utente con ruolo di sola lettura può eliminare applicazioni tramite MCP, poiché il token M2M ha quel permesso
Perdita d'identità: la API downstream vede sempre come chiamante l'applicazione M2M, e i log di audit non sono riconducibili a una persona specifica
Confused deputy: il server MCP diventa un proxy ad alto privilegio, e chiunque riesca a convincerlo a effettuare una chiamata prende in prestito i suoi permessi
M2M si adatta a scenari senza contesto utente, come job schedulati e sincronizzazioni tra sistemi. Un server MCP è diverso: ogni chiamata è iniziata da un utente specifico, quindi deve essere eseguita con la sua identità e i suoi permessi.
Dai due approcci otteniamo i requisiti per il design giusto:
Il token posseduto dal client MCP può accedere solo al server MCP (lezione dall'approccio 1)
Nel chiamare le API downstream, il server MCP deve agire come l'utente, con i suoi permessi (lezione dall'approccio 2)
Questo porta a un meccanismo standard: token exchange (RFC 8693). Il server MCP prende una credenziale che rappresenta l'utente e la scambia presso l'auth server per un token downstream. L'identità e i permessi dell'utente vengono preservati nello scambio.
In Logto, questa "credenziale che rappresenta l'utente" deriva dalla feature di impersonificazione utente: il subject token. È una credenziale a breve scadenza che il server richiede per un utente specifico, significando "il prossimo token exchange verrà eseguito come questo utente". L'utente non deve configurare nulla, il flusso è completamente automatizzato. Il subject token è a breve scadenza, monouso e scade appena utilizzato.
L'architettura prevede quattro ruoli. Il server MCP è distribuito indipendentemente su mcp.logto.io:
Il flusso completo dei token dietro una tool call:
Step by step:
① Validazione in entrata. Il client MCP chiama uno strumento con lo user token. L'audience del token è l'identificatore di risorsa del server MCP, e lo scope è mcp:all. Il server MCP verifica la firma, l'issuer, l'audience, lo scope e ricava l'identità utente. La parte in entrata si ferma qui. Questo token non va mai in downstream.
② Identità di servizio. Il server MCP utilizza le proprie credenziali M2M per ottenere un access token con uno scope dedicato, access:mcp:api. Questo scope serve solo per la chiamata al prossimo endpoint.
③ Richiesta del subject token, il passaggio chiave dell'intera catena. Il server MCP chiama POST /api/mcp/subject-tokens, un endpoint che Cloud apre specificamente per MCP, presentando due credenziali contemporaneamente:
Header Authorization: il token M2M, che prova "sono il server MCP ufficiale"
Header x-mcp-user-token: il token utente, che prova "quest'utente mi ha autorizzato, e l'autorizzazione è ancora valida"
Cloud verifica completamente il token utente: firma, issuer, scadenza, l'audience deve essere l'identificatore di risorsa del server MCP e lo scope deve includere mcp:all. Dopo la verifica, il userId deriva direttamente dal claim sub del token. L'endpoint non accetta parametri per specificare un utente.
Questo design impedisce l'abuso delle credenziali M2M. Se l'endpoint accettasse un userId arbitrario, chiunque avesse la credenziale M2M potrebbe impersonare qualunque utente. Con questo design, il server MCP può scambiare credenziali solo per utenti che hanno dato una valida autorizzazione.
Il subject token emesso è a breve durata e monouso. L'implementazione non lo cache mai e ne richiede uno nuovo ad ogni uso.
④ Scambio per token operativi. Con il subject token, esegui uno standard token exchange per ottenere due tipi di token all'occorrenza:
Cloud API token: per operazioni a livello utente, come elencare o creare tenant
Org token: limitato al tenant di destinazione (ogni tenant in Logto Cloud corrisponde a un'organizzazione). Il token viene emesso come l'utente, e i permessi corrispondono esattamente al ruolo dell'utente su quel tenant
⑤ Chiamata in uscita. Chiama la business API con il token scambiato e restituisci il risultato al client MCP.
Il contesto multi-tenant si risolve anch'esso nella fase di scambio: il tenant compare al livello di exchange, non a quello di connessione. list_tenants elenca le opzioni col Cloud API token, l'utente ne sceglie uno nella conversazione e il server MCP scambia un org token per il tenant selezionato. Un solo endpoint serve tutti i tenant, senza bisogno di deploy separati, e un tenant creato durante la conversazione è subito disponibile.
Controlliamo rispetto ai punti deboli degli approcci precedenti, e tutti vengono risolti:
Il blast radius dello user token è controllato (lezione dall'approccio 1): la sua audience è solo il server MCP. Anche se venisse trafugato, può solo chiamare quegli strumenti controllati
Il token M2M non può essere abusato (lezione dall'approccio 2): non chiama più direttamente la business API e serve solo per provare l'identità del servizio. L'emissione del subject token richiede anche un token utente valido, quindi il server MCP può scambiare credenziali solo per utenti autorizzati. Il problema del confused deputy sparisce
Nessuna credenziale utente da salvare: i subject token vengono richiesti al volo e scartati dopo l'uso. Il server MCP conserva solo le proprie credenziali M2M
La revoca è collegata: se l'utente revoca l'autorizzazione al MCP, lo user token diventa invalido, il controllo su x-mcp-user-token fallisce e la catena in uscita si blocca subito
I permessi sono allineati a quelli dell'utente: i token scambiati sono emessi come l'utente. Un utente in sola lettura resta tale tramite MCP, e un tentativo di escalation fallisce a livello di auth server. I log di audit riportano anche l'identità reale dell'utente
Riguardando, la credenziale M2M svolge il ruolo giusto nel design finale: prova "chi sono io", mentre la capacità di "agire come l'utente" deve essere scambiata al volo con una valida autorizzazione utente.
Guardando l'intera catena, la strategia dei token per un server MCP remoto si riduce a pochi punti essenziali:
In entrata e in uscita sono due gambe di autenticazione distinte, e l'audience del token MCP deve essere il server MCP stesso
L'uscita si risolve tramite token exchange: se il tuo auth server supporta lo scambio diretto del token in entrata, il token exchange standard basta; se no (ad esempio, lo scambio in Logto richiede un subject token), usa una feature simile all'impersonificazione per avviare lo scambio lato server
La multi-tenancy non cambia il meccanismo. Il contesto organizzativo è solo un parametro durante lo scambio, e i prodotti single tenant semplicemente lo ignorano
L'IA agisce per conto dell'utente, quindi i permessi downstream dei token devono essere limitati a quell'utente
Un test semplice: supponi che il token in possesso del client MCP venga trafugato. Tutto ciò che un attaccante dovrebbe poter fare è chiamare quegli strumenti controllati sul server MCP, sempre nei limiti dei permessi dell'utente. Se può accedere direttamente all'intera API, il confine dei permessi è rotto.
L'ecosistema MCP si sta ancora evolvendo rapidamente. L'autenticazione in entrata è ben coperta dalla specifica, mentre "come il server MCP chiama le downstream" è ancora responsabilità dei singoli team. Speriamo che la nostra esperienza possa essere un riferimento utile.
Se stai costruendo un server MCP per il tuo prodotto, esplora la soluzione Logto per gli scenari AI: Auth per app, agent e server MCP AI. Per vedere questa catena di token in azione, collegati al server MCP Logto e provala.