MCP para agentes: push, replay e compatibilidade

Use notifications para baixa latência, o stream com cursor para recuperar lacunas e um Endpoint quando você precisa acordar um worker com garantia durável.

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 turn

Versã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.

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.

RecursoTools
Mensagenssend_message, send_media_message, prepare_send_intent, list_messages, get_message
Conversaslist_conversations, get_conversation, update_conversation, reply_to_conversation, control_conversation_agent
Atribuiçõeslist_conversation_assignments, create_conversation_assignment, get_conversation_assignment, update_conversation_assignment
Ferramentas da Inboxget_inbox_tools, mutate_inbox_tools
Respostas salvaslist_saved_replies, get_saved_reply, create_saved_reply, update_saved_reply, delete_saved_reply
Contatoslist_contacts, get_contact, create_contact, update_contact, delete_contact
Etapas e campos de Contatolist_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
Oportunidadeslist_opportunities, get_opportunity, create_opportunity, update_opportunity, list_opportunity_activities, list_opportunity_conversations, link_opportunity_conversation, unlink_opportunity_conversation
Demandaslist_demands, get_demand, create_demand, update_demand, list_demand_activities, list_demand_conversations, link_demand_conversation, unlink_demand_conversation
Radarlist_radar, list_radar_stage_rules, configure_radar_stage_rule
Réguaslist_journeys, get_journey, create_journey, update_journey, archive_journey, control_journey, list_journey_runs, enroll_journey, get_journey_run, control_journey_run
Agendalist_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áriolist_calendar_connections, disconnect_calendar, list_connection_calendars, refresh_connection_calendars, select_calendar, list_calendar_jobs, retry_calendar_job, resolve_calendar_conflict
Mídiaingest_media
Clientes e links de setuplist_customers, get_customer, create_customer, update_customer, delete_customer, list_setup_links, create_setup_link, update_setup_link
Númeroslist_phone_numbers, get_phone_number, update_phone_number, phone_number_health
Contas de canallist_channel_accounts, get_channel_account, list_comment_rules, get_comment_rule, create_comment_rule, update_comment_rule
Uso e custosget_meta_costs
Templateslist_templates, get_template, create_template
Webhooks e Entregaslist_webhooks, get_webhook, create_webhook, update_webhook, delete_webhook, test_webhook, list_webhook_deliveries
Conta e auditorialist_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_usage
  • ai_credentials_*: list, get, create, update, revalidate, remove
  • ai_providers_*: get, save_binding, remove_binding, models, catalog, sync_catalog
  • ai_executions_*: list, get, decide
  • ai_knowledge_*: list, get, create, update, remove, reindex, chunks, search, upload, import_source, conversations, conversation_permission, conversation_preview, search_diagnostics, catalog_items, sync_catalog, citations, coverage
  • ai_memory_*: get, publish, set_enabled, versions, entries, create_entry, update_entry, archive_entry, approve_entry, checkpoints, entry_events, reactivate_entry
  • ai_skills_*: list, get, create, update, remove, versions, reference, import_zip, catalog, install, near_misses, decide_near_miss, composition
  • ai_followup_flows_*: list, get, create, update, control, publish, versions, rollback, models, install_model
  • ai_followups_*: list, get, enroll, control, decide_effect, resolve_effect, queue, promises, schedule_promise, get_promise, cancel_promise, resolve_promise
  • ai_routers_*: list, get, create, update, archive, operation, test, activate_member
  • ai_cases_*: list, get, messages, update, reply, consult, chat_history, events
  • ai_alerts_*: list, update, resolve_bulk
  • ai_notices_*: get, update, options, jobs, test, retry, diagnostics, effect
  • ai_proposals_*: list, decide, analyze, settings, save_settings
  • ai_usage_*: get, budget, save_budget, rates, save_rate, reprice
  • ai_uploads_*: prepare, complete
  • ai_commercial_proposals_*: list, request, decide, jobs, settings, save_settings
  • ai_eligibility_*: get, save_settings, channel, save_channel, authorizations, revoke_authorization
  • ai_inferences_*: list
  • ai_operator_*: metrics, promises
  • ai_evolution_*: get
  • ai_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.

ClienteNotification recebidaResource relidoNovo agent turnEvidência e uso recomendado
CodexNão com o BotoZapNãoNãoNo 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 CodeNão trata resources/updatedNãoNãoNo 2.1.247, list_changed atualiza catálogos, mas resources/updated não é assinado nem tratado. Channels é outro mecanismo. docs oficiais.
CursorNão com o template atualNão; atualiza só o catálogoNãoNo 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ênciaSim, handler observadoSim, chamada explícita no handlerSó se a aplicação enfileirarO 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.0

O 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.* e whatsapp.phone_number.*;
  • Contato: crm.contact.stage_changed e crm.contact.consent_changed;
  • CRM: crm.opportunity.* e crm.demand.*;
  • Agenda: crm.appointment_created e crm.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.0

O 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.mjs
import { 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

  1. Carregue o último cursor confirmado; zero inicia desde o primeiro Evento retido.
  2. Leia botozap://events?after=<cursor>&limit=100 até paging.has_more ficar falso.
  3. Deduplique cada efeito por event.id e só então salve paging.cursor.
  4. Assine uma nova URI com o cursor final. O probe inicial fecha a corrida entre catch-up e subscribe.
  5. 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