As tools do MCP formam o plano de comando. O resource botozap://events{?after,limit} forma um canal de Eventos para clientes que mantêm uma sessão e negociam resources.subscribe. A notification é apenas um sinal para releitura: ela não inicia, por si só, uma nova execução do agente.
Meta → BotoZap → account_events (PostgreSQL)
├─ notification MCP (sinal)
│ └─ read resource → Evento → cursor salvo
└─ outbox → Endpoint durável
└─ fila do dev → novo agent turnVersão e disponibilidade
Use @botozap/mcp@0.9.0: 270 tools com resultados estruturados (149 do módulo de IA BYOK), resource de Eventos assinável e transportes stdio e Streamable HTTP. O SDK compatível é @botozap/sdk@0.11.0 e a CLI é @botozap/cli@0.7.0. Os pacotes são preview público 0.x; fixe a versão para repetir a instalação.
Para o serviço hospedado, conecte seu cliente a https://mcp.botozap.com.br/mcp com sua chave no header Authorization: Bearer. A chave nunca vai na URL. A versão 0.2.5 do pacote npm tem uma dependência de workspace inválida; atualize para a versão fixada neste guia.
Catálogo de tools
Cada tool chama uma rota da API /v1 com a chave da sessão; o escopo exigido aparece na descrição da tool. São 121 tools de operação, agrupadas abaixo por recurso, e 149 do módulo de IA.
| Recurso | Tools |
|---|---|
| Mensagens | send_message, send_media_message, prepare_send_intent, list_messages, get_message |
| Conversas | list_conversations, get_conversation, update_conversation, reply_to_conversation, control_conversation_agent |
| Atribuições | list_conversation_assignments, create_conversation_assignment, get_conversation_assignment, update_conversation_assignment |
| Ferramentas da Inbox | get_inbox_tools, mutate_inbox_tools |
| Respostas salvas | list_saved_replies, get_saved_reply, create_saved_reply, update_saved_reply, delete_saved_reply |
| Contatos | list_contacts, get_contact, create_contact, update_contact, delete_contact |
| Etapas e campos de Contato | list_contact_stages, create_contact_stage, update_contact_stage, delete_contact_stage, reorder_contact_stages, list_contact_fields, create_contact_field, update_contact_field, delete_contact_field |
| Oportunidades | list_opportunities, get_opportunity, create_opportunity, update_opportunity, list_opportunity_activities, list_opportunity_conversations, link_opportunity_conversation, unlink_opportunity_conversation |
| Demandas | list_demands, get_demand, create_demand, update_demand, list_demand_activities, list_demand_conversations, link_demand_conversation, unlink_demand_conversation |
| Radar | list_radar, list_radar_stage_rules, configure_radar_stage_rule |
| Réguas | list_journeys, get_journey, create_journey, update_journey, archive_journey, control_journey, list_journey_runs, enroll_journey, get_journey_run, control_journey_run |
| Agenda | list_appointments, get_appointment, create_appointment, update_appointment, delete_appointment, list_appointment_history, get_appointment_availability, list_appointment_services, create_appointment_service, update_appointment_service, list_appointment_schedules, create_appointment_schedule, update_appointment_schedule, list_appointment_exceptions, create_appointment_exception, update_appointment_exception, delete_appointment_exception |
| Calendário | list_calendar_connections, disconnect_calendar, list_connection_calendars, refresh_connection_calendars, select_calendar, list_calendar_jobs, retry_calendar_job, resolve_calendar_conflict |
| Mídia | ingest_media |
| Clientes e links de setup | list_customers, get_customer, create_customer, update_customer, delete_customer, list_setup_links, create_setup_link, update_setup_link |
| Números | list_phone_numbers, get_phone_number, update_phone_number, phone_number_health |
| Contas de canal | list_channel_accounts, get_channel_account, list_comment_rules, get_comment_rule, create_comment_rule, update_comment_rule |
| Uso e custos | get_meta_costs |
| Templates | list_templates, get_template, create_template |
| Webhooks e Entregas | list_webhooks, get_webhook, create_webhook, update_webhook, delete_webhook, test_webhook, list_webhook_deliveries |
| Conta e auditoria | list_api_logs, list_users |
reply_to_conversation recebe só o id da Conversa e o texto; o BotoZap resolve Contato e Número e a API revalida janela de 24h, quota e billing. control_conversation_agent pausa ou retoma o agente de IA do BotoZap numa Conversa (action pause ou resume, escopo agents:write). Retomar pode gerar resposta automática ao Contato, então só chame com intenção explícita do usuário. create_webhook e update_webhook aceitam customer_id para limitar as entregas do Endpoint a um Cliente da Conta; em update_webhook, null remove o filtro.
prepare_send_intent devolve uma idempotency_key para send_message, send_media_message e reply_to_conversation: reutilize a mesma chave nos retries da mesma intenção e nunca gere outra depois de um timeout. Os recibos de envio seguem a API: no WhatsApp trazem wamid; no Instagram, wamid: null, channel: "instagram" e o mid da Meta em external_id. send_media_message aceita o IGSID em to (o Instagram não aceita legenda) e ingest_media é só do WhatsApp.
Em Contas de canal, list_channel_accounts lista Números do WhatsApp e Contas do Instagram (filtros channel e customer_id), e o bloco instagram traz a saúde da conexão em token_status (ok, expiring, expired, invalid, revoked ou unknown). As tools de Regras de comentário usam os escopos comment-rules:read e comment-rules:write; uma regra ativa dispara directs automáticos, e não há exclusão: desative com is_active: false. Esse grupo fica no catálogo completo, o de quem conecta com chave de API; conexões OAuth no perfil assistente (como o ChatGPT) não recebem essas tools.
As tools de IA seguem o padrão ai_<grupo>_<operação>, uma por operação do SDK (por exemplo, ai_agents_publish e ai_knowledge_search). Por padrão, leitura exige agents:read e escrita, agents:write. Conceitos, escopos e limites estão em Agente de IA. Como o processo MCP não lê arquivos do host, os uploads recebem o conteúdo em file_base64: ai_knowledge_upload aceita até 28 MiB em base64, o que corresponde a um arquivo de até 20 MiB; ai_skills_import_zip aceita até 7 MiB em base64, o que corresponde a um arquivo de até 5 MiB.
Ver as 149 tools de IA por grupo
ai_agents_*:list,get,create,save_draft,publish,restore,duplicate,operation,archive,preview,capability_usageai_credentials_*:list,get,create,update,revalidate,removeai_providers_*:get,save_binding,remove_binding,models,catalog,sync_catalogai_executions_*:list,get,decideai_knowledge_*:list,get,create,update,remove,reindex,chunks,search,upload,import_source,conversations,conversation_permission,conversation_preview,search_diagnostics,catalog_items,sync_catalog,citations,coverageai_memory_*:get,publish,set_enabled,versions,entries,create_entry,update_entry,archive_entry,approve_entry,checkpoints,entry_events,reactivate_entryai_skills_*:list,get,create,update,remove,versions,reference,import_zip,catalog,install,near_misses,decide_near_miss,compositionai_followup_flows_*:list,get,create,update,control,publish,versions,rollback,models,install_modelai_followups_*:list,get,enroll,control,decide_effect,resolve_effect,queue,promises,schedule_promise,get_promise,cancel_promise,resolve_promiseai_routers_*:list,get,create,update,archive,operation,test,activate_memberai_cases_*:list,get,messages,update,reply,consult,chat_history,eventsai_alerts_*:list,update,resolve_bulkai_notices_*:get,update,options,jobs,test,retry,diagnostics,effectai_proposals_*:list,decide,analyze,settings,save_settingsai_usage_*:get,budget,save_budget,rates,save_rate,repriceai_uploads_*:prepare,completeai_commercial_proposals_*:list,request,decide,jobs,settings,save_settingsai_eligibility_*:get,save_settings,channel,save_channel,authorizations,revoke_authorizationai_inferences_*:listai_operator_*:metrics,promisesai_evolution_*:getai_style_adjustments_*:list,save
O que o MCP não cobre
Não há tool para:
- transmissões (broadcasts).
- consentimentos do Contato (/contacts/:id/consents).
- leitura e download de mídia recebida (/media/:id).
- remoção de Número (DELETE /phone_numbers/:id).
Nesses casos, chame a API /v1 direto ou use request do SDK. A rotação do secret de um Endpoint também fica fora das tools: use webhooks.update do SDK, a CLI ou o PATCH da API. A leitura de Eventos não é uma tool: ela usa o resource descrito abaixo.
Matriz de compatibilidade
Leitura em 26/08/2026, nas versões indicadas. A primeira coluna registra se o cliente trata o sinal; a segunda exige releitura real do conteúdo, não apenas invalidar ou atualizar um catálogo. Nenhum dos hosts inspecionados transforma essa atualização em novo agent turn. Os links da tabela apontam as fontes consultadas. Esses resultados descrevem as versões indicadas; uma atualização do host precisa de nova validação.
| Cliente | Notification recebida | Resource relido | Novo agent turn | Evidência e uso recomendado |
|---|---|---|---|---|
| Codex | Não com o BotoZap | Não | Não | No rust-v0.150.0, o cliente não chama resources/subscribe. Se receber um update, apenas o registra; não relê nem abre outro turno. fonte oficial. |
| Claude Code | Não trata resources/updated | Não | Não | No 2.1.247, list_changed atualiza catálogos, mas resources/updated não é assinado nem tratado. Channels é outro mecanismo. docs oficiais. |
| Cursor | Não com o template atual | Não; atualiza só o catálogo | Não | No 3.17.8, o host assina URIs concretas listadas. O BotoZap expõe apenas um template não enumerável; mesmo após update, o host não faz resources/read nem abre turno. mecanismo de wake separado. |
| Cliente de referência | Sim, handler observado | Sim, chamada explícita no handler | Só se a aplicação enfileirar | O SDK recebe notifications/resources/updated; o código do consumidor relê o resource e decide se cria trabalho. teste de contrato. |
Para Codex, Claude Code e Cursor, configure as tools pelo quickstart de SDK, CLI e MCP do painel. Quando o seu host não comprovar as três colunas, use Endpoint durável para disparar o worker; para consultas manuais, use list_messages com cursor. A tool wait_for_message não faz parte do catálogo.
Quickstart 1 — tools e subscriptions por stdio
export BOTOZAP_API_KEY='bz_live_substitua_por_sua_chave'
pnpm dlx @botozap/mcp@0.9.0O processo fala MCP em stdout e deve permanecer vivo enquanto o host usa as tools. Os snippets completos para Codex, Claude Code e Cursor ficam na SDK, CLI e MCP, no painel; a chave entra por variável de ambiente e nunca deve ser gravada no repositório.
Como funciona a assinatura por stdio
O pacote anuncia resources.subscribe e emite notifications/resources/updated. Enquanto existe assinatura, o servidor consulta /events a cada 1,5 segundo; isso dispensa polling manual pelo agente, mas não elimina consultas periódicas. Unsubscribe ou encerramento da sessão cancela o tail. A chave precisa do escopo events:read no ambiente live ou Sandbox dos Eventos.
O resource botozap://events{?after,limit} lê o mesmo log de GET /v1/events e traz todos os tipos de Evento gravados para a Conta e o ambiente da chave, sem filtro por tipo:
- mensagens e status:
whatsapp.message.*; - conta e Número:
whatsapp.account.*ewhatsapp.phone_number.*; - Contato:
crm.contact.stage_changedecrm.contact.consent_changed; - CRM:
crm.opportunity.*ecrm.demand.*; - Agenda:
crm.appointment_createdecrm.appointment_updated; - réguas:
automation.journey_run.finished; - Instagram:
instagram.*, quando a Conta tem o canal.
O webhook.test não entra no stream. Filtre por type no consumidor. O catálogo com os payloads está em Catálogo de eventos. Por exemplo, crm.contact.stage_changed chega quando um Contato muda de etapa, o gatilho natural para o agente agir na negociação (Contato entrou em "Fechou" → envie o template de confirmação, crie o agendamento). O payload traz previous_stage/new_stage e changed_by (manual, auto ou system).
Quickstart 2 — operar seu servidor Streamable HTTP
BOTOZAP_MCP_TRANSPORT=streamable-http BOTOZAP_MCP_HOST=127.0.0.1 BOTOZAP_MCP_PORT=3001 BOTOZAP_EVENT_BUS_DATABASE_URL='postgresql://usuario:senha@host:5432/banco' pnpm dlx @botozap/mcp@0.9.0O exemplo inicia http://127.0.0.1:3001/mcp para o operador que tem acesso ao banco de Eventos. Para consumir o serviço BotoZap, use a URL hospedada acima; você não precisa de acesso ao PostgreSQL. Cada cliente envia sua chave em Authorization: Bearer; Conta e ambiente vêm dessa credencial. O servidor valida Hoste Origin antes da autenticação. Para bind público, configure BOTOZAP_MCP_ALLOWED_HOSTS, TLS e, quando o cliente enviar Origin, BOTOZAP_MCP_ALLOWED_ORIGINS. A conexão PostgreSQL deve preservar LISTEN/NOTIFY, usando conexão direta ou pooler em modo de sessão. O bus sinaliza eventos entre processos; um heartbeat de 15 segundos recupera sinais perdidos. A recuperação após reconexão usa o cursor de negócio BotoZap, não Last-Event-ID do transporte.
Quickstart 3 — subscription com cliente de referência
mkdir cliente-mcp-botozap && cd cliente-mcp-botozap
pnpm init
pnpm add @modelcontextprotocol/sdk@1.29.0
# salve o bloco abaixo como client.mjs
BOTOZAP_API_KEY='bz_live_substitua_por_sua_chave' BOTOZAP_MCP_URL='https://mcp.botozap.com.br/mcp' BOTOZAP_EVENT_CURSOR='0' node client.mjsimport { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { ResourceUpdatedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
const apiKey = process.env.BOTOZAP_API_KEY;
const mcpUrl = process.env.BOTOZAP_MCP_URL;
let cursor = process.env.BOTOZAP_EVENT_CURSOR ?? "0";
if (!apiKey || !mcpUrl) throw new Error("Defina BOTOZAP_API_KEY e BOTOZAP_MCP_URL");
const client = new Client({ name: "botozap-events", version: "1.0.0" });
const transport = new StreamableHTTPClientTransport(new URL(mcpUrl), {
requestInit: { headers: { Authorization: "Bearer " + apiKey } },
});
async function read(uri) {
const result = await client.readResource({ uri });
const content = result.contents[0];
if (!content || !("text" in content)) throw new Error("Resource sem JSON");
return JSON.parse(content.text);
}
async function drain(uri) {
do {
const page = await read(uri.replace(/after=\d+/, "after=" + cursor));
for (const event of page.data) {
// Em produção: dedupe durável por event.id e enqueue do worker.
console.log(JSON.stringify({ id: event.id, cursor: event.cursor, type: event.type }));
}
cursor = page.paging.cursor;
console.error("salve BOTOZAP_EVENT_CURSOR=" + cursor);
if (!page.paging.has_more) break;
} while (true);
}
await client.connect(transport);
await drain("botozap://events?after=" + cursor + "&limit=100"); // catch-up
const subscribedUri = "botozap://events?after=" + cursor + "&limit=100";
let pendingDrain = Promise.resolve();
client.setNotificationHandler(ResourceUpdatedNotificationSchema, ({ params }) => {
if (params.uri !== subscribedUri) return;
pendingDrain = pendingDrain.then(() => drain(subscribedUri)).catch(() => {
console.error("Falha na releitura; mantenha o cursor e reconecte para catch-up.");
});
});
await client.subscribeResource({ uri: subscribedUri }); // resources/subscribe
console.error("assinatura ativa; Ctrl+C encerra");
await new Promise(() => {});O handler serializa releituras, imprime somente identificadores do Evento e informa o novo paging.cursor em stderr. Em produção, persista esse cursor e deduplique por event.id antes de qualquer efeito. Substitua o console.log por enqueue durável; chamar o modelo diretamente dentro do handler torna replay, retry e deploy frágeis.
Cursor, replay e reconexão
- Carregue o último cursor confirmado; zero inicia desde o primeiro Evento retido.
- Leia
botozap://events?after=<cursor>&limit=100atépaging.has_moreficar falso. - Deduplique cada efeito por
event.ide só então salvepaging.cursor. - Assine uma nova URI com o cursor final. O probe inicial fecha a corrida entre catch-up e subscribe.
- Trate notifications como hints best-effort: elas podem repetir ou se perder. A garantia vem do stream autoritativo e do replay, não do sinal.
Se o runtime precisa iniciar mesmo com o host MCP fechado, complete o quickstart do Endpoint durável. Ele valida HMAC sobre bytes crus, persiste antes do 2xx, deduplica retries e acorda um worker separado. O MCP reduz latência enquanto conectado; o Endpoint e o outbox mantêm a garantia operacional.
Fontes e prova local
- Testes públicos do servidor — schemas, transportes e isolamento.
- Especificação MCP — resources e subscriptions.
- Cliente real por stdio — notification, leitura e replay.
- No checkout público
botozap-js, rodepnpm buildepnpm --filter @botozap/mcp test. Os testes do pacote usam fixtures locais e não enviam mensagens reais.
