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 turnEstado da release
O pacote publicado @botozap/[email protected] contém as tools por stdio, mas ainda não contém o resource de Eventos nem o transporte remoto. O release candidate @botozap/[email protected] está fixado no commit 4b7f678 e passou typecheck, testes, build, Changesets e instalação dos tarballs em projeto limpo no gate da issue #222, mas ainda não foi publicado no npm. Até a publicação, use a versão publicada para tools e o checkout fixado abaixo somente para avaliar push/replay em localhost. Não atribua subscriptions ao pacote 0.1.0.
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. Veja versões, hashes, comandos de inspeção e fontes primárias no relatório técnico da matriz.
| 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 da Central de Desenvolvedores. Quando o seu host não comprovar as três colunas, use Endpoint durável para disparar o worker; polling limitado fica apenas como fallback de desenvolvimento.
Quickstart 1 — stdio publicado (tools)
export BOTOZAP_API_KEY='bz_live_substitua_por_sua_chave'
pnpm dlx @botozap/[email protected]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 Central de Desenvolvedores; a chave entra por variável de ambiente e nunca deve ser gravada no repositório.
Quickstart 2 — release candidate por stdio
git clone https://github.com/bytecraft-fernando/botozap-js.git
cd botozap-js
git checkout 4b7f67820f27887a5d1af78ad0fe90c5eb51de9c
pnpm install --frozen-lockfile
pnpm build
BOTOZAP_API_KEY='bz_live_substitua_por_sua_chave' node packages/mcp/dist/index.jsEsse checkout exato anuncia resources.subscribe, registra o template botozap://events{?after,limit} e emite notifications/resources/updated. O tail stdio existe só enquanto o processo e a sessão continuam abertos; executar uma tool e encerrar o servidor elimina o canal de push. A chave precisa incluir o escopo events:read no mesmo ambiente live ou Sandbox dos Eventos.
Quickstart 3 — Streamable HTTP remoto
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' node packages/mcp/dist/index.jsO endpoint stateful fica em http://127.0.0.1:3001/mcp. A BotoZap ainda não oferece uma URL MCP remota hospedada. Mantenha o processo em localhost e coloque um proxy com TLS, autenticação e allowlist de Origin na frente dele; o preview não valida esse header sozinho. Cada cliente ainda envia a própria chave BotoZap em Authorization: Bearer; a Conta e o ambiente vêm dessa credencial, nunca da URI. A conexão PostgreSQL precisa preservar LISTEN/NOTIFY, portanto use conexão direta ou pooler em modo de sessão. Esse comando é para quem opera o servidor remoto, não para expor a URL local sem proteção. O preview não oferece replay do protocolo por Last-Event-ID; depois de reconectar, recupere os Eventos pelo cursor BotoZap como na seção seguinte.
Quickstart 4 — subscription com cliente de referência
mkdir cliente-mcp-botozap && cd cliente-mcp-botozap
pnpm init
pnpm add @modelcontextprotocol/[email protected]
# salve o bloco abaixo como client.mjs
BOTOZAP_API_KEY='bz_live_substitua_por_sua_chave' BOTOZAP_MCP_URL='http://127.0.0.1:3001/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));
});
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
- Pesquisa reproduzível — clientes, versões, hashes e limites de release.
- Especificação MCP — resources e subscriptions.
- Contrato BotoZap — stdio, Streamable HTTP, notification, isolamento e replay.
- No checkout do core, rode
pnpm exec vitest run tests/api-contract/sdk-mcp-events.test.tscom o Supabase local e o siblingbotozap-jsconstruído.
