Skip to content

Protocols — MCP, A2A, and event egress

Axocoatl agents don’t live in a walled garden. The daemon speaks MCP (Model Context Protocol) in both directions, accepts inbound A2A (Agent-to-Agent) tasks, and emits signed outbound event webhooks.

The daemon connects to every server you list under mcp_servers, over two transports:

  • stdio — a local child process the daemon spawns and talks to over stdin/stdout. Configured env vars (e.g. an API key) are layered onto the child’s environment.
  • streamable HTTP — a remote server addressed by URL.

On connect, the daemon discovers each server’s tools and registers them under qualified names (mcp__<server>__<tool>). From there an agent can call them: when the model invokes a discovered tool, the tool executor routes the call to the live client — the connection is kept alive after discovery — maps the qualified name back to the bare name the server registered, and round-trips the call. A server-reported tool error surfaces as a failed tool call rather than a silent success.

The same protocol runs the other way. axocoatl mcp serve bootstraps your agents and then speaks MCP over stdio, exposing each agent as an agent_<id> tool. Point any MCP client — Claude Desktop, another agent runtime — at the command, and it can list your agents and call them. A call to agent_<id> bridges straight to that agent’s execution and returns its output.

MCP tool calls can be gated behind human approval. When the gate has no prior decision for a call, it pauses, surfaces the pending approval to the dashboard over the WebSocket stream, and waits for a verdict before the tool runs.

Axocoatl exposes the standard A2A surface on its HTTP server:

  • GET /.well-known/agent.json — a discovery card describing this instance and listing its agents as capabilities.
  • POST /a2a/tasks — task intake. Address an agent with the task’s receiver_id; the daemon dispatches the task to that agent and returns the result.

A2A and the MCP server are inbound — the outside reaches in. Webhooks are the outbound counterpart: when the lattice publishes an event, Axocoatl POSTs a signed JSON payload to systems you own (Slack, PagerDuty, a CI pipeline). It’s the lattice’s outbound notification — opt-in and verifiable.

webhooks:
- name: deploy-alerts
url: https://hooks.example.com/axocoatl
events: [TaskCompleted, AgentFailed] # empty = all coordination events
secret: ${WEBHOOK_SECRET} # enables HMAC-SHA256 signing
headers:
Authorization: ${INTERNAL_TOKEN} # optional static headers

Opt-in. With no webhooks: configured the dispatcher never runs, so a default install makes zero outbound requests. axocoatl doctor and the startup log name exactly which hosts receive egress, so you always see what leaves the box.

Payload — a stable JSON schema:

{
"delivery_id": "", // unique per delivery; use it for idempotency
"event_id": "",
"event_type": "TaskCompleted", // the canonical event name
"produced_by": "researcher",
"timestamp": 1700000000,
"payload": { } // the event's own payload
}

Filtering. events lists the names to send (TaskCompleted, AgentFailed, or a Skill’s own custom event name). Empty means all coordination events — pure telemetry like AgentActivated is excluded unless you name it.

Signing. With a secret, every body is HMAC-SHA256 signed and the hex digest is sent in X-Axocoatl-Signature: sha256=… (alongside X-Axocoatl-Event, -Delivery, and -Timestamp). Recompute the HMAC on your side to verify the request is genuinely from your Axocoatl and untampered. Secrets and header values are held as secrets and never logged.

Delivery. At-least-once best-effort, in-process: transient failures (timeout, 5xx, 429) are retried with exponential backoff + jitter, honoring Retry-After; a 4xx is not retried. Deliveries are concurrency-bounded (dropped under a flood, never unbounded-spawned) and are not durable across a daemon restart — pair with a message broker if you need stronger guarantees.

Three different doors lead to “run this agent.” Which one you use depends on who’s calling:

| | Caller | Transport | Direction | |---|---|---|---| | A2A | Another agent system that speaks A2A | HTTP (/a2a/tasks) | Inbound | | MCP server | An MCP client (Claude Desktop, another runtime) | stdio (agent_<id> tool) | Inbound | | /api/agents/{id}/execute | Your own app or script | HTTP (the daemon’s own API) | Inbound |

All three end at the same place — the agent runs and returns its output. A2A and MCP are the standards-based front doors for other systems; the /api/agents/{id}/execute route is the daemon’s native API for your own integrations.