47. Agent Protocols and Interoperability
Protocols standardize edges. They do not grant trust, authority, or shared truth.
On O-1001, Intake loads order facts through MCP tools. Resolution and Policy write proposals and verdicts to the ledger — not to each other over ad-hoc JSON-RPC. Someone proposes A2A between Resolution and Policy inside one deploy. That is coordination tax with no isolation benefit. Meanwhile Outreach nearly sends because an MCP tool server accepted a prompt-shaped argument and nothing in Policy checked send-window rules or required fields.
MCP → external tools | ledger → Intake/Resolution/Policy inside trust boundary | A2A → foreign org agents | Policy + authz on every consequential path
The failure mode is protocol substitution for authz: acronyms on the diagram, no permit/deny on the consequential path.
This chapter adds where each wire belongs — MCP for tools, ledger for in-boundary coordination, A2A for cross-org tasks — without confusing interoperability with authorization.
First principles
Keep three layers distinct:
| Layer | Job | ShopOps home |
|---|---|---|
| MCP (Model Context Protocol) | Agent ↔ tools/resources: discover, call, typed args | OMS/CRM, balances, policy check APIs, channel gateways |
| A2A (Agent2Agent) | Agent ↔ agent across org/trust boundaries: tasks, artifacts, capability cards | External fraud-check or carrier agent; sister-company logistics agent |
| Ledger | Shared facts / conflicts inside your trust boundary | Resolution vs Policy beliefs on case O-1001 |
Rules of thumb:
- MCP for every system your worker calls as a tool.
- Ledger for coordination among your own modules/agents on one case.
- A2A when another organization’s agent must accept a task and return an artifact — and you still record that task on your ledger for audit.
Interoperability ≠ authorization. An MCP server that exposes send_email still needs your policy engine, authz, and idempotency (Ch 5–6, 27–28). A2A capability cards are advertisements, not proofs of safety.
Ch 35 (cost/latency) already covered KV/prefix cache — do not re-teach serving math here. This chapter is about wires and trust boundaries.
Concrete example
Case O-1001:
- Worker calls MCP tool
get_orderon the accounts server. - Resolution and Policy write proposals/verdicts to the ledger (not MCP, not A2A).
- Address / fraud verification is outsourced: orchestrator opens an A2A task to an external carrier verification agent; result artifact returns; worker appends
ADDRESS_VERIFIEDto ledger. - Outreach sends only after policy + approval — via MCP
draft_email/send_emailtools with scopes.
If you replaced step 2 with A2A between Resolution and Policy inside one process boundary, you paid coordination tax for no isolation benefit.
Diagram
flowchart TB
subgraph trust_boundary [Your trust boundary]
W[Case worker]
L[(Ledger)]
P[Policy / authz]
W --> L
W --> P
end
W -->|MCP tools| CRM[CRM MCP server]
W -->|MCP tools| CH[Channels MCP server]
W -->|A2A task| EXT[External bureau agent]
EXT -->|artifact| W
EXT -.->|must also land as| L
Caption: MCP down to tools; A2A out to foreign agents; ledger for shared truth inside. Protocols do not replace P.
Layer cake (ASCII)
┌─────────────────────────────────────────────┐
│ Orchestration / HITL / eval │
├─────────────────────────────────────────────┤
│ Ledger (facts, conflicts, audit events) │
├─────────────────────────────────────────────┤
│ A2A (cross-org tasks / artifacts) │
├─────────────────────────────────────────────┤
│ MCP (tools, resources, prompts) │
├─────────────────────────────────────────────┤
│ HTTP / DB / queues / model gateway │
└─────────────────────────────────────────────┘
Implementation
Minimal MCP-shaped stub over ShopOps tools (stdio JSON-RPC teaching server — not a full SDK):
# manuscript/code/shopops/shopops/mcp_server.py
"""MCP-shaped JSON-RPC stub for Chapter 47.
This is a teaching surface: list tools + call tools.
It does NOT implement authn, sampling, or roots.
Policy/authz remain outside — call into your executor.
"""
from __future__ import annotations
import json
import sys
from typing import Any, Callable
ToolHandler = Callable[[dict[str, Any]], dict[str, Any]]
TOOLS: dict[str, dict[str, Any]] = {
"get_order": {
"name": "get_order",
"description": "Return fictional order total for a case",
"inputSchema": {
"type": "object",
"properties": {"case_id": {"type": "string"}},
"required": ["case_id"],
},
},
"draft_email": {
"name": "draft_email",
"description": "Draft email body; does not send",
"inputSchema": {
"type": "object",
"properties": {
"case_id": {"type": "string"},
"body": {"type": "string"},
},
"required": ["case_id", "body"],
},
},
}
def _get_order(args: dict[str, Any]) -> dict[str, Any]:
# Fictional accounts only
return {"case_id": args["case_id"], "order_total_cents": 54000, "currency": "USD"}
def _draft_email(args: dict[str, Any]) -> dict[str, Any]:
return {"case_id": args["case_id"], "draft": args["body"], "status": "drafted"}
HANDLERS: dict[str, ToolHandler] = {
"get_order": _get_order,
"draft_email": _draft_email,
}
def handle(msg: dict[str, Any]) -> dict[str, Any]:
mid = msg.get("id")
method = msg.get("method")
params = msg.get("params") or {}
if method == "initialize":
return {
"jsonrpc": "2.0",
"id": mid,
"result": {
"protocolVersion": "2024-11-05",
"serverInfo": {"name": "shopops-mcp-stub", "version": "0.1.0"},
"capabilities": {"tools": {}},
},
}
if method == "tools/list":
return {
"jsonrpc": "2.0",
"id": mid,
"result": {"tools": list(TOOLS.values())},
}
if method == "tools/call":
name = params.get("name")
args = params.get("arguments") or {}
if name not in HANDLERS:
return {
"jsonrpc": "2.0",
"id": mid,
"error": {"code": -32601, "message": f"unknown tool: {name}", "data": {"known": list(HANDLERS)}},
}
# NOTE: production must validate schema + authz + policy before handler
result = HANDLERS[name](args)
return {
"jsonrpc": "2.0",
"id": mid,
"result": {"content": [{"type": "text", "text": json.dumps(result)}], "isError": False},
}
return {
"jsonrpc": "2.0",
"id": mid,
"error": {"code": -32601, "message": f"method not found: {method}"},
}
def main() -> None:
for line in sys.stdin:
line = line.strip()
if not line:
continue
msg = json.loads(line)
sys.stdout.write(json.dumps(handle(msg)) + "\n")
sys.stdout.flush()
if __name__ == "__main__":
main()
A2A mapping (conceptual): only org-boundary tasks — e.g. VerifyIncomeTask{case_id, evidence_request_id} → artifact IncomeArtifact{verified: bool, provider_ref}. On receipt, append ledger event; never let the foreign agent call your send_email tool directly.
Failure modes
| Failure | Symptom | Fix |
|---|---|---|
| MCP as security | Tools callable without authz | Sidecar policy; least-privilege tokens |
| A2A inside monolith | Chatty task theater | Use ledger / function calls |
| Ledger over HTTP tools | Everything becomes an event | Tools for I/O; ledger for beliefs |
| Schema drift | Silent arg coercion | Version tools; fail closed |
| Sampling-in-tool | Nested unbudgeted agent | Disable or treat as full agent |
| Trusting capability cards | Foreign agent over-scoped | Separate credentials; allowlists |
Production considerations
- One MCP server per trust tier (read CRM ≠ send channel).
- Pin protocol and tool schema versions in traces.
- Treat tool output as untrusted content (Ch 26).
- Adopt MCP first; add A2A when a second independent org agent exists — not before.
- Official docs evolve quickly — verify field names against current specs before production. [VERIFY]
Chapter summary
- MCP standardizes tool edges; A2A standardizes cross-agent tasks; ledger stores shared facts inside your boundary.
- Interoperability does not include trust, authz, or policy.
- Prefer ledger over A2A for co-owned agents on one case.
- Teaching MCP stub lists/calls tools; policy stays in your executor.
- Record foreign A2A artifacts on the ledger or audit stops at the protocol edge.
Exercises
- Mechanical: Pipe an
initialize+tools/list+tools/callget_order forO-1001intomcp_server.py. - Boundary: Redesign a mistaken A2A link between Resolution and Policy as ledger events.
- Security: Add a fake
send_emailtool to the stub; show where authz must block beforeHANDLERS. - Design: Write a one-page integration guide for a bureau A2A task including ledger event types.
References
- Model Context Protocol — official documentation. https://modelcontextprotocol.io [VERIFY]
- Agent2Agent (A2A) — official specification / docs. [VERIFY URL]
- Lamport, 1978 — event ordering intuition for ledgers.
- Kleppmann — logs as systems of record.
- Cross-links: Ch 4–5 (tools), Ch 16–17 (ledger/conflicts), Ch 26–28 (trust/policy), Ch 35 (serving — separate concern).