1. MCP Jam como um laboratório para autorização
MCP Jam não é “mais uma ferramenta estranha”, e sim o seu bancado de laboratório que sabe desempenhar o papel de cliente MCP. Na prática, é um emulador do comportamento do ChatGPT ao trabalhar com um servidor MCP: ele sabe ler .well-known/oauth-protected-resource, iniciar o fluxo de OAuth, anexar tokens às requisições e mostrar exatamente o que deu errado.
Um ponto prático muito importante: se você conseguiu um fluxo Default OAuth bem-sucedido no MCP Jam, você está cerca de 80% pronto para integrar com um ChatGPT App real. Tudo o que o ChatGPT faz ao vincular a conta (linking), o Jam já sabe fazer — só que com logs e botões mais transparentes.
Na aula anterior, configuramos a autorização básica para o nosso servidor MCP de estudo, o GiftGenius: escolhemos a estratégia de validação do token (JWT ou introspection), implementamos .well-known/oauth-protected-resource e um middleware que protege as ferramentas. Agora vamos ver como tudo isso se comporta no MCP Jam em diferentes modos de autorização.
Nosso objetivo nesta aula é aprender a:
- alternar conscientemente os modos de autorização no Jam (None, Bearer, OAuth with credentials, Default OAuth);
- entender o que exatamente o Jam envia ao servidor MCP em cada modo;
- diagnosticar qual parte do sistema quebrou: MCP Server, Auth Server ou metadados;
- verificar que ferramentas protegidas funcionam apenas com token, enquanto as abertas funcionam também sem ele.
2. Nosso servidor MCP de estudo: o que vamos testar
Para não falar de forma abstrata, vamos recapitular rapidamente o contexto. Vamos continuar com nosso aplicativo de estudo GiftGenius — um ChatGPT App que ajuda a escolher presentes e mostra ao usuário seus pedidos e wishlists.
Do lado do MCP Server, nós já temos:
- uma ferramenta aberta, por exemplo search_gifts — pode ser chamada anonimamente;
- uma ferramenta protegida, por exemplo list_user_orders — deve funcionar apenas para o usuário autenticado e exigir o escopo mcp:tools.
O servidor consegue:
- publicar .well-known/oauth-protected-resource;
- validar o token (JWT ou via introspection — você escolheu uma abordagem na aula anterior);
- extrair do token sub (user id), scope, aud e repassá-los aos handlers das ferramentas.
Um middleware típico de verificação de token em Node.js/TypeScript pode ser assim:
// middleware/auth.ts
export function requireScope(requiredScope: string) {
return async (req: any, res: any, next: () => void) => {
const header = req.headers["authorization"];
if (!header?.startsWith("Bearer ")) {
res
.status(401)
.set(
"WWW-Authenticate",
`Bearer realm="mcp", resource_metadata="${process.env.BASE_URL}/.well-known/oauth-protected-resource", scope="${requiredScope}"`
)
.json({ error: "unauthorized" });
return;
}
// aqui você já valida o token (assinatura, exp, aud, scope...)
// e armazena o resultado em req.user
next();
};
}
Esse middleware será usado antes das ferramentas protegidas do MCP. Se não houver token, retornamos 401 e um WWW-Authenticate correto, com resource_metadata, como exige a especificação MCP Authorization. A análise detalhada da validação do token e da implementação das funções auxiliares você já fez na aula anterior; aqui vamos assumir isso como dado.
3. Modos de autorização no MCP Jam: visão geral
O MCP Jam tem vários modos de autorização para conectar-se ao servidor MCP. Eles correspondem a padrões típicos de OAuth: desde a completa ausência de token até Authorization Code + PKCE completo.
Resumindo:
- None (No Auth) — o Jam não adiciona cabeçalho Authorization algum. É acesso anônimo. Serve para servidores MCP abertos e para verificar que recursos fechados recusam corretamente com 401 e WWW-Authenticate.
- Bearer Token — o Jam adiciona Authorization: Bearer <token>, que você insere manualmente na interface. Útil para testes rápidos: o token já foi obtido em outro lugar (curl, UI do Keycloak), e você quer verificar o comportamento do recurso MCP.
- OAuth with credentials (Client Credentials) — o Jam obtém o token por client_credentials no Auth Server, usando o Client ID e o Secret informados. É o modo de “cliente confidencial”, mais parecido com autorização servidor-servidor, sem participação do usuário.
- Default OAuth (Authorization Code + PKCE) — é o modo principal para clientes do tipo ChatGPT (public client, sem secret). O Jam lê resource_metadata, encontra o Auth Server, abre o navegador com /authorize, executa o fluxo PKCE e obtém um token de usuário.
Para facilitar, vamos resumir em uma tabela.
| Modo no Jam | O que o Jam envia | Quem obtém o token | Cenário típico |
|---|---|---|---|
| None | Sem Authorization | Ninguém | Ferramentas anônimas, verificação de 401 |
| Bearer Token | Bearer <manual> | Você (curl, UI do IdP) | Testar a lógica do Resource Server |
| OAuth with cred. | Bearer <token do cliente> | Jam via client_credentials | Ferramentas de serviço/admin |
| Default OAuth | Bearer <token de usuário> | Jam via Authorization Code+PKCE | Login do usuário como no ChatGPT |
Agora vamos passar por cada modo e ver como exercitar nosso servidor MCP GiftGenius por eles.
4. Modo None: verificar que o servidor recusa corretamente
Vamos começar pelo modo mais simples: nenhuma autorização.
No MCP Jam, você seleciona seu servidor (por exemplo, http://localhost:4000/mcp) e, nas configurações da conexão, define o modo de autorização None.
O que acontece:
- O Jam estabelece a conexão MCP;
- ao chamar uma ferramenta, ele não adiciona o cabeçalho Authorization;
- você pode chamar quaisquer ferramentas abertas (por exemplo, search_gifts);
- ao chamar uma ferramenta protegida (por exemplo, list_user_orders), seu servidor deve responder com 401 Unauthorized.
É importante que, nesse 401, o servidor inclua um WWW-Authenticate correto. Um exemplo de resposta com campos adicionais realm e scope, próximo ao recomendado pela OpenAI e pela especificação MCP Authorization:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
resource_metadata="https://giftgenius.example.com/.well-known/oauth-protected-resource",
scope="mcp:tools"
Content-Type: application/json
{"error": "unauthorized"}
Ao ver essa resposta, o Jam entende: o recurso é protegido, aqui está de onde obter os metadados (resource_metadata) e quais escopos são esperados. No modo None ele apenas mostrará o erro, mas no modo Default OAuth ele seguirá automaticamente o resource_metadata indicado e iniciará o fluxo de OAuth.
Do ponto de vista da depuração, no modo None você verifica:
- que as ferramentas abertas funcionam sem token;
- que as ferramentas protegidas nunca são executadas anonimamente;
- que o cabeçalho WWW-Authenticate está conforme a especificação (inclui Bearer e resource_metadata).
Parece uma verificação trivial, mas muitos problemas começam porque o 401 é retornado sem WWW-Authenticate ou com parâmetro incorreto nele (por exemplo, o obsoleto resource_metadata_uri em vez do atual resource_metadata).
5. Modo Bearer Token: teste rápido da lógica do Resource Server
O próximo passo é o modo em que você já possui um token válido (obtido fora do Jam) e quer testar especificamente a lógica do Resource Server: se ele aceita/recusa corretamente esse token, se trabalha corretamente com scope e audience e se liga o sub ao usuário do seu serviço.
No MCP Jam, mude para o modo Bearer Token e cole no campo de token algo como:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Agora o Jam vai adicionar a cada requisição MCP o cabeçalho:
Authorization: Bearer eyJhbGciOi...
Seu servidor MCP recebe a requisição, passa pelo middleware requireScope("mcp:tools"), decodifica o JWT e verifica as claims. Um código típico de verificação pode ser, de forma simplificada:
// auth/verifyToken.ts
import jwt from "jsonwebtoken";
export function verifyToken(header: string) {
const token = header.replace("Bearer ", "");
const payload = jwt.verify(token, process.env.JWT_PUBLIC_KEY!);
// aqui você pode verificar aud, scope etc.
return payload as { sub: string; scope?: string };
}
E usá-lo no middleware:
// dentro de requireScope
const payload = verifyToken(header);
if (!payload.scope?.includes(requiredScope)) {
res.status(403).json({ error: "insufficient_scope" });
return;
}
(req as any).user = { id: payload.sub };
next();
No modo Bearer você pode experimentar:
- inserir um token sem o scope necessário e se certificar de que o servidor responde com 403/401;
- inserir um token com aud incorreto e ver que o servidor o rejeita;
- inserir um token expirado para verificar o erro invalid_token.
Este é um modo de “teste de estresse” local da lógica do Resource Server, sem login via UI e sem PKCE. Tudo o que você validar aqui será aplicado da mesma forma aos tokens que o ChatGPT ou o Jam obterão no modo Default OAuth.
6. Modo OAuth with credentials (Client Credentials): token “em nome do aplicativo”
Agora — um modo mais raro, mas útil para entendimento: OAuth with credentials, isto é, o grant client_credentials. No Jam, você informa:
- Client ID
- Client Secret
- os escopos necessários (por exemplo, mcp:tools)
O Jam faz uma requisição ao token_endpoint do seu Auth Server mais ou menos assim:
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=<ID>&
client_secret=<SECRET>&
scope=mcp:tools
O Auth Server emite um token em que sub geralmente significa o próprio cliente (por exemplo, sub = "mcp-jam-test-client"), e não um usuário específico. O Jam passa a usar esse token como um Bearer normal.
Para que isso pode ser útil no mundo MCP:
- ferramentas de serviço/admin não vinculadas a um usuário específico (por exemplo, exportação de logs, health-check, suporte técnico);
- verificar que o MCP Server consegue diferenciar tokens de usuário e tokens “do cliente”, se sua lógica de negócio considerar isso.
No contexto de ChatGPT Apps, esse modo geralmente não é usado, porque o ChatGPT, como public client, não armazena segredos (e um public client, por definição, não deve ter client_secret). Mas no Jam ele ajuda a ver a diferença entre:
- “Eu apenas forneci um token pronto” (modo Bearer);
- “O Jam obteve o token usando as credenciais do cliente” (OAuth with credentials).
No servidor de estudo, você pode, por exemplo, criar uma ferramenta MCP especial admin_list_all_orders, acessível apenas com token cujo grant_type=client_credentials e a função correspondente. Não é obrigatório para a aula de hoje, mas é um experimento útil.
7. Modo Default OAuth: Authorization Code + PKCE completo, como no ChatGPT
Agora — a estrela principal: Default OAuth. Este é o modo mais próximo do que o ChatGPT faz ao vincular a conta do seu App. O cliente lê o resource_metadata, vai ao Auth Server, abre para o usuário a página de login, obtém um authorization code e o troca por um access token usando Authorization Code + PKCE S256.
Vamos detalhar a sequência de passos. Para facilitar, segue um diagrama.
sequenceDiagram
participant Jam as MCP Jam (Client)
participant RS as MCP Server (Resource)
participant PRM as /.well-known/oauth-protected-resource
participant AS as Auth Server (Keycloak/Auth0)
Jam->>RS: Chamar tool protegido (sem token)
RS-->>Jam: 401 + WWW-Authenticate (resource_metadata=PRM)
Jam->>PRM: GET /.well-known/oauth-protected-resource
PRM-->>Jam: JSON com resource, authorization_servers, scopes_supported...
Jam->>AS: GET /authorize?client_id=...&code_challenge=...&scope=...
Note right of AS: O usuário faz login e concede consentimento
AS-->>Jam: redirect com authorization_code
Jam->>AS: POST /token (code + code_verifier)
AS-->>Jam: { access_token, scope, expires_in, ... }
Jam->>RS: Chamada do tool com Authorization: Bearer <access_token>
RS-->>Jam: Resultado do tool com sucesso
O que é importante verificar neste modo:
- Resposta 401/WWW-Authenticate correta do MCP Server. Se o servidor não envia resource_metadata ou envia uma URL inválida, o Jam não conseguirá ler o PRM nem iniciar o fluxo de OAuth.
- Documento válido .well-known/oauth-protected-resource. Ele deve conter resource, authorization_servers, scopes_supported etc. corretos, para que o Jam entenda para onde ir buscar tokens e quais escopos solicitar.
- Configuração correta do Auth Server.
- Authorization Code Flow com PKCE S256 habilitado.
- Client ID corresponde ao esperado no PRM (ou é registrado via DCR — Dynamic Client Registration).
- O Redirect URI no Auth Server coincide exatamente com o que o Jam usa.
- PKCE S256. O Jam forma o code_challenge e espera que o Auth Server suporte o método S256. Se o PKCE estiver desabilitado ou suportar apenas plain, o fluxo vai falhar.
- Scopes e audience. O Auth Server deve emitir um token com o aud necessário e os escopos solicitados (mcp:tools etc.), e o MCP Server deve verificá-los.
Como resultado de um Default OAuth bem-sucedido, você terá:
- no Jam — a conexão com o MCP Server em que a ferramenta protegida list_user_orders retorna dados corretos exatamente para o usuário com o qual você fez login no Auth Server;
- nos logs do Auth Server — autorização e troca de token bem-sucedidas;
- nos logs do MCP Server — validação do token bem-sucedida e extração de sub.
Para depuração, muitas vezes ajuda adicionar um logger simples no handler da ferramenta, para garantir que você realmente está vendo o userId do token:
// dentro do handler da ferramenta MCP list_user_orders
export async function listUserOrders(args: any, context: any) {
const user = context.user as { id: string };
console.log("[MCP] listUserOrders for user", user.id);
// depois você retorna os pedidos desse usuário
}
8. Onde quebra: diagnóstico por modo
Agora vamos discutir como, pelos sintomas no MCP Jam, entender exatamente onde está o problema: no MCP Server, no Auth Server ou nos metadados. Esta seção é um checklist de diagnóstico por modo.
Se no modo None:
Você chama uma ferramenta protegida, e o servidor retorna:
- 200 OK e executa a ação mesmo sem token — significa que você não tem verificação de token antes dessa ferramenta. É necessário adicionar o middleware ou verificação de escopos.
- 401, mas sem WWW-Authenticate ou com resource_metadata incorreto — o Jam não saberá para onde ir buscar metadados e não conseguirá iniciar o Default OAuth. Corrija o cabeçalho conforme o exemplo acima.
Se no modo Bearer Token:
- O Jam recebe 401/403 de forma consistente mesmo com um token que você tem certeza ser válido quando chamado diretamente (via curl ou Postman). Muito provavelmente há algo errado na lógica do Resource Server: verificação de aud/scope incorreta ou a chave pública usada para a assinatura do JWT não é a correta.
- Se o token Bearer funciona no Jam, mas não funciona depois no Default OAuth — então o problema não está no MCP Server, e sim no Auth Server ou no PRM: o token obtido via Default OAuth difere em scope/aud daquele que você testou manualmente.
Se no modo OAuth with credentials:
- Se o Jam não consegue obter o token (erro no passo /token) — procure a causa na configuração do cliente no Auth Server (secret incorreto, client_credentials não permitido ou scope bloqueado).
- Se o token existe, mas o MCP Server o rejeita — talvez seu servidor espere um sub de usuário (email/ID), e no token só há o identificador do cliente. Ou aud/scope não correspondem ao esperado.
Se no modo Default OAuth:
Este é o cenário com mais armadilhas. Problemas frequentes:
- Redirect URI incorretos. O Auth Server reclama de invalid_redirect_uri ou simplesmente não emite o código. Garanta que o URI do Jam esteja registrado no cliente do IdP sem barras extras e sem erros de digitação.
- Ausência de PKCE ou PKCE não suportado. Se o Auth Server exige PKCE e o Jam (ou uma versão antiga dele) não envia code_challenge, ou o contrário — o Jam envia S256 e o IdP não suporta esse método, você verá invalid_request.
- Escopos divergentes. No PRM você declarou mcp:tools, mas o cliente no IdP tem permitido apenas openid, ou o contrário — o Jam pede mais escopos do que o IdP está disposto a conceder.
- Audience (aud) incorreto. O token é emitido com aud diferente do esperado pelo MCP Server (por exemplo, URL de outro recurso). O servidor vai rejeitá-lo corretamente.
É muito importante aprender a olhar os logs de três lugares:
- MCP Jam — erros ao analisar o PRM e nas requisições HTTP ao Auth Server;
- Auth Server — logs de /authorize e /token indicam por que ele recusou;
- MCP Server — motivos de rejeição do token (invalid_token, insufficient_scope, wrong_audience).
9. Como isso se relaciona ao ChatGPT App real
Por que gastamos tanto tempo brincando com o Jam em vez de ir direto ao Developer Mode do ChatGPT? Porque o Jam é justamente um bancado de laboratório: ele coloca em suas mãos o controle dos modos de autorização e exibe toda a “cozinha interna” do fluxo.
Quando você executa o Default OAuth no Jam e o leva ao sucesso, está efetivamente confirmando que:
- .well-known/oauth-protected-resource do MCP Server está correto;
- o Auth Server (Keycloak/Auth0/…) está configurado corretamente;
- funções, escopos, audience e claims correspondem às expectativas;
- o MCP Server consegue validar o token e vinculá-lo ao usuário.
O ChatGPT, conectado ao mesmo MCP Server, fará exatamente o mesmo: lerá o PRM, irá ao Auth Server, obterá um token e começará a chamar as ferramentas com Authorization: Bearer.
A diferença é que, no ChatGPT, você vê apenas o resultado final (“conta vinculada com sucesso” ou “algo deu errado”), enquanto no Jam você vê todo o protocolo e pode entender, passo a passo, onde exatamente “deu errado”.
10. Mini prática: testando em sequência nosso MCP Server GiftGenius
Vamos juntar tudo em um roteiro simples, que você pode repetir no seu projeto.
Primeiro, inicie seu MCP Server (por exemplo, pnpm dev:mcp) e garanta que:
- ele escuta em http://localhost:4000/mcp (ou sua URL);
- o endpoint /.well-known/oauth-protected-resource retorna um JSON correto;
- o Auth Server (Keycloak) está funcionando e possui um public client configurado para Jam/ChatGPT.
Em seguida:
- Modo None.
Conecte o Jam ao MCP Server sem autorização. Verifique que:- search_gifts executa corretamente;
- list_user_orders retorna 401 com um WWW-Authenticate correto.
- Modo Bearer Token.
Obtenha um access token pelo Keycloak (pela UI ou via curl). Informe-o no Jam, chame list_user_orders e confirme que:- com token válido a ferramenta executa e retorna os pedidos do usuário específico;
- com token sem mcp:tools ou com aud diferente — o servidor retorna erro.
- Modo OAuth with credentials.
Se você tiver um cliente confidencial: informe client_id e client_secret no Jam, defina o scope necessário, chame uma ferramenta técnica (por exemplo, admin_list_all_orders) e verifique que ela funciona apenas com esse token de serviço. - Modo Default OAuth.
Ative o Default OAuth e chame list_user_orders. O Jam vai:- receber 401 + WWW-Authenticate,
- ler o PRM,
- abrir o navegador, onde você fará login no Keycloak,
- obter o token via Authorization Code + PKCE,
- chamar a ferramenta MCP com o token, após o que você verá seus pedidos na resposta.
Se os quatro modos funcionarem conforme o esperado — parabéns, você não apenas “configurou algo no Keycloak”, mas realmente entende como verificar e depurar todo o fluxo de autorização.
11. Erros comuns ao usar o MCP Jam e ao testar autorização
Na prática, esses problemas frequentemente aparecem como padrões recorrentes de erros. Abaixo estão alguns cenários típicos de “como não fazer”, para que você os reconheça pelos sintomas.
Erro nº 1: esperar que a ferramenta protegida funcione no modo None.
Às vezes o desenvolvedor liga o Jam no modo None, chama list_user_orders e se surpreende com o 401, e então “por via das dúvidas” remove a verificação de token no servidor. Como resultado, a ferramenta MCP passa a funcionar anonimamente, o que é categoricamente inaceitável para dados pessoais e cenários de comércio. O modo None serve para verificar que o servidor recusa corretamente sem token e retorna WWW-Authenticate com resource_metadata.
Erro nº 2: cabeçalho WWW-Authenticate ausente ou incorreto.
Caso muito comum: o servidor retorna 401 sem WWW-Authenticate ou com o parâmetro obsoleto resource_metadata_uri. O Jam (assim como o ChatGPT), nesse caso, não sabe para onde ir buscar o Protected Resource Metadata e o Default OAuth simplesmente não inicia. O mínimo necessário é WWW-Authenticate: Bearer resource_metadata="https://.../.well-known/oauth-protected-resource". Os campos realm e scope são opcionais; o principal é não esquecer o resource_metadata.
Erro nº 3: testar apenas o modo Bearer e ignorar o Default OAuth.
O desenvolvedor obtém manualmente um token, insere no Jam, vê que tudo funciona e dá a tarefa por encerrada. Mas quando chega a hora de conectar o ChatGPT real, descobre-se que o .well-known está incorreto, o PKCE não é suportado, o Redirect URI não coincide e o linking falha. Testar o modo Bearer é necessário, mas não suficiente. O Default OAuth precisa ser executado obrigatoriamente; caso contrário, você não valida metade das configurações mais importantes do Auth Server e do PRM.
Erro nº 4: tentar usar client_credentials onde é necessário token de usuário.
Às vezes, em desespero, o desenvolvedor ativa no Jam o modo OAuth with credentials e começa a obter tokens por client_credentials, e depois usá-los para ferramentas de usuário, como list_user_orders. O resultado é que sub no token é o client_id, e não um usuário real, e a lógica de negócio passa a se comportar de forma estranha (por exemplo, mostrar dados “gerais” ou falhar ao tentar encontrar um usuário com esse ID). Para cenários do ChatGPT com usuários reais, é necessário Authorization Code + PKCE (Default OAuth), enquanto client_credentials serve apenas para tarefas de serviço.
Erro nº 5: inconsistência de escopos e audience entre PRM, Auth Server e MCP Server.
Em .well-known/oauth-protected-resource você declarou que o recurso é https://giftgenius.example.com e que os escopos suportados são ["mcp:tools"]. No Auth Server, o cliente recebe um token sem aud, e o MCP Server, ao verificar o token, espera estritamente aud = "https://giftgenius.example.com" e a presença de mcp:tools. Como resultado, o token obtido via Default OAuth é rejeitado pelo MCP Server, e você perde metade do dia procurando “magia”. Verifique sempre se PRM, a configuração do cliente no IdP e a verificação no middleware do MCP Server estão alinhados quanto a audience e scope.
Erro nº 6: usar uma versão antiga do MCP Jam.
A especificação MCP Authorization evolui ativamente; novos campos aparecem (resource_metadata, fluxo PKCE aprimorado, utilitários de depuração). Se você estiver com uma versão antiga do Jam, ela pode não entender os campos mais recentes ou trabalhar com nomes de parâmetros obsoletos. Isso leva a bugs surreais: você configura tudo conforme o último RFC, mas o Jam simplesmente não sabe o que fazer. Antes de se desesperar, garanta que o Jam esteja atualizado para a versão mais recente.
GO TO FULL VERSION