uazapi-mcp
by ogallotti
README.md
# uazapi-mcp
Servidor MCP remoto para operar **uma instância** [Uazapi](https://docs.uazapi.com/) (WhatsApp) a partir de agentes: Claude, Cursor, Composio, Executor e qualquer cliente MCP. É uma instância por conexão, e cada conexão leva o token da instância no header.
- 10 tools diretas para o dia a dia (status, reconectar por QR code, enviar texto e mídia, ler conversas e mensagens, baixar ou transcrever mídia) e acesso às outras 126 ações da API (grupos, canais, enquetes, botões, reações, etiquetas, CRM, envio em massa) por busca progressiva.
- Spec MCP **2026-07-28** (stateless, sem sessão, `tools/list` cacheável) com fallback automático para clientes 2025.
- Roda em Cloudflare Workers e também em Bun ou Deno. Não guarda nada: o token chega em cada requisição e é repassado só para `*.uazapi.com`.
## Instalar com um agente
Cole este prompt no Claude Code, Codex, Cursor ou outro agente com terminal:
```text
Instale o servidor MCP do Uazapi do repositório https://github.com/ogallotti/uazapi-mcp na minha conta Cloudflare e me deixe pronto para conectar.
1. Clone o repositório e rode `npm install`.
2. Confira o acesso à Cloudflare com `npx wrangler whoami`. Se não houver login, rode `npx wrangler login` e espere eu autorizar no navegador.
3. Publique com `npm run deploy` e anote a URL `https://uazapi-mcp.<conta>.workers.dev` que o wrangler mostrar.
Se eu quiser um domínio próprio, pergunte qual e publique com `npx wrangler deploy --domain <dominio>` (o domínio precisa estar na mesma conta Cloudflare).
4. Rode `MCP_URL=<url>/teste/mcp npm run smoke` e confirme "all checks passed".
5. Pergunte a Server URL da minha instância Uazapi (ex.: https://minhaempresa.uazapi.com). O <subdominio> é a parte antes de .uazapi.com.
Se eu quiser testar com a instância real, nunca peça o token no chat: me peça para rodar no terminal
`read -rs SMOKE_UAZAPI_TOKEN && export SMOKE_UAZAPI_TOKEN && MCP_URL=<url>/<subdominio>/mcp npm run smoke` (só faz leituras).
6. Me entregue a configuração final: URL `<url>/<subdominio>/mcp` e header `Authorization: Bearer <token da instância>`, com o passo a passo para o meu cliente MCP (seção "Conectar" do README).
Se eu não usar Cloudflare, siga a seção "Outros runtimes" do README.
```
## Instalar à mão
Requisitos: Node 20+ e uma conta Cloudflare. O plano grátis serve para começar; se aparecer erro de limite de CPU, o Workers Paid (US$ 5/mês) resolve.
```bash
git clone https://github.com/ogallotti/uazapi-mcp && cd uazapi-mcp
npm install
npx wrangler login # se ainda não estiver logado
npm run deploy # publica em https://uazapi-mcp.<conta>.workers.dev
# domínio próprio (opcional):
npx wrangler deploy --domain mcp.seudominio.com
```
Confira com `MCP_URL=https://uazapi-mcp.<conta>.workers.dev/teste/mcp npm run smoke`.
### Outros runtimes
O servidor é um handler `fetch` padrão, testado também em Bun e Deno. Coloque HTTPS na frente (proxy reverso, Caddy, túnel) antes de expor.
```bash
PORT=8787 bun src/index.ts
deno serve --allow-net --allow-env --allow-read --port 8787 src/index.ts
```
## Conectar
Endpoint: `https://<seu-servidor>/<subdominio>/mcp`, onde `<subdominio>` é a parte antes de `.uazapi.com` na Server URL da instância.
Use o **token da instância** (não o admintoken) em um destes headers:
| Header | Valor |
|---|---|
| `Authorization` | `Bearer <token>` |
| `token` | `<token>` (o mesmo header da API Uazapi) |
| `x-uazapi-token` | `<token>` |
Também dá para usar `POST /mcp` com o header `x-uazapi-server: <subdominio>`. Só hosts `*.uazapi.com` são aceitos; outros entram pela variável `EXTRA_ALLOWED_HOSTS` (lista de sufixos separados por vírgula).
### Claude Code
```bash
read -rs UAZAPI_TOKEN # cole o token; ele não aparece na tela nem fica no histórico
claude mcp add --transport http uazapi https://<seu-servidor>/<subdominio>/mcp --header "Authorization: Bearer $UAZAPI_TOKEN"
unset UAZAPI_TOKEN
```
### Cursor, Claude Desktop e clientes com JSON
```json
{
"mcpServers": {
"uazapi": {
"url": "https://<seu-servidor>/<subdominio>/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
```
### Composio (Custom MCP)
No painel, em Add Custom MCP:
- **MCP server URL:** `https://<seu-servidor>/<subdominio>/mcp`. Acrescente `?tools=all` para expor as 136 tools granulares, porque o Composio já faz busca de tools.
- **Authentication:** API key. Em Advanced settings, **Header name** `Authorization` e **Header prefix** `Bearer`.
- Na tela de conexão da conta, cole **só o token da instância**, sem `Bearer`.
Pela API, o header é um modelo com `{{generic_api_key}}`:
```bash
curl -X POST https://backend.composio.dev/api/v3.1/custom/toolkits/upsert \
-H "x-api-key: $COMPOSIO_API_KEY" -H "Content-Type: application/json" \
-d '{
"slug": "UAZAPI",
"toolkit_config": {
"name": "Uazapi WhatsApp",
"app_url": "https://<seu-servidor>/<subdominio>/mcp?tools=all",
"auth_schemes": [{ "mode": "API_KEY", "headers": { "Authorization": "Bearer {{generic_api_key}}" } }]
}
}'
```
Cada instância vira uma conta conectada do toolkit, com o próprio token.
### Executor
Custom app → MCP → URL `https://<seu-servidor>/<subdominio>/mcp` e header `Authorization: Bearer <token>`. O Executor também faz busca e code mode, e tanto o perfil padrão quanto `?tools=all` funcionam.
### Conferir se funcionou
Peça ao agente o status do WhatsApp (tool `whatsapp_status`). Se ele responder `connected` com o nome do perfil, está tudo certo.
| Resposta | Causa |
|---|---|
| `401 Invalid token` | O token não é da instância daquele subdomínio, ou é o admintoken. |
| `400 Servidor Uazapi inválido no caminho` | O subdomínio da URL está errado. |
| `Conexão sem credenciais Uazapi` | O cliente não enviou o header. |
## Tools
Perfil padrão (progressive disclosure, 13 tools):
| Tool | O que faz |
|---|---|
| `whatsapp_status` | Conexão, perfil e motivo da última queda; QR code se estiver conectando |
| `whatsapp_connect` | Reconecta: QR code (imagem) ou código de pareamento |
| `send_text`, `send_media` | Envio de texto e de mídia por URL |
| `find_chats`, `get_messages`, `get_chat_details` | Leitura compacta de conversas e mensagens |
| `check_whatsapp_numbers` | Confere se números têm WhatsApp |
| `download_media` | Link da mídia e transcrição de áudio |
| `mark_chat_read` | Marca conversa como lida |
| `search_operations` → `describe_operation` → `call_operation` | As outras 126 ações: grupos, canais, enquetes, botões, reações, editar ou apagar, etiquetas, CRM, respostas rápidas, catálogo, envio em massa |
`?tools=all` expõe as 10 tools curadas e mais uma tool por operação do catálogo (136 no total), com as anotações `readOnlyHint` e `destructiveHint`.
Ficam fora do escopo, por desenho: rotas de `admintoken`, proxy, webhooks e SSE, Chatwoot, apagar, desconectar ou renomear a instância, privacidade e delay de fila.
## Segurança
- O servidor não tem banco nem segredo próprio. Quem tem a URL e um token válido opera aquela instância, exatamente como na API do Uazapi.
- Só chama hosts `*.uazapi.com` (ou os de `EXTRA_ALLOWED_HOSTS`), recusa redirects e mascara `token`, `admintoken` e `openai_apikey` nas respostas.
- Envios nunca são repetidos automaticamente. Leituras são repetidas até 3 vezes em 429 ou 5xx, respeitando `Retry-After`.
## Desenvolvimento
```bash
npm install
npm run catalog # regenera src/catalog.json a partir do OpenAPI publicado
npm run typecheck
npm run dev # wrangler dev em http://localhost:8787
read -rs SMOKE_UAZAPI_TOKEN && export SMOKE_UAZAPI_TOKEN # opcional: testa leituras na instância real
MCP_URL=http://localhost:8787/<subdominio>/mcp npm run smoke
```
O smoke test usa um cliente da spec 2026-07-28 e outro de 2025. Sem `SMOKE_UAZAPI_TOKEN`, não chama o Uazapi; com ele, faz só leituras.
O catálogo é gerado do OpenAPI oficial (`docs.uazapi.com/openapi-bundled.json`) e fica travado pelo `contractSha256` do `release.json`.
## Known gaps
- Não há confirmação (elicitation/MRTR) antes de ações destrutivas, porque Composio e Executor não suportam elicitation. A proteção vem das anotações `destructiveHint` e das aprovações do cliente.
- Não há eventos em tempo real (`/sse` do Uazapi → `subscriptions/listen`). Ficam para quando um cliente-alvo suportar assinaturas.
- `call_operation` não valida argumentos localmente. A validação é a da API Uazapi, e um erro 400 volta para o agente.
- Respostas acima de 60 mil caracteres são cortadas com aviso para paginar.
- Chamadas ao Uazapi expiram em 30 s. Um envio que expira pode ter sido entregue, e o agente é avisado para conferir antes de repetir.
## Licença
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues