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.
MCP client — calling external tools
Section titled “MCP client — calling external tools”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.
MCP server — exposing your agents
Section titled “MCP server — exposing your agents”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.
Human-in-the-loop approval
Section titled “Human-in-the-loop approval”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.
A2A — inbound tasks
Section titled “A2A — inbound tasks”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’sreceiver_id; the daemon dispatches the task to that agent and returns the result.
Event egress — outbound webhooks
Section titled “Event egress — outbound webhooks”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 headersOpt-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.
A2A vs. MCP vs. the HTTP execute endpoint
Section titled “A2A vs. MCP vs. the HTTP execute endpoint”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.
Examples
Section titled “Examples”examples/mcp-bridge— connecting to an external MCP server and calling its tools.examples/a2a-server— the inbound A2A surface end to end.examples/configs/event-webhooks.yaml— outbound event egress: event filter + HMAC signing.