Como um servidor MCP chama a tua API em nome dos utilizadores: uma estratégia de tokens para produção
Explica a estratégia de token de saída para servidores MCP: porque é que o token passthrough e M2M falham, e como a troca de token mantém as permissões alinhadas com os utilizadores.
YijunDeveloper
Pare de perder semanas com autenticação de utilizadores
Lance aplicações seguras mais rapidamente com o Logto. Integre a autenticação de utilizadores em minutos e concentre-se no seu produto principal.
Como um servidor MCP chama a tua API em nome dos utilizadores: uma estratégia de tokens para produção#
No nosso artigo anterior, partilhámos a nossa experiência geral na construção do servidor MCP remoto da Logto. Este artigo detalha o design da arquitetura e o fluxo OAuth.
Com o servidor MCP como fronteira, a autenticação para um servidor MCP remoto tem duas vertentes: entrada e saída.
Entrada: o cliente MCP (VS Code, Cursor, etc.) faz login via OAuth, recebe um token de acesso e usa-o para aceder ao teu servidor MCP
Saída: ao tratar pedidos de ferramentas, o servidor MCP solicita a tua própria API de negócio em nome do utilizador
A vertente de entrada está bem definida na especificação MCP, com muitas discussões e implementações na comunidade. Também escrevemos um guia de implementação previamente. A vertente de saída recebe muito menos atenção: o servidor MCP só possui um token para aceder a si mesmo. Como pode chamar a tua API de negócio em nome do utilizador?
Foi a esta questão que continuámos a responder e ajustar enquanto construíamos o servidor MCP da Logto. O Logto Cloud é um produto SaaS B2B multi-inquilino típico: um utilizador pode pertencer a múltiplos inquilinos, e as permissões são derivadas do papel do utilizador em cada inquilino. A IA não só precisa de agir como o utilizador, como também precisa de aterrar no inquilino certo.
Este artigo segue o nosso processo real de decisão:
Porque é que o servidor MCP deve ser implantado de forma independente, como um recurso protegido à parte
Duas abordagens de saída que recusámos (token passthrough e token M2M) e os respetivos problemas
O design final, troca de token mais subject token, incluindo como os tokens de organização tratam o multi-inquilino
Alguns princípios importantes a manter se decidires construir algo semelhante
O problema central: o duplo papel de um servidor MCP#
Do ponto de vista do OAuth, um servidor MCP remoto desempenha dois papéis em simultâneo:
Para o cliente MCP, é um servidor de recursos: o cliente tem de trazer um token para o aceder
Para a API de negócio, é um cliente: traz um token para aceder a outro serviço
Em outras palavras, o servidor MCP assume um papel diferente de cada lado e cada lado utiliza um token diferente. O token que o cliente MCP obtém via OAuth tem o servidor MCP como audiência, por isso a API de negócio irá rejeitá-lo ao validar a audiência.
Assim, a vertente de saída é essencialmente um problema de delegação: como é que o servidor MCP chama APIs a jusante como utilizador, dentro das permissões do utilizador, sem possuir credenciais do utilizador?
Decisão de arquitetura: o servidor MCP como serviço autónomo#
Integrar o endpoint MCP no serviço da API de negócio, partilhando o mesmo processo e stack de autenticação, parece a opção mais direta. Após ponderação, escolhemos o deployment autónomo:
Isolamento de risco: quando tomámos a decisão, o SDK oficial de MCP não estava pronto para produção, e o próprio protocolo estava a evoluir rapidamente (a mudança de transport de SSE para HTTP streamable é um exemplo). O Logto é um serviço IAM e a disponibilidade do caminho de login é essencial. Com deployment autónomo, se o servidor MCP falhar, só a entrada da IA será afetada. O serviço principal mantém-se.
Iteração independente: o ecossistema MCP muda semanalmente, e problemas de compatibilidade entre clientes podem exigir hotfixes, enquanto o serviço principal tem um processo de lançamento rigoroso com testes de regressão. Deployments separados evitam bloqueios entre si.
Liberdade de runtime: um serviço autónomo pode escolher o runtime que mais lhe convém. O servidor MCP da Logto corre em Cloudflare Workers: sem estado, escala por pedido e praticamente zero manutenção. A opção embutida não dá esta escolha.
O servidor MCP é instalado no seu próprio domínio, mcp.logto.io, sem acoplamento privado ao serviço principal. Se quisermos abrir o código um dia, não há impedimentos.
A opção embutida também não resolve o problema do token: o endpoint MCP e a API de negócio partilhariam o mesmo identificador de recurso, pelo que o token obtido pelo cliente MCP teria permissões totais de API. A questão de "com as permissões de quem corre esta chamada" apenas muda da troca de token entre serviços para passagem de permissões em processo — mas o problema permanece. O deployment autónomo obriga à definição explícita da fronteira de permissões, que é o tema do resto do artigo.
O servidor MCP tem de ter o seu próprio identificador de recurso#
A discussão sobre a vertente de saída começa com uma questão ainda mais elementar: qual deve ser a audiência do token que o cliente MCP recebe?
Reutilizar o identificador de recurso da API de negócio é a opção mais direta: a Management API da Logto já é um recurso estándar protegido por OAuth, logo o cliente MCP poderia pedir o seu token diretamente e o servidor MCP apenas o reencaminhava. Muitos servidores MCP iniciais seguiram esta via.
O custo: se a audiência do token MCP for a API de negócio, deixa de haver fronteira de permissões.
As tuas ferramentas MCP tornam-se decoração: o token por si já pode chamar toda a API sem passar por elas
O raio de ação de um token comprometido aumenta de "operações controladas expostas pelo servidor MCP" para "toda a Management API"
Auditoria, limitação de taxa e controlo de permissões para cenários MCP ficam sem base
Assim, a nossa primeira decisão: o servidor MCP é um recurso protegido à parte, com identificador próprio (https://mcp.logto.io) e scope próprio.
Esta decisão clarifica a fronteira de permissões e torna a questão de saída concreta: o token apenas acede ao MCP — que token o MCP usa para a API?
O Logto Console é uma SPA. O modo como chama a Management API é simples: o utilizador faz login via OAuth no browser, recebe um token cuja audiência é a Management API, e o frontend usa-o diretamente para a API.
E se o servidor MCP funcionasse como mais um Console? O cliente MCP pediria o token da API de negócio ao autenticar, e o MCP apenas o reencaminhava sem alterações:
O apelo desta solução é a simplicidade: o MCP só reencaminha tokens. Mas tem problemas óbvios:
Primeiro, entra em conflito direto com a decisão sobre a fronteira de permissões. O passthrough exige que o cliente MCP tenha o token da API de negócio, precisamente o que rejeitámos.
Segundo, o servidor MCP deixa de ser realmente um recurso protegido. A audiência dos tokens recebidos não é ele próprio, por isso a validação da audiência perde sentido e reduz-se a "verificar assinatura e emissor". Isto não segue a definição da spec MCP (o servidor MCP deve ser servidor de recurso e declarar-se via RFC 9728 Protected Resource Metadata). No fundo, faz de um backend um SPA no browser.
Terceiro, os clientes MCP não vão alinhar. Um cliente MCP conforme à spec obtém tokens conforme o Protected Resource Metadata, e a audiência será sempre o servidor MCP. Não há forma estándar de o fazer pedir tokens da API de negócio, logo esta via falha logo no cliente.
Se o token do utilizador não serve, e as credenciais do próprio MCP?
Criar uma aplicação M2M (máquina-a-máquina) para o servidor MCP, obter um token pelas client credentials e chamar a API de negócio com ele. É a prática habitual para serviços internos.
Agora também a autenticação de entrada funciona: o token do cliente MCP tem audiência = MCP, o MCP valida normalmente e usa o seu token M2M próprio.
O problema fatal: as permissões do token M2M nada têm a ver com as do utilizador:
Excesso de permissões: o token M2M reflete "o que o MCP pode fazer", não "o que este utilizador pode fazer". Um utilizador só-leitura pode apagar aplicações via MCP pois o token M2M permite
Perda de identidade: a API a jusante só vê a aplicação M2M como chamada, sem ligação ao utilizador real
Confused deputy: o MCP torna-se proxy de privilégio alto, e quem o convencer a chamar algo ganha as permissões dele
O modelo M2M serve para cenários sem contexto de utilizador, como tarefas agendadas e sincronização serviço-a-serviço. Um MCP é diferente: cada chamada é iniciada por um utilizador específico, logo deve correr com a sua identidade e permissões.
Das duas abordagens retiram-se os requisitos para o design certo:
O token do cliente MCP só pode aceder ao servidor MCP (lição da abordagem 1)
Ao chamar APIs a jusante, o MCP tem de agir como o utilizador, dentro das suas permissões (lição da 2)
Isto aponta para um mecanismo estándar: troca de token (RFC 8693). O servidor MCP pega numa credencial que representa o utilizador e troca-a no servidor de auth por um token de API a jusante. A identidade e permissões do utilizador mantêm-se.
No Logto, esta "credencial que representa o utilizador" é fruto da impersonação de utilizador: o subject token. É uma credencial de curta duração que o servidor solicita expressamente para um utilizador, significando "a próxima troca de token será como este utilizador". O utilizador não precisa de criar nem configurar nada, tudo é automático. O subject token é de curta duração, uso único e expira após uso.
Há quatro papéis nesta arquitetura. O servidor MCP é autónomo em mcp.logto.io:
O fluxo de tokens completo de uma chamada de ferramenta:
Passo a passo:
① Validação de entrada. O cliente MCP chama uma ferramenta com o token de utilizador. A audiência do token é o identificador próprio do MCP e o scope é mcp:all. O servidor MCP verifica assinatura, emissor, audiência e scope, e obtém a identidade do utilizador. A entrada termina aqui. Este token nunca vai a jusante.
② Identidade do serviço. O MCP usa as suas credenciais M2M para obter um access token dedicado, scope access:mcp:api. Este scope serve apenas para chamar o endpoint do passo seguinte.
③ Pedido do subject token, a etapa chave de toda a cadeia. O MCP chama POST /api/mcp/subject-tokens, endpoint aberto pela Cloud só para MCP, apresentando duas credenciais de uma vez:
Header Authorization: o token M2M, provando "sou o MCP oficial"
Header x-mcp-user-token: o token do utilizador, provando "este utilizador autorizou-me e a autorização está válida"
A Cloud valida totalmente o token do utilizador: assinatura, emissor, expiração, audiência deve ser a do MCP, e o scope incluir mcp:all. Após validação, o userId vem da claim sub. O endpoint não tem parâmetro para user.
Este design impede abusos da credencial M2M. Se o endpoint aceitasse um userId arbitrário, qualquer um com as credenciais M2M podia fazer-se passar por qualquer utilizador. Assim, o servidor MCP só pode trocar credenciais para um utilizador se possuir autorização válida desse mesmo utilizador.
O subject token emitido é de curta duração e uso único. A implementação nunca o armazena e pede sempre um novo para cada uso.
④ Troca pelos tokens de trabalho. Com o subject token, faz uma troca estándar para obter dois tipos de tokens conforme necessário:
Cloud API token: para operações ao nível do utilizador, como listar e criar inquilinos
Org token: com scope para um inquilino específico (cada inquilino na Logto Cloud é uma organização). O token é emitido como o utilizador, com permissões exatas do seu papel nesse inquilino
⑤ Chamada a jusante. A chamada à API de negócio é feita com o token trocado, devolvendo também o resultado ao cliente MCP.
O contexto multi-inquilino é também definido na etapa de troca: o inquilino vive na troca, não na conetividade. list_tenants mostra as opções com Cloud API token, o utilizador escolhe uma, e o servidor MCP troca por um org token do inquilino escolhido. Um endpoint serve todos, não há deployment por inquilino e inquilinos criados no decorrer da conversa ficam logo disponíveis.
Comparemos com as falhas das abordagens anteriores — todas cobertas:
O raio de risco do token do utilizador está contido (lição da abordagem 1): a audiência é só MCP. Mesmo comprometido, só acede a ferramentas controladas
O token M2M não pode ser abusado (lição da abordagem 2): não chama APIs de negócio diretamente e só prova identidade do serviço. Para sair credenciais para o utilizador, é preciso um token válido desse utilizador. O problema do "confused deputy" desaparece
Sem guardar credenciais de utilizador: subject tokens são pedidos no momento e descartados após o uso. O MCP só armazena as credenciais do serviço
Revogação está acoplada: quando o utilizador revoga a autorização ao MCP, o token fica inválido, o check ao x-mcp-user-token falha e a cadeia de saída para
Permissões mantêm-se alinhadas com o utilizador: tokens trocados são emitidos como o utilizador. Um utilizador só-leitura mantém-se assim no MCP, tentativas de escalada de privilégio falham já no auth server. Auditoria mostra sempre o utilizador real
Revendo, a credencial M2M tem o papel certo: prova "quem sou"; atuar como utilizador só pode com autorização válida desse utilizador no momento.
No seu todo, a estratégia de tokens para um MCP remoto resume-se a:
Entrada e saída são duas perninhas independentes de autenticação, e a audiência do token MCP tem de ser o próprio servidor MCP
Na saída, a troca de token resolve: se o server de auth permite trocar já o token de entrada, o estándar chega; se não (como no Logto, que pede subject token como input), usa-se a funcionalidade de impersonação para começar o fluxo do lado servidor
Multi-inquilino não muda o mecanismo. O contexto de organização é só um parâmetro na troca, e produtos single-tenant ignoram-no
A IA age em nome do utilizador, logo permissões do token de saída têm de ser as do utilizador
Um teste simples: se o token do cliente MCP for comprometido, o atacante só deve conseguir usar as ferramentas do MCP, sempre dentro das permissões concedidas do utilizador. Se conseguir ir diretamente à API, a fronteira de permissões está quebrada.
O ecossistema MCP ainda evolui rápido. A autenticação de entrada já tem spec bem clara; "como o MCP chama serviços a jusante" continua a ser responsabilidade de cada equipa. Esperamos que esta partilha ajude.