npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@botozap/mcp

v0.20.0

Published

Servidor MCP (Model Context Protocol) para a API pública do BotoZap — expõe as operações da plataforma WhatsApp Cloud API como ferramentas MCP para Claude Code, Cursor e Codex.

Readme

@botozap/mcp

Servidor MCP (Model Context Protocol) para a API pública do BotoZap — a plataforma sobre a WhatsApp Cloud API oficial. Painel, API/MCP e links de setup operam a mesma Conta, para um ou vários negócios.

Ele expõe as operações da plataforma (/api/v1) como ferramentas MCP e o stream durável de Eventos como resource assinável, para que assistentes como Claude Code, Cursor e Codex consigam operar o canal e receber um sinal de baixa latência quando o WhatsApp mudar.

Cada ferramenta MCP mapeia para uma operação do SDK oficial @botozap/sdk (que autentica com a sua chave bz_..., monta a requisição e trata o envelope de erro). É uma ponte fina: valida os argumentos e resultados (zod), delega ao SDK e devolve o JSON da API.

Todas as tools anunciam outputSchema e devolvem o resultado em structuredContent, além do content[].text em JSON mantido para clientes antigos. Isso inclui paginação por cursor/offset, operações de gestão e os DELETE sem corpo — estes preservam null no texto legado e devolvem { success: true } na saída estruturada. Erros preservam isError e também expõem { error: { code, message, status } } em structuredContent, sem credenciais.

Status: preview público 0.x. Sem promessa de estabilidade de tools e argumentos até a 1.0; fixe a versão para integrações reproduzíveis.

Requisitos

  • Node.js ≥ 20.19 (ESM).
  • Uma chave de API do BotoZap (gere em /chaves no painel).
  • pnpm (este monorepo usa pnpm exclusivamente).

Descoberta por permissão (próxima versão)

A inicialização consulta GET /v1/me pelo SDK e anuncia somente as ferramentas permitidas pelos escopos e pelo ambiente da credencial. A API precisa oferecer esse endpoint antes de publicar o SDK e o MCP; erro de introspecção impede iniciar a sessão, sem fallback para o catálogo completo. Essa consulta não exige events:read: o escopo só é necessário para o resource de Eventos.

No stdio, o catálogo reflete a inicialização; reconecte após alterar escopos. No Streamable HTTP, a identidade da sessão é reutilizada por até 60 segundos, inclusive nas consultas periódicas de Eventos. Após esse prazo, uma introspecção compartilhada entre requests e polls atualiza as permissões. A chave de API precisa manter o fingerprint original. Um bearer OAuth novo exige introspecção imediata e só é aceito para a mesma Conta, ambiente, usuário, cliente e grant. A descoberta OAuth também exige as rotas concretas usadas pela ferramenta.

A API continua autorizando cada chamada, inclusive após revogação da credencial; esconder uma ferramenta e reutilizar a identidade não substituem essa verificação. reply_to_conversation exige conversations:read e messages:send, pois o SDK resolve a conversa antes de enviar. get_profile identifica a Conta e o ambiente autorizados e revalida a credencial a cada consulta; não representa uma pessoa.

Todas as ferramentas declaram readOnlyHint, destructiveHint, idempotentHint e openWorldHint, sempre como true ou false, conforme seus efeitos. Operações compostas consideram também efeitos indiretos (por exemplo, criar um compromisso pode iniciar uma régua que envia mensagem ao contato). As anotações orientam o host; não implementam autorização nem comprovam que uma pessoa confirmou a ação. OAuth e a publicação no diretório ChatGPT são etapas separadas.

Para desenvolvimento: buildServer é assíncrono. Aguarde sua conclusão antes de conectar o transporte. Os testes de operação usam identidade previamente validada; os testes de descoberta exercitam o contrato real de introspecção com HTTP simulado.

Compatibilidade das respostas e ordem de publicação

Recibos de envio (send_message, send_media_message, reply_to_conversation) seguem a API: no WhatsApp trazem wamid; no Instagram, wamid: null, channel: "instagram" e o mid em external_id (em reação, reaction.message_id é o mid do alvo). O output schema aceita as duas formas.

Respostas tipadas publicam somente os campos declarados pelo schema, tanto no JSON textual quanto em structuredContent. list_users.data[].id e o alias data[].user_id continuam disponíveis. A auditoria dos endpoints revisados não identificou remoção de campo público na projeção final. O segredo HMAC de webhook continua disponível apenas em create_webhook. Conteúdo dinâmico de mensagens, metadados de contatos e payloads de IA preservam seus objetos de negócio.

Publique primeiro a API com GET /v1/me validado; só depois publique SDK e MCP. Clientes que usam npx @botozap/mcp ou pnpm dlx @botozap/mcp sem versão podem receber a atualização imediatamente. Veja o plano de release para a auditoria dos campos e as verificações por ambiente.

Instalação

O pacote pode ser executado diretamente do npm:

pnpm dlx @botozap/[email protected]

Para desenvolver o monorepo localmente:

pnpm install
pnpm build
pnpm --filter @botozap/mcp smoke
pnpm --filter @botozap/mcp test

Configuração (variáveis de ambiente)

| Variável | Quando | Default | Descrição | | --- | --- | --- | --- | | BOTOZAP_API_KEY | Obrigatória em stdio | — | Chave bz_... usada pelo processo stdio. | | BOTOZAP_API_URL | Opcional | https://botozap.com.br/api/v1 | Base da API (útil para staging/local). | | BOTOZAP_MCP_TRANSPORT | Remoto | stdio | Use streamable-http para iniciar o endpoint remoto. | | BOTOZAP_MCP_HOST | Remoto | 127.0.0.1 | Interface TCP do endpoint remoto. | | BOTOZAP_MCP_PORT | Remoto | 3001 | Porta do endpoint remoto (0 escolhe uma porta livre). | | BOTOZAP_MCP_ALLOWED_HOSTS | Remoto | loopback em localhost | CSV saneado (trim, vazio descartado) dos valores de Host aceitos em /mcp. Obrigatória fora de localhost. | | BOTOZAP_MCP_ALLOWED_ORIGINS | Remoto | vazio | CSV saneado das Origin aceitas em /mcp (match exato). Sem Origin continua aceito; Origin presente só passa se estiver na lista. | | BOTOZAP_MCP_RATE_LIMIT_PER_CLIENT | Remoto | 120 | Requisições por minuto por cliente/IP antes da autenticação. | | BOTOZAP_MCP_RATE_LIMIT_GLOBAL | Remoto | 1200 | Teto global de requisições por minuto por processo. | | BOTOZAP_MCP_TRUSTED_PROXY_CIDRS | Remoto | vazio | CSV de CIDRs autorizados a alcançar /mcp; com Cloudflare, use somente as faixas oficiais e o rate limit passa a usar CF-Connecting-IP. | | BOTOZAP_EVENT_BUS_DATABASE_URL | Obrigatória no remoto | — | Conexão PostgreSQL de sessão que suporta LISTEN/NOTIFY. |

Sem a configuração obrigatória do transporte escolhido, o servidor falha imediatamente com uma mensagem clara e sem imprimir o valor recebido.

Streamable HTTP (operador da plataforma)

O mesmo binário pode manter sessões remotas stateful em /mcp. Nesse modo cada cliente envia sua própria chave BotoZap no header Authorization: Bearer; a sessão fica vinculada a essa credencial e a Conta/ambiente continuam derivados pela API. A chave nunca entra na URL do endpoint, URI do resource ou notification.

BOTOZAP_MCP_TRANSPORT=streamable-http \
BOTOZAP_MCP_HOST=0.0.0.0 \
BOTOZAP_MCP_PORT=3001 \
BOTOZAP_MCP_ALLOWED_HOSTS=mcp.botozap.com.br \
BOTOZAP_EVENT_BUS_DATABASE_URL=postgresql://... \
pnpm dlx @botozap/[email protected]

Bind em 0.0.0.0 ou :: sem BOTOZAP_MCP_ALLOWED_HOSTS recusa o boot (fail-closed). Em 127.0.0.1/::1/localhost a allowlist padrão de loopback é aplicada e o desenvolvimento local segue igual.

Todo request a /mcp valida Host e Origin antes de Bearer e do parse do corpo, como exige o transporte Streamable HTTP (spec 2025-11-25): Host precisa estar na allowlist; Origin ausente é aceita (clientes server-to-server); Origin presente só passa por match exato. Desvio responde 403 JSON-RPC (-32000) sem autenticar.

GET /healthz é público, mínimo e sem segredo: responde 200 com {"ok": true} para readiness (Fly). Não exige Bearer, não consulta sessão e não revela estado interno — o health check interno pode usar um Host que não está na allowlist de /mcp.

Quando BOTOZAP_MCP_TRUSTED_PROXY_CIDRS está definida, /mcp exige que o Fly-Client-IP pertença a uma das redes configuradas; isso bloqueia acesso direto ao origin mesmo com Host forjado. Em deploy atrás da Cloudflare, mantenha a lista sincronizada com https://www.cloudflare.com/ips/; somente depois dessa validação o rate limiter confia em CF-Connecting-IP.

Use uma conexão PostgreSQL persistente/direta ou pooler em modo de sessão; pooler em modo de transação não preserva LISTEN. O bus carrega somente um sinal vazio. Ao recebê-lo, cada sessão consulta /events com sua própria chave e só envia notifications/resources/updated se houver Evento no cursor autorizado. O payload autoritativo permanece no stream durável.

Se o bus cair — inclusive durante o boot — o servidor continua disponível em modo degradado, reconecta com backoff de 250 ms a 10 s e mantém um heartbeat de reconciliação pelo cursor a cada 15 s. Um sinal que chega enquanto outra leitura está em voo fica pendente para um novo probe imediato; não depende do próximo heartbeat.

O processo mantém no máximo 1.000 sessões, 5 sessões por chave e 8 resources de Eventos por sessão. resources/unsubscribe interrompe o consumo daquele resource; DELETE /mcp encerra a sessão e libera listeners/timers. Uma sessão que perde o transporte sem enviar DELETE é recolhida após 5 minutos sem request ativo (varredura a cada 30 s). Esses limites são conservadores para o preview 0.x e podem mudar antes da 1.0.

Usar com Claude Code

Via CLI:

claude mcp add botozap \
  --env BOTOZAP_API_KEY=bz_live_suachaveaqui \
  -- pnpm dlx @botozap/[email protected]

Ou no JSON do MCP (.mcp.json do projeto ou config do usuário):

{
  "mcpServers": {
    "botozap": {
      "command": "pnpm",
      "args": ["dlx", "@botozap/[email protected]"],
      "env": {
        "BOTOZAP_API_KEY": "bz_live_suachaveaqui"
      }
    }
  }
}

Usar com Cursor

Edite ~/.cursor/mcp.json (global) ou .cursor/mcp.json (no projeto):

{
  "mcpServers": {
    "botozap": {
      "command": "pnpm",
      "args": ["dlx", "@botozap/[email protected]"],
      "env": {
        "BOTOZAP_API_KEY": "bz_live_suachaveaqui"
      }
    }
  }
}

Ferramentas disponíveis

Transportes: stdio (padrão, local) e Streamable HTTP (remoto stateful). Nomes em inglês (snake_case, melhor para tool-calling); descrições em PT-BR.

Mensagens — send_message (text, template, interactive: WhatsApp button/list/cta_url, Instagram button (web_url/postback) ou generic (1–10 cards), location, reaction; reply_to responde citando, menos em reaction), list_messages (sort: created_at ou event_at; cada item traz source e, no catálogo completo, reply_to, played_at e transcription), get_message send_message aceita from da Conta de canal, to IGSID, type: "interactive" e tag?: "human_agent" (sujeita à flag e janela). Instagram devolve wamid: null, external_id, channel: "instagram", type: "interactive"; mantenha idempotency_key em retries. Os schemas anunciam os formatos de botões/cards; a API valida limites e erros 422.

Conversas — reply_to_conversation (reply_to_message_id cita uma mensagem da conversa), conversation_typing ("digitando…"; messages:send; no_inbound_message sem mensagem do contato), mark_conversation_read (tiques azuis; conversations:write), list_conversations, get_conversation, update_conversation, request_consent (pedido de consentimento: botões no WhatsApp, respostas rápidas no Instagram; só com a janela de 24h aberta; outside_window/window_closed; messages:send), delete_conversation (destrutiva e sem volta: mensagens, notas, vínculos e mídia; o contato fica; só Administrador; conversations:write) (leituras trazem entry_point, referral Click-to-WhatsApp, fep_expires_at e fep_reply_by) Contatos — list_contacts, get_contact, create_contact, update_contact, delete_contact (apaga também a mídia; a recusa fica guardada como código cifrado e vale se o contato voltar), list_contact_consents e record_contact_consent (origens contact_keyword, template_button e meta_opt_out entre as de sempre; aceite sobre recusa responde consent_opted_out) (display_name é o nome dado pela empresa; null limpa; create_contact devolve warnings quando o contato já recusou) Mídia — send_media_message (WhatsApp ou Instagram: to aceita o IGSID; o Instagram não aceita caption; voice: true só em audio no WhatsApp; reply_to), ingest_media (só WhatsApp) Clientes — list_customers, get_customer, create_customer, update_customer (inclui opt_out_keywords, opt_in_keywords, opt_out_confirmation e opt_in_confirmation), delete_customer Links de setup — list_setup_links, create_setup_link, update_setup_link (um canal por link: channel whatsapp, o padrão, ou instagram, em que allowed_connection_types não vale; redirects https: concluído → success_redirect_url com status=completed; link esgotado → failure_redirect_url com status=failed; cliente volta num erro recuperável → failure_redirect_url com status=cancelled, link segue válido; todo destino recebe setup_link_id) Números — list_phone_numbers, get_phone_number, update_phone_number (só o label, nome local), phone_number_health, get_phone_number_history_import e set_phone_number_history_import (conversas antigas do app WhatsApp Business em coexistência: 0, 30 ou 180 dias, escolha única por conexão) Configurações do número (WhatsApp) — get_phone_number_profile, update_phone_number_profile (contato público do negócio; por OAuth a API responde 403; coexistência 409 coexistence_not_supported), get_conversational_automation, update_conversational_automation (gravar exige plano com automações), clear_conversational_automation, list_qr_codes, create_qr_code (não idempotente), get_qr_code, update_qr_code, delete_qr_code (sem volta), list_blocked_users, block_contact (só quem escreveu nas últimas 24 h), unblock_contact. Direto na Meta, sem sandbox, numbers:read/numbers:write; a foto do perfil fica no SDK/CLI/API Contas de canal — list_channel_accounts (Números do WhatsApp e Contas do Instagram; filtros channel e customer_id; o bloco instagram traz token_status da conexão), get_channel_account (no catálogo completo, com ou sem UI) IA de comentários (Instagram) — get_instagram_comment_ai, update_instagram_comment_ai, list_instagram_comment_ai_runs (somente catálogo completo, fora de assistant). Todas recebem channel_account_id (UUID interno), sem sandbox. GET/runs exigem agents:read; update exige agents:write e capacidade ai do plano, exceto quando somente desliga. PATCH parcial com enabled, agent_id (UUID ou null) e daily_limit (inteiro 1–200): ausente preserva, null limpa o agente quando desligada. Ativar permite respostas públicas e DMs automáticas; a tool anuncia destructiveHint: true e openWorldHint: true. No OAuth, PATCH é exclusivo do painel. GET/update retornam {data} com configuração, status, respostas/reservas de hoje, timezone, gate e prontidão; runs usa page/per_page e retorna {data,meta} com decisões e estados dos envios. Regras de comentário (Instagram) — list_comment_rules, get_comment_rule, create_comment_rule, update_comment_rule, delete_comment_rule (scopes comment-rules:read/comment-rules:write; uma regra ativa dispara directs automáticos; criação/leitura/atualização incluem keywords, excluded_keywords, reply_variations e next_post; exclusão retorna {data:{id,deleted:true}}) Regras de mensagem (Instagram) — list_message_rules, get_message_rule, create_message_rule, update_message_rule, delete_message_rule (message-rules:read/write, sandbox permitido). Resultados de comentário — get_comment_rule_metrics (comment-rules:read, from/to obrigatórios). Perguntas e menu (Instagram) — get_instagram_messenger_profile, update_instagram_messenger_profile, delete_instagram_messenger_profile, sync_instagram_messenger_profile (scopes messenger-profile:read/messenger-profile:write; sem sandbox; catálogo completo). Todas usam channel_account_id e retornam {data:{channel_account_id,config,revision,sync_status,synced_at,last_error_code,updated_at}}. Update aceita ice_breakers ({question,payload}[], até 4) e persistent_menu (até 5 itens postback {type,title,payload} ou web_url {type,title,url}): ausente preserva; null ou [] remove. Sync verifica/reaplica explicitamente o desejado salvo; delete conserva configuração vazia. Templates — list_templates, get_template, create_template, update_template (marca os botões de saída em opt_out_buttons; no catálogo completo as leituras trazem opt_out_buttons) Webhooks — list_webhooks, get_webhook, create_webhook, update_webhook, delete_webhook, test_webhook (events aceita a categoria opt-in app_messages; channel_account_ids em create_webhook/update_webhook limita o endpoint a alguns canais e null no update volta a todos; as leituras trazem channels_health, a saúde por canal dos canais em falha) Entregas de webhook — list_webhook_deliveries (filtros por status, evento e canal channel_account_id; status limited = cortada pelo limite de repasse do Free) Logs — list_api_logs Usuários — list_users Registro de atividades — list_team_events (team:read; só Administrador; filtros since, until, actor_id, action, target_type e target_id). Só chave de API, fora do perfil assistant e sem sandbox Exportação de dados — create_export (exports:write), list_exports e get_export (exports:read). O arquivo fica 7 dias; get_export devolve um link de download que vale poucos minutos. Só chave de API, fora do perfil assistant e sem sandbox Uso — get_meta_costs (custo aproximado da Meta por moeda, dia e categoria; custo ausente vem null, nunca 0) Plano e slots — get_billing (billing:read; traz guarantee, a garantia de 7 dias), create_business_slot e cancel_business_slot (billing:write). Só chave de API, fora do perfil assistant e sem sandbox. O plano pago inclui 1 negócio; cada negócio a mais ocupa um slot de R$ 47/mês. create_business_slot gera essa cobrança e aceita idempotency_key (a mesma chave devolve o mesmo slot); recusas plan_required, slot_limit e billing_customer_required. cancel_business_slot (slot_id) cancela só slot vazio, sem reembolso; ocupado responde slot_in_use. Os links de setup trazem connection_billing (o que acontece na cobrança quando o Cliente conectar). Limites do Free — o Free permite 1 Cliente e 1 número. No catálogo completo, list_customers, get_customer, list_phone_numbers, get_phone_number e update_phone_number trazem plan_suspended: o recurso acima do teto continua recebendo, mas não envia (402 free_cap pelo Cliente, 402 free_number_cap pelo número); create_customer do 2º Cliente responde 402 free_cap. Assinar libera na hora; no Free, desconectar o outro número ou apagar o outro Cliente libera a vaga. Regras de mensagem do Direct e Perguntas e menu respondem 403 plan_restricted no Free (pausar uma regra e remover seções seguem livres). O perfil assistant não muda: sem plan_suspended na saída e com as mesmas descrições.

Resource de Eventos

Os dois transportes anunciam resources.subscribe e o template botozap://events{?after,limit}. Um cliente persistente pode assinar, por exemplo, botozap://events?after=42&limit=100 e receber notifications/resources/updated quando existir um Evento posterior ao cursor 42. O tail da API existe somente enquanto há assinatura ativa; clientes que usam apenas tools não iniciam consultas de Eventos. A chave usada pelo servidor precisa incluir o escopo events:read.

A notification é deliberadamente só um sinal e não carrega mensagem, Contato ou credencial. Ao recebê-la, releia o mesmo resource, processe data e persista paging.cursor. Depois de uma desconexão, leia a partir do último cursor salvo e drene todas as páginas de catch-up (paging.next). Só então assine uma nova URI com o cursor final em after; o probe inicial da assinatura fecha a corrida com um Evento persistido entre a última leitura e o subscribe. Notifications são hints at-least-once e podem se repetir; deduplique por event.id antes de produzir qualquer resposta. O mesmo Evento conserva id, cursor e identidade de mensagem em retries e replay.

No stdio, o tail usa intervalo padrão de 1,5 segundo e para no unsubscribe ou no fim da sessão. No remoto, o PostgreSQL acorda o processo MCP mesmo quando o Evento foi persistido por outra instância; a consulta escopada continua sendo a fonte do payload e o heartbeat cobre sinais perdidos. Receber a notification não significa que todo host inicia automaticamente uma nova execução do agente.

Em caso de erro da API, a ferramenta devolve um resultado de erro (isError) com a mensagem PT-BR do envelope { error: { code, message } } no formato Erro [code]: message. code, message e o status HTTP também ficam disponíveis de forma programática em todas as tools.

Chaves de sandbox

Uma chave de sandbox (prefixo bz_sandbox_) habilita as ferramentas de mensagem (send_message, send_media_message, list_messages e get_message) e o resource de Eventos do próprio Sandbox. Qualquer outra ferramenta responde 403 sandbox_forbidden (vindo da API) — útil para testar a integração sem tocar dados reais.

Números mágicos (destinatários simulados, sem custo, sem número real):

| to | Comportamento simulado | | ---------------- | ------------------------------------------------------- | | +5500000000001 | happy path: sent → delivered → read, janela sempre aberta | | +5500000000002 | falha: sent → failed (erro Meta 131026) | | +5500000000003 | sent → delivered + resposta inbound simulada (abre a janela de 24h) |

Smokes de sandbox

Há dois smokes, com propósitos distintos:

  • Hermético (roda no test, CI): tests/sandbox-smoke.test.ts usa um fetch stub fiel ao contrato do sandbox — não precisa de credencial nem rede. Verifica que o texto (acento + emoji) sobrevive intacto, que a chave vira Authorization: Bearer, que a resposta (sandbox: true + wamid.sandbox.<...>) chega ao agente, e que a chave nunca aparece no resultado.

  • Externo (manual, exige credencial): pnpm --filter @botozap/mcp smoke:sandbox bate na API real. Exige dist/ compilado (pnpm --filter @botozap/mcp build antes) e as variáveis BOTOZAP_API_KEY (uma chave bz_sandbox_) e BOTOZAP_API_URL. Recusa qualquer chave que não seja sandbox e nunca imprime a chave (redação defensiva em toda saída, inclusive erros). Nunca entra no test — não roda em PR/fork, que não têm credencial.

    pnpm --filter @botozap/mcp build
    BOTOZAP_API_KEY=bz_sandbox_suachave BOTOZAP_API_URL=https://.../api/v1 \
      pnpm --filter @botozap/mcp smoke:sandbox

Segurança

A conta é sempre derivada da chave de API no servidor do BotoZap (multi-tenant, IDOR-safe). A chave é um segredo — não a comite nem a logue. O servidor MCP só escreve logs em stderr (stdout é reservado para o protocolo MCP); a chave nunca aparece em resultado de ferramenta, mensagem de erro, /healthz ou resposta 403 de Host/Origin.

Publicação pelos mantenedores

Use pnpm release:mcp a partir de um checkout limpo da main, após pnpm build, pnpm typecheck, pnpm test e pnpm gate:tarballs. O comando empacota com pnpm, verifica as dependências no próprio tarball e publica esse mesmo arquivo com npm publish. Publicação usa npm; instalação, build e pack continuam usando pnpm. pnpm release:mcp --dry-run valida sem publicar e aceita branches de revisão com alterações locais; a publicação real exige main limpa. Publique primeiro o SDK 0.10.0, depois a CLI 0.6.1 e o MCP 0.8.0: os dois tarballs dependem da versão exata do SDK, convertida de workspace:*.

Não publique o diretório com outro empacotador: workspace:* pertence ao monorepo e precisa virar a versão do SDK no artefato. A versão 0.2.5 violou esse contrato; a 0.2.6 corrige a instalação. Após publicar, instale a versão exata em um projeto vazio sem overrides e repita o cliente MCP real. O gate com override serve para candidatos cujo SDK ainda não está no registry.

2FA exige terminal interativo. npm publish pede o código do autenticador na hora; rodar pnpm release:mcp fora de um terminal interativo (CI, script não-interativo, agente sem TTY) não tem como responder esse prompt. Publique a partir de um terminal interativo de verdade.

Depois do npm publish, o npm pode levar alguns minutos para listar a versão nova (janela de processamento). pnpm release:mcp espera essa janela com um poll em npm view @botozap/mcp@<versão> version (~20 min de timeout, 15 s de intervalo) antes de instalar a versão publicada na prova pós-registry; o timeout imprime como conferir manualmente. Se a publicação ficar presa nessa janela sem 2FA (terminal não-interativo) ou a versão demorar mais que o timeout, não rode pnpm release:mcp de novo: publicar de novo com a versão anterior ainda na janela de processamento falha com 409 "Cannot publish over previously staged version", e essa versão fica presa até a janela passar sozinha. Espere a versão aparecer (npm view acima) e, só então, rode a prova pós-registry manualmente se quiser confirmar sem esperar o script.

Atendimento, CRM e Agenda

O catálogo inclui respostas compartilhadas, notas/retornos/arquivo/adiamento, oportunidades/demandas com histórico e conversas, Radar, réguas e execuções, Agenda completa e sincronização Google Calendar. Etapas, campos e atribuições a membros também podem ser descobertos e gerenciados. Cada operação tem schemas de entrada e saída estruturada, com campos de versão exigidos quando aplicável.

A chave representa a Conta: não acessa rascunhos/respostas pessoais nem executa a autorização OAuth em nome de um humano. Configure Google em /calendarios. Ferramentas de réguas e ativação podem gerar envios: use conforme instrução do usuário, preservando o controle de canal, consentimento e plano do servidor. Modelos prontos de Régua: list_journey_models (os mesmos da galeria de Automações) e enable_journey_model (cria e submete o template do modelo e a Régua, que liga sozinha quando a Meta aprova; destrutiva, só a pedido).

Scopes, CAS e roteiro de release.

IA usa exclusivamente credenciais próprias (BYOK): SDK client.ai, CLI botozap ai e ferramentas MCP ai_*. Agentes versionados, provedores, credenciais, conhecimento, memória, skills, follow-ups e retornos prometidos, roteadores, casos, alertas, avisos, propostas de aprendizado e comerciais, controle de acesso (elegibilidade), inferências, promessas do operador, catálogo de modelos, execuções e uso. Scopes agents:read/write; aprovação exige chave criada por owner/admin ainda autorizado. Prévia não envia WhatsApp. Não há carteira, créditos ou compra de vagas de IA. ai_agents_diagnostic devolve o diagnóstico do agente, o mesmo do painel (só lê, sem custo). O catálogo de provedores é só openai e anthropic: as entradas ai_* aceitam só esses dois (outro provedor responde provider_not_enabled). Contratos e exemplos IA.

Tamanho e contrato dos retornos

O texto de cada resposta usa JSON compacto. Nas entidades com schema tipado, campos fora do contrato são removidos antes de gerar texto e structuredContent. IDs, cursores e metadados de paginação são preservados; textos e listas não são truncados. O segredo HMAC de webhook só é retornado na criação.

Os payloads de negócio dinâmicos (IA, CRM/Agenda, conteúdo de mensagens, metadados de contato, diagnóstico de saúde e extensões de mídia) mantêm seu conteúdo. Reduções futuras nesses domínios precisam de contratos específicos.

OAuth no transporte remoto

Configure as mesmas URLs canônicas usadas pelo app BotoZap:

OAUTH_ENABLED=true
OAUTH_RESOURCE_URL=https://mcp.botozap.com.br/mcp
OAUTH_ISSUER_URL=https://botozap.com.br

O endpoint publica metadados RFC 9728 em /.well-known/oauth-protected-resource/mcp e /.well-known/oauth-protected-resource. Respostas 401 incluem o endereço desses metadados em WWW-Authenticate. As URLs vêm da configuração do servidor.

scopes_supported anuncia openid, profile, email e phone, aceitos pelo servidor de autorização do app. As permissões BotoZap, como contacts:read e messages:send, são escolhidas no consentimento e retornadas por /v1/me; não devem ser enviadas no parâmetro OAuth scope. O parâmetro OAuth resource deve usar a URL canônica https://mcp.botozap.com.br/mcp em autorização e troca de token.

Cada request, inclusive em sessões já abertas, valida a credencial em /v1/me. Uma sessão OAuth aceita o token renovado somente quando grant, usuário, cliente, Conta e ambiente permanecem iguais. Chaves de API continuam vinculadas ao fingerprint original. Tokens de requests simultâneos ficam isolados; chamadas de API usam a credencial do próprio request. A descoberta de ferramentas acompanha os scopes e as rotas efetivamente permitidas pelo app. Revogação e mudança de papel passam a valer na próxima requisição.

O transporte stdio continua recebendo a credencial pelo ambiente BOTOZAP_API_KEY. O servidor MCP não realiza login nem armazena refresh tokens.

Intenção de envio e painel de revisão

send_message, send_media_message e reply_to_conversation continuam aceitando as chamadas da versão 0.6.0. idempotency_key é opcional; omiti-la mantém o fluxo anterior, sem deduplicação por chave. Recomenda-se chamar prepare_send_intent antes de um novo envio e passar a chave retornada à tool. Em retry, preserve chave e conteúdo. Não crie outra chave para contornar timeout ou 409 pendente.

O painel candidato de Plugin Extensions é habilitado com BOTOZAP_MCP_UI_ENABLED=true (HTTP ou stdio). open_review_panel abre a seleção de negócio e Radar; stage_review_reply prepara um rascunho sem enviar. O rascunho e a UI sempre preservam uma chave por intenção, com confirmação explícita e sem retry automático. A UI só aparece após o cliente negociar MCP Apps: capabilities.extensions["io.modelcontextprotocol/ui"].mimeTypes deve incluir text/html;profile=mcp-app. Sem essa capacidade, mesmo com as flags, o catálogo é tools-only, igual ao catálogo sem UI.

O build inclui dist/ui/review.html, sem assets externos nem credenciais. pnpm preview:build cria uma demonstração local em web/.preview/demo.html, com dados fictícios. O pacote de plugin candidato está em plugins/botozap; não é uma publicação no diretório.

API /v1/me e suporte a idempotência devem estar disponíveis antes de publicar os pacotes novos. Resultados terminais por chave expiram após 24h; uma tentativa desconhecida permanece pendente até reconciliação. Ver docs/chatgpt-phase1-release.md para a ordem de release e compatibilidade.

Recusas confirmadas (outcome: rejected) liberam a chave. Aguarde o backoff ou corrija a causa e repita com a mesma chave; após liberação, outro payload é uma nova intenção. unknown ou accepted exigem conciliação antes de considerar outro envio. SDK e MCP preservam outcome e retry da API. Resultados concluídos vencidos são removidos pelo cron a cada minuto; claims incertas não são removidas por tempo, conforme o runbook botozap/docs/ops/message-send-receipts.md.

Piloto da UI por conta

Com BOTOZAP_MCP_UI_ENABLED=true, a variável opcional BOTOZAP_MCP_UI_ACCOUNTS=account_id_1,account_id_2 restringe as tools de revisão, recursos e metadados de UI às Contas selecionadas, usando a identidade autenticada já resolvida da sessão. Conta fora da lista recebe o catálogo tools-only habitual (fixture 0.6.0 mais get_profile e prepare_send_intent). Lista definida mas vazia não libera nenhuma conta; espaços são ignorados. Sem essa variável, a flag mantém a regra de contas da flag, sempre condicionada à capacidade MCP Apps. Sem a flag, a lista não ativa UI. Nenhuma configuração foi habilitada em produção por este protótipo.

Simulador de hosts MCP Apps

pnpm --filter @botozap/mcp demo:ui
# Abra http://127.0.0.1:4173/chat

A página hospeda os iframes reais de produção via AppBridge / MCP Apps. Dados, modelo, envio e eventos de entregue/lido são simulados exclusivamente no host. Use o compositor ou os controles do roteiro para explorar carrossel, rascunho, conversa fullscreen e os estados alternativos. Consulte web/DESIGN.md.

O seletor também inclui 4 · Template aprovado, 6 · O agente precisa de você e 7 · Marcar horário, com carregamento, vazio, erro, recusa/incerteza quando há envio, e ausência de permissão no atendimento. A janela fechada abre um editor de templates aprovados com prévia renderizada. Quando o cliente anuncia extensions["openai/elicitation"].form, as variáveis usam o formulário nativo com x-openai-suggestions; nos demais hosts ficam no formulário acessível da UI. Nenhum formulário envia sem a confirmação final.

As auxiliares stage_review_template, review_template_variables, open_agent_cases e stage_appointment_booking só aparecem no piloto da UI, respeitam allowed_routes, não aceitam account_id e apenas consultam/preparam. Envio, atribuição de caso e compromisso usam as tools existentes. Um resultado incerto bloqueia repetição; compromisso criado com aviso incerto continua criado sem tentar marcar novamente. Casos usam expected_revision e a identidade do operador; devolver ao agente exige confirmação da retomada automática.

# Com a demo em execução, gerar as capturas e vídeos da rodada 3A:
node packages/mcp/scripts/screenshot-3a.mjs

O seletor Host alterna ChatGPT, Genérico MCP Apps (variáveis de estilo próprias, sem elicitation OpenAI) e Sem UI (dados e rascunho em texto, sem iframe). Templates aprovados incluem cabeçalho texto/imagem/vídeo/documento/localização, corpo posicional/nomeado, moeda/data com fallback, rodapé, respostas rápidas, URL dinâmica, telefone, cupom, autenticação e carrossel. Oferta limitada e botões de catálogo já sincronizados têm prévia e parâmetros; isso não cria templates que o builder do app hoje não aceita. Consulte web/RELATORIO-4A.md para limites. A mídia usa somente placeholders locais: não baixa arquivos, resolve URLs ou valida existência de media_id. Nome/tamanho são exibidos quando informados no contexto. Códigos de autenticação devem ser emitidos pelo sistema do negócio.

stage_review_template aceita campos opcionais template_id, suggested_values (por ID de template) e media_metadata (por slot header/card_0_header, com filename e file_size em bytes); nada muda nos parâmetros obrigatórios das tools.

A tela de escalação lê usage.unique_contacts_today e usage.timezone (também aceita ambos em usage.handoff). Contagem inteira não negativa + fuso IANA válido mostram clientes hoje no fuso do negócio. Sem ambos, mantém a métrica real usage.handoff.conversations com o rótulo conversas hoje (UTC). Não deduz pessoas a partir de conversas. A API dessa contagem é trabalho separado.

node packages/mcp/scripts/screenshot-4a.mjs

Capturas/vídeos e validação axe A/AA ficam em web/screenshots/.

Templates: prévia primeiro

Na revisão de templates, peça ajustes na conversa ou abra Editar campos. Referências de mídia e outros detalhes técnicos ficam em Avançado; o código de autenticação vem do sistema do negócio e permanece visível. O staging existente atualiza a prévia, sem enviar. Veja o roteiro e as capturas da rodada 4A.2.

Perfil de catálogo OAuth

API keys mantêm o catálogo completo, sujeito aos scopes, ambiente e rotas existentes. Sessões OAuth usam o perfil assistant por padrão, inclusive para um client_id não mapeado. Esse perfil oferece 35 tools sem UI e até 44 com UI negociada; list_users fica visível apenas à app quando a UI está ativa, pois o agendamento precisa do nome do responsável. O limite real pode ser menor conforme os scopes/rotas da identidade. Administração, webhooks e credenciais de IA ficam fora desse perfil.

A configuração opcional BOTOZAP_MCP_OAUTH_CLIENT_PROFILES aceita pares UUID:perfil separados por vírgula, por exemplo:

e948ea06-d925-41b6-980c-d7ea3538a4ac:assistant,1ec8fa7c-f51a-44dd-88b2-b5696bc0b627:assistant

Os perfis válidos são assistant e full. full é uma opção explícita para clientes futuros que precisem do catálogo completo; não altera os scopes/rotas autorizados. A configuração é lida na criação da sessão/servidor e rejeita UUIDs, perfis ou duplicatas inválidos. O filtro vale no registro, na atualização de identidade e na execução: conhecer o nome de uma tool omitida não permite chamá-la. Nenhuma configuração foi aplicada a produção neste PR.

Instagram — onda 2

Regras de comentário incluem dm_button_title, delivery_text e delivery_media. Regras de mensagem têm CRUD com gatilhos e ações tipados (message-rules:read/write); métricas de comentário recebem from/to (comment-rules:read). Contrato e exemplos de SDK, CLI e MCP.

Backlog do Instagram: catálogo completo

| Tool | Permissão | Efeito | | --- | --- | --- | | get_instagram_connection | numbers:read | Estado, saúde e diagnóstico; sem sandbox | | resubscribe_instagram_connection | numbers:write | Reassina webhooks com autorização existente; sem sandbox | | disconnect_instagram_connection | numbers:write | Desconecta, destructiveHint: true; chave de API, sem sandbox | | get_message_rule_qr | message-rules:read | Link ig.me em structuredContent.data.ref_link e imagem PNG no conteúdo MCP |

Todas recebem channel_account_id; o QR também recebe rule_id. Regras aceitam require_follow e non_follower_text; regras de comentário também aceitam live, com 422 feature_unavailable quando indisponível. Métricas preservam posts[].live. list_contacts e list_conversations aceitam ig_follows_business, inclusive false.

Essas quatro tools pertencem somente ao catálogo completo. A composição do perfil assistant permanece intacta. Os cinco campos somente leitura de perfil Instagram adicionados ao contactSchema passam a aparecer nas saídas de get_contact e list_contacts desse perfil quando esta versão for publicada na Fly; o filtro opcional também é aceito nas duas listagens existentes. Deploy na Fly continua suspenso pela issue #760.