CodeGym /Cursos /ChatGPT Apps /Arquitetura de autorização MCP: MCP Client, MCP Server, M...

Arquitetura de autorização MCP: MCP Client, MCP Server, MCP Auth Server

ChatGPT Apps
Nível 10 , Lição 1
Disponível

1. Sobre o que é esta aula e o que ela não cobre

Será uma aula bem interessante; nela nós:

  • montamos mentalmente a imagem do “triângulo de confiança” entre MCP Client, MCP Server e MCP Auth Server — com a pessoa usuária, que fica “acima” desse triângulo como proprietária dos recursos;
  • explicamos o fluxo: quem envia token para quem, onde o usuário faz login e por que o MCP Server nunca vê a senha dele;
  • conectamos isso ao nosso backend Next.js/MCP e à futura configuração de Keycloak/Auth0.

O que não faremos hoje:

  • não marcamos caixas de seleção no Keycloak e não configuramos um IdP específico;
  • não escrevemos uma verificação completa de JWT nem introspecção — esses são temas das próximas aulas (sobre o Auth Server e sobre o MCP Server como recurso protegido).

A meta agora – que você possa pegar um papel, desenhar setas entre o ChatGPT, seu servidor e o Auth0/Keycloak e explicar sem hesitar: onde é o login, onde está o token e onde estão os dados.

2. Triângulo de confiança: MCP Client, MCP Server, MCP Auth Server

Comecemos pelos personagens. O “triângulo de confiança” técnico é formado pelo MCP Client, MCP Server e MCP Auth Server; o usuário (User) é um papel à parte, o proprietário dos recursos, que fica como que acima desse triângulo e concede acesso. Na especificidade do MCP e do Apps SDK, essa arquitetura é formalizada de maneira bem clara.

User (Resource Owner)

É a pessoa do outro lado da tela. Ela:

  • entra no ChatGPT;
  • envia uma solicitação “mostre meus pedidos / minhas listas de presentes”;
  • aceita “vincular a conta” do seu serviço ao ChatGPT.

O principal: é ela quem possui os recursos (histórico de pedidos, perfis, listas de presentes) e é ela quem concede acesso a eles.

MCP Client

Para nós aqui, isso é:

  • o ChatGPT com o Apps SDK;
  • às vezes – o MCP Jam Inspector (durante a depuração).

O MCP Client sabe:

  • ler metadados do seu MCP Server (via .well-known);
  • iniciar o fluxo OAuth no navegador do usuário;
  • armazenar e anexar tokens às chamadas das ferramentas MCP.

É importante lembrar que o MCP Client é um public client. Ele não armazena seu client_secret, portanto fala com o Auth Server como um aplicativo SPA público: Authorization Code + PKCE.

MCP Server (Resource Server)

É o seu backend que implementa MCP:

  • estabelece conexão com o ChatGPT;
  • declara ferramentas (tools), recursos, prompts;
  • em cada chamada de ferramenta, analisa o cabeçalho Authorization: Bearer <token>;
  • verifica o token (assinatura, exp, aud, scope) e, se estiver tudo ok, executa a lógica de negócios.

Ponto fundamental: o MCP Server não cuida do login. Ele não vê senhas, não desenha formulário de login, não envia ao usuário um e-mail “confirme o e-mail”. Ele confia apenas em tokens assinados criptograficamente emitidos pelo Auth Server.

MCP Auth Server (Authorization Server / IdP)

É um serviço separado de autenticação e autorização: Keycloak, Auth0, Ory Hydra+Kratos, Okta, Cognito, Azure AD etc.

Ele é responsável por:

  • UI de login (e-mail/senha, SSO, 2FA);
  • armazenamento de contas de usuários;
  • emissão de tokens (access token, refresh token);
  • publicação de metadados OAuth/OIDC (/authorize, /token, jwks_uri, /registration etc.).

Para MCP, ele deve oferecer suporte a OAuth 2.1 para public clients (PKCE S256, dynamic client registration etc.).

Tabela-resumo de papéis

Quem O que faz O que não faz
User Insere login/senha, dá consentimento para acesso aos dados Não se comunica diretamente com o MCP Server
MCP Client (ChatGPT/Jam) Inicia OAuth, armazena o token, chama MCP tools Não verifica senhas, não valida a assinatura do token
MCP Server Valida tokens, executa a lógica de negócios das tools Não renderiza formulário de login, não armazena senhas
MCP Auth Server Autentica o usuário, emite tokens Não conhece suas ferramentas MCP nem sua lógica de negócios

Se, na sua cabeça, tudo isso se misturava em um “grande servidor que faz tudo” – é hora de separar.

3. Como é o fluxo: de “sem token” até a chamada protegida das ferramentas

Agora vamos olhar o fluxo de mensagens. Na especificação MCP esse processo é chamado de “The Flow”: discovery → redirect → code → token → authorized calls.

Passo 0. Tentativa de chamar uma ferramenta protegida sem token

O usuário escreve: “Mostre minhas ideias salvas de presentes”.

O ChatGPT, como MCP Client, decide: “para isso é preciso chamar a ferramenta getUserGiftLists do nosso MCP Server”. Ele faz a chamada sem token (o usuário ainda não fez login).

Seu MCP Server:

  • vê a ausência ou incorreção do cabeçalho Authorization;
  • responde com 401 Unauthorized e adiciona o cabeçalho WWW-Authenticate: Bearer resource_metadata="https://api.giftgenius.com/.well-known/oauth-protected-resource" com um link para os metadados do recurso protegido (resource metadata, veremos abaixo).

Fica aproximadamente assim (lógica, não HTTP completo):

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.giftgenius.com/.well-known/oauth-protected-resource"

O ChatGPT vê esse cabeçalho e entende: “ok, o recurso é protegido por OAuth, precisamos executar o fluxo OAuth e vincular a conta”.

Discovery: .well-known/oauth-protected-resource

Em seguida, o MCP Client solicita ao seu servidor os metadados:

GET /.well-known/oauth-protected-resource

O servidor responde com um documento JSON contendo o identificador do recurso e a lista de servidores de autorização dos quais obter tokens.

Exemplo mínimo (vamos configurar em detalhes depois; agora importa a ideia):

{
  "resource": "https://api.giftgenius.com",
  "authorization_servers": [
    "https://auth.giftgenius.com"
  ],
  "scopes_supported": ["gifts.read", "gifts.write"]
}

Aqui:

  • resource — o ID canônico do seu recurso; ele deve ser usado depois como audience ou resource na emissão do token;
  • authorization_servers — a lista de Auth Servers nos quais o ChatGPT pode solicitar um token;
  • scopes_supported — quais “permissões” o seu MCP Server entende.

Solicitação de autorização: redirecionamento para o Auth Server

Após receber os metadados, o MCP Client vai ao Auth Server. Ele abre no navegador uma aba:

GET https://auth.giftgenius.com/authorize
    ?response_type=code
    &client_id=chatgpt-giftgenius
    &redirect_uri=... (URL de retorno do MCP Client)
    &code_challenge=...
    &code_challenge_method=S256
    &scope=openid gifts.read
    &resource=https://api.giftgenius.com

O usuário:

  • vê a tela de login conhecida (por exemplo, Keycloak ou Auth0);
  • insere login/senha e passa pela 2FA;
  • confirma que o ChatGPT pode ler suas listas de presentes (escopo gifts.read).

Code → Token: troca do código por token com PKCE

Após um login bem-sucedido, o Auth Server redireciona o usuário de volta ao MCP Client com um code. O MCP Client:

  • faz um POST para /token;
  • envia o code e o code_verifier (que corresponde ao code_challenge do passo anterior).

O Auth Server verifica o PKCE: calcula o hash do code_verifier e compara com o code_challenge original. Se estiver tudo ok e o cliente for realmente o mesmo que iniciou o fluxo, então:

  • emite um access_token de curta duração (geralmente JWT);
  • nele indica:
    • sub — o ID do usuário no Auth Server;
    • aud ou resource — o seu MCP Server;
    • scope — as ações permitidas (gifts.read, openid etc.).

Solicitação autenticada: chamada da ferramenta MCP com token

Agora o MCP Client está pronto para chamar novamente a sua ferramenta, mas com o cabeçalho:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

O MCP Server:

  • verifica a assinatura do token (via JWK do Auth Server) ou via introspection;
  • verifica o tempo de expiração (exp);
  • verifica aud / resource — se o token foi realmente emitido para https://api.giftgenius.com;
  • olha o scope e decide se pode chamar getUserGiftLists.

Depois disso, ele acessa tranquilamente seu banco de dados por algum userId e retorna as listas pessoais de presentes.

Observe que até este momento falamos apenas do fluxo de rede: como o token é obtido e chega ao MCP Server. Em seguida, é importante entender como de sub e outras claims no token se obtém um userId específico no seu BD — é aqui que entra a ponte de identidade (identity bridge).

4. Identity Bridge: como o usuário do ChatGPT vira userId no seu BD

A parte mais interessante da arquitetura é a “ponte de identidade” (identity bridge). A especificação MCP enfatiza diretamente: o MCP Server não conhece os usuários do ChatGPT, ele se baseia nos dados do token do Auth Server.

O esquema é aproximadamente assim:

flowchart TD
  User[User no ChatGPT] -->|Login/SSO| Auth[Auth Server]
  Auth -->|JWT: sub, email, tenant| MCP[MCP Server]
  MCP -->|userId/tenantId| DB[(Seu banco de dados)]

Passo a passo fica assim.

Primeiro, o Auth Server conhece seus próprios usuários: ele tem entidades user, email, id, possivelmente tenant, roles. Ao fazer login com sucesso, ele coloca essas informações no token (nas claims):

{
  "sub": "auth0|abc123",
  "email": "user@example.com",
  "given_name": "Alice",
  "https://giftgenius.com/tenant": "tenant-42",
  "scope": "openid gifts.read",
  "aud": "https://api.giftgenius.com"
}

Em segundo lugar, o MCP Server, ao verificar o token, extrai essas claims e decide quem é essa pessoa no seu mundo. Por exemplo:

  • se o sub já existe na tabela User.authProviderId, pegamos o userId associado;
  • se não existir, criamos um registro local (on‑the‑fly provisioning) e vinculamos.

Um trecho típico de código TypeScript no lado do MCP Server (simplificado, sem verificação de assinatura) pode ser assim:

type TokenClaims = {
  sub: string;
  email?: string;
  scope?: string;
};

async function mapClaimsToUserId(claims: TokenClaims): Promise<string> {
  const user = await db.user.findUnique({ where: { authSub: claims.sub } });
  if (user) return user.id;

  const created = await db.user.create({
    data: { authSub: claims.sub, email: claims.email ?? null }
  });
  return created.id;
}

Em terceiro lugar, já com seu próprio userId, o MCP Server busca tudo o que precisa: listas de presentes, histórico de pedidos, configurações, plano.

Assim, o Auth Server se torna a “ponte” entre o mundo externo (ChatGPT, Google, SSO) e o seu mundo interno (customer_id no banco de dados de pedidos).

5. Por que separar Auth Server e MCP Server

Pode surgir a tentação: “Vamos fazer com que meu MCP Server também mostre o login e emita o token”. Formalmente é possível (você pode embutir um mini‑IdP nele), mas arquiteturalmente é uma má ideia. As razões são bem pragmáticas.

Primeiro, segurança e escalabilidade. O Auth Server é uma máquina pesada: 2FA, social login, políticas de senha, bloqueio de conta, recuperação de acesso, auditoria de logins, possivelmente certificações. Reescrever isso em cada microsserviço (em cada MCP Server) é caminho para o inferno e para o PCI‑DSS. Muito melhor delegar isso ao Keycloak/Auth0 e apenas verificar o token deles.

Segundo, capacidade de trocar clientes. Hoje você só tem o ChatGPT. Amanhã você conecta o Claude Desktop, seu web frontend em Next.js, o app móvel. Todos eles podem usar o mesmo Auth Server e o mesmo esquema OAuth 2.1, e o seu MCP Server apenas continua verificando tokens. Você não precisará reescrever a lógica de negócios para cada novo cliente.

Terceiro, limpeza do código. O MCP Server, no ideal:

  • sabe publicar /.well-known/oauth-protected-resource;
  • sabe verificar o token Bearer e extrair dele userId, scopes, tenant;
  • implementa as ferramentas de negócio (orders, gifts, profiles).

Toda a UI de login — formulários, layout, logins sociais — vive no Auth Server e não polui o backend.

6. Como isso fica no nosso app didático GiftGenius

Voltando ao aplicativo que conduzimos ao longo do curso. Suponha que temos:

  • um ChatGPT App “GiftGenius” com um widget (Apps SDK) que sabe sugerir presentes;
  • um MCP Server em Node/Next.js que expõe ferramentas:
    • searchGifts — anônima, não requer login;
    • getSavedGiftLists — pessoal, requer autenticação;
  • um Auth Server (depois — Keycloak/Auth0), onde cada usuário tem uma conta.

Cenário de usuário anônimo e autenticado

Se o usuário simplesmente diz “escolha um presente para meu irmão, 30 anos, gosta de jogos de tabuleiro”, nosso App pode:

  • chamar a ferramenta anônima searchGifts;
  • exibir recomendações na interface.

Nesse caso:

  • não é necessário token;
  • o MCP Server apenas executa a solicitação (por exemplo, ao seu catálogo ou a uma API de terceiros).

Assim que o usuário diz “salve isto nas minhas listas” ou “mostre minhas ideias salvas”, o modelo decide chamar a ferramenta protegida getSavedGiftLists. O servidor responde com 401 + WWW-Authenticate com resource_metadata. O ChatGPT inicia o assistente “Link GiftGenius account”, guia o usuário pelo login e obtém um token.

Depois, a cada chamada protegida:

  • o MCP Server já vê Authorization: Bearer ...;
  • extrai o userId do token;
  • filtra os dados por esse userId.

É graças a isso que podemos:

  • isolar os dados de usuários diferentes;
  • exibir com segurança histórico de pedidos, lista de favoritos;
  • implementar funções de comércio (mais adiante no curso).

Arquitetura do backend: middleware + handlers de ferramentas

Na prática, em código Node/Next.js, isso frequentemente aparece como uma cadeia: “middleware de autenticação → handler de negócios da ferramenta”. Na aula sobre implementação de handlers de ferramentas, já enfatizamos que é preciso repassar o contexto: user_id, tokens, configurações.

Um fragmento de código pode ser assim:

// auth-context.ts
export type AuthContext = {
  userId: string | null;    // null para chamadas anônimas
  scopes: string[];
};

Middleware pendurado em todos os endpoints MCP:

// mcp-auth-middleware.ts
export async function buildAuthContext(req: Request): Promise<AuthContext> {
  const header = req.headers.authorization || "";
  const token = header.replace(/^Bearer\s+/i, "");

  if (!token) return { userId: null, scopes: [] }; // usuário anônimo

  const claims = await verifyAndDecodeToken(token); // verificação do token
  const userId = await mapClaimsToUserId(claims);
  const scopes = (claims.scope || "").split(" ");
  return { userId, scopes };
}

E o próprio handler da ferramenta recebe esse contexto:

// tools/getSavedGiftLists.ts
export async function getSavedGiftLists(_args: {}, ctx: AuthContext) {
  if (!ctx.userId) throw new Error("User must be authenticated");

  return db.giftList.findMany({
    where: { ownerId: ctx.userId }
  });
}

A ideia é que o handler da ferramenta não sabe nada sobre OAuth nem sobre PKCE. Ele simplesmente trabalha com um userId “óbvio”. Toda a mágica de OAuth fica antes dele: no MCP Client e no Auth middleware.

7. Esquemas visuais: como convivem Client, Server e Auth

Já detalhamos o fluxo no texto da seção 3. Às vezes é mais fácil ver uma vez do que explicar sete, então vamos mostrar as mesmas interações em duas diagramas.

Esqueleto da interação (The Triangle of Trust)

flowchart TD
  U[User] -->|1. Login / Consent| A[MCP Auth Server]
  U -->|2. Conversa| C["MCP Client (ChatGPT)"]
  C -->|3. OAuth Flow| A
  C -->|4. Bearer Token| S[MCP Server]
  S -->|5. Data| C

O esquema se lê assim.

Primeiro, o usuário faz login via o Auth Server, que essencialmente confirma sua identidade e emite um token. O MCP Client gerencia esse processo e depois usa o token para acessar o MCP Server. O MCP Server não vê login/senha; ele vê apenas o token e decide o que é permitido.

Fluxo do pedido à resposta

sequenceDiagram
  participant User
  participant ChatGPT as MCP Client
  participant Auth as Auth Server
  participant MCP as MCP Server

  User->>ChatGPT: "Mostre minhas listas de presentes"
  ChatGPT->>MCP: callTool(getSavedGiftLists) (sem token)
  MCP-->>ChatGPT: 401 + WWW-Authenticate (resource_metadata)
  ChatGPT->>Auth: /authorize + PKCE
  User->>Auth: Insere login/senha, dá consentimento
  Auth-->>ChatGPT: redirect + code
  ChatGPT->>Auth: /token + code_verifier
  Auth-->>ChatGPT: access_token (JWT)
  ChatGPT->>MCP: callTool(getSavedGiftLists) + Authorization: Bearer ...
  MCP-->>ChatGPT: JSON com listas pessoais
  ChatGPT-->>User: Lista renderizada no widget

Este diagrama é aquilo que você deve conseguir “explicar de olhos fechados” ao final do módulo.

8. Um pouco mais fundo: múltiplos recursos, múltiplos clientes, DCR

O bom dessa arquitetura — ela escala.

Primeiro, você pode ter vários MCP Servers (por exemplo, um sobre presentes, outro sobre pedidos) e um único Auth Server emitindo tokens com diferentes aud/resource. Cada servidor de recursos é obrigado a verificar que o token foi realmente destinado a ele, caso contrário teremos o problema clássico do “confused deputy”, quando um token para um serviço é aceito por outro.

Segundo, você pode ter muitos clientes:

  • ChatGPT App;
  • seu próprio frontend;
  • aplicativo móvel;
  • integração de parceiro via MCP Gateway.

Todos eles irão:

  • ler /.well-known/oauth-protected-resource;
  • descobrir onde está o Auth Server;
  • passar pelo fluxo OAuth 2.1;
  • obter tokens e chamar o MCP Server.

Terceiro, Auth Servers modernos cada vez mais suportam Dynamic Client Registration (DCR) — a capacidade de registrar clientes dinamicamente via API. A especificação MCP justamente pressupõe tal possibilidade: o cliente (ChatGPT/Jam) pode se registrar automaticamente no Auth Server via seu registration_endpoint.

Neste módulo, é importante entender que:

  • o MCP Client, o MCP Server e o Auth Server se comunicam por meio de documentos de discovery padronizados e tokens;
  • você não precisa “fixar rigidamente” todos os clientes no código do backend;
  • você pode expandir o ecossistema sem quebrar o modelo de autorização existente.

9. Erros típicos no entendimento da arquitetura de autorização MCP

Erro nº 1: “O MCP Server deve ele mesmo autenticar o usuário”.
Às vezes desenvolvedores tentam embutir o formulário de login diretamente no MCP Server e então enviar login/senha por meio das ferramentas. Isso subverte a própria ideia do OAuth. O MCP Server não deve ver a senha sob nenhuma circunstância. Login e consentimento são responsabilidade do Auth Server. O MCP Server trabalha apenas com tokens e suas claims.

Erro nº 2: Confusão entre MCP Client e MCP Server.
Acontece de tomarem o ChatGPT como “parte do meu backend” e tentarem, por exemplo, armazenar segredos nele ou esperar que ele próprio verifique direitos de acesso. Na verdade, o MCP Client apenas inicia o OAuth e anexa tokens. Validar token e permissões é tarefa do MCP Server, não do ChatGPT.

Erro nº 3: “Chave de API no .env em vez de OAuth”.
Anti‑padrão clássico: criar um grande SERVICE_API_KEY, colocá‑lo no .env do MCP Server e achar que está resolvido. Nesse cenário não há segregação de permissões por usuário, não é possível exibir dados pessoais com segurança ou efetuar compras, tudo é feito “em nome do serviço”, não do usuário. Isso contraria completamente os objetivos de autorização nos ChatGPT Apps.

Erro nº 4: Ignorar audience e resource.
Se o MCP Server aceita qualquer JWT válido com assinatura correta e não verifica aud/resource, então qualquer token emitido para outro serviço pelo mesmo Auth Server pode ser usado para chamar suas ferramentas. É uma violação direta do modelo de segurança do OAuth. O servidor deve verificar que o token foi emitido exatamente para o seu resource.

Erro nº 5: Misturar lógica de auth e lógica de negócios.
Às vezes começam a enfiar nos handlers de ferramentas toda a análise do token, verificação de assinatura, trabalho com JWK etc. No fim, o código fica frágil e difícil de manter. Muito mais correto é separar a camada “verificação do token, mapeamento para userId” (middleware) da camada “lógica da ferramenta”, que recebe um AuthContext já claro.

Erro nº 6: Esperar que o ChatGPT “faça tudo sozinho” sem .well-known.
Sem o endpoint correto /.well-known/oauth-protected-resource, o MCP Client simplesmente não sabe onde está o seu Auth Server nem quais scopes são necessários. Resultado — o chat “não sabe fazer login”, e o desenvolvedor fica olhando logs vazios. O caminho certo: o MCP Server declara claramente seus requisitos de autorização via .well-known, o cliente lê e constrói o fluxo.

Erro nº 7: Usuário “esquecido” na lógica de negócios.
Às vezes, mesmo configurando corretamente o OAuth e o mapeamento do token para userId, os desenvolvedores não usam isso nas consultas ao BD: por exemplo, esquecem de filtrar por ownerId = userId. Assim, qualquer usuário autenticado pode ver dados de outros. Ter um token é apenas o primeiro passo; o segundo é sempre usar corretamente userId e scope no código de negócios.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION