Exemplos_APIMCP
by cmacetko
README.md
# Exemplos_APIMCP — um MCP que se conecta a uma API com OAuth
Exemplo completo, e que roda sozinho, de um **servidor MCP remoto** que acessa
uma **API protegida por OAuth** em nome de quem está conversando com o Claude.
Artigo com a explicação passo a passo:
**[Criando um MCP que se conecta a uma API com OAuth](https://blog.palomamacetko.com.br/criando-um-mcp-que-se-conecta-a-uma-api-com-oauth/)**
O problema que ele resolve: o Claude exige falar com um **Authorization Server
OAuth completo** (metadados publicados, registro dinâmico de cliente, PKCE).
Praticamente nenhuma API de mercado é isso — elas têm um app **fixo**, com
`client_id` e `client_secret` cadastrados à mão e `redirect_uri` validado por
igualdade exata. Este repositório mostra a **ponte** entre os dois, sem pedir
nenhuma alteração na API.
## O que tem aqui
| Arquivo | Papel |
|---|---|
| `api-externa.js` | A API de demonstração, com OAuth próprio. Existe para o exemplo rodar sem você contratar nada. |
| `ponte-oauth.js` | **O coração.** Finge ser o Authorization Server que o Claude quer e conversa com o OAuth que a API tem. |
| `api-externa-oauth.js` | As três chamadas do OAuth da API: autorizar, trocar código, renovar. |
| `armazenamento.js` | As três tabelas da ponte (clientes, sessões em voo, tokens). Em memória — troque por banco em produção. |
| `chamar-api.js` | Chama a API com o token certo e **renova sozinho** quando toma 401. |
| `ferramentas.js` | As ferramentas que o Claude enxerga (`listar_notas`, `criar_nota`). |
| `servidor.js` | Junta tudo: MCP + Authorization Server + cliente OAuth. |
| `publico/index.html` | Página que percorre o fluxo inteiro no navegador, **sem o Claude**. |
## Rodando
Precisa de Node 20 ou mais novo.
```bash
npm install
npm run api # a API externa, na porta 3012
npm start # o servidor MCP, na porta 3011 (outro terminal)
```
Abra **http://localhost:3011/** e siga os seis passos da página. Ela faz, um a
um, exatamente o que o Claude faz ao adicionar um conector — e mostra a resposta
de cada requisição.
Na tela de login da API, use `paloma` / `123`.
## Ver o refresh acontecer (30 segundos de espera)
O `access_token` da API de demonstração vale **60 segundos**, de propósito. No
passo 6 da página, clique em *Esperar 61s e listar*: a chamada funciona igual, e
no terminal do servidor aparece a linha que conta o que houve:
```
[ferramenta] listar_notas
[api] --> GET /notas
[api] 401 — renovando o token da API externa e repetindo
[api] <-- GET /notas HTTP 200
```
O usuário não viu nada. É esse o objetivo.
## Ver a armadilha do `redirect_uri` acontecer
A validação por **igualdade exata** é o erro que mais custa tempo. Reproduza em
alguns segundos: suba o servidor MCP trocando `localhost` por `127.0.0.1` —
mesmo endereço, string diferente.
```bash
URL_PUBLICA="http://127.0.0.1:3011" npm start
```
Agora comece o fluxo pela página. A API rejeita **antes da tela de login**:
```
HTTP 400
redirect_uri nao confere.
Recebido: http://127.0.0.1:3011/callback
Cadastrado: http://localhost:3011/callback
```
## As armadilhas, em resumo
- **`redirect_uri` é comparado como texto.** `127.0.0.1` ≠ `localhost`, e uma
barra a mais no fim também reprova. Derive-o sempre de uma variável só
(`URL_PUBLICA`), nunca escreva o endereço em dois lugares.
- **O `state` que vai à API não é o do Claude.** São dois: o do Claude fica
guardado na sessão, e o que viaja é o nosso id de sessão — é ele que faz o
`/callback` reconhecer de qual autorização é a volta.
- **O código de autorização que volta ao Claude é NOSSO**, não o da API. Repassar
o código da API seria entregar credencial alheia a um terceiro.
- **O tipo do erro decide o código HTTP.** `InvalidTokenError` vira 401 com
`WWW-Authenticate` — o sinal que faz o Claude oferecer "reconectar". Um `Error`
comum viraria 500, e o usuário ficaria sem saída. Vale igual para
`InvalidGrantError` (400) no `/token`.
- **O `refresh_token` rotaciona.** Se você não gravar o novo depois de renovar, o
refresh seguinte usa um token morto e o usuário tem de logar de novo.
- **`structuredContent` tem de ser um objeto**, nunca um array cru.
- **`readOnlyHint: false`** é o que faz o Claude pedir confirmação antes de uma
escrita. Mentir aí tira do usuário a chance de dizer não.
- **`trust proxy`** é obrigatório atrás de nginx/IIS/túnel: sem ele o limitador de
taxa do SDK reclama do `X-Forwarded-For` e derruba o `/register`.
## Conectando de verdade no Claude
O Claude precisa de **HTTPS público**. Suba um túnel para a porta 3011, aponte
`URL_PUBLICA` para a URL do túnel, cadastre `<URL-do-túnel>/callback` na API
(letra por letra) e adicione `<URL-do-túnel>/mcp` como conector personalizado.
## Em produção, mude isto
- `armazenamento.js` em banco de verdade — em memória, todo mundo é desconectado
a cada reinício.
- `CLIENT_SECRET` por variável de ambiente, nunca no código.
- HTTPS de verdade, e `trust proxy` no número de proxies à frente.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues