Talk to a Tenzro Node via the A2A Protocol
Google's Agent-to-Agent (A2A) protocol is an open JSON-RPC + SSE standard for letting autonomous agents discover, invoke, and stream from each other. Every Tenzro node exposes an A2A server on port 3002 (live at a2a.tenzro.network) advertising 19 skills— wallet, identity, inference, AP2 payments, swarm orchestration, task marketplace, bridges, and more. This tutorial covers discovery, blocking send, polling, and streaming.
What You'll Build
- Discover a node's skills via
/.well-known/agent.json - Send a task with
message/sendand retrieve it withtasks/get - Stream partial artifacts over SSE via
/a2a/stream - Talk to the live testnet endpoint from TypeScript and Python
Endpoints
GET /.well-known/agent.json— Agent Card (discovery)POST /a2a— JSON-RPC 2.0 dispatcherPOST /a2a/stream— SSE streaming for task updates and artifacts
RPC Methods
message/send— create a new task from a user messagetasks/send— alias ofmessage/sendtasks/get— poll a task by IDtasks/list— list recent tasks for the sessiontasks/cancel— cancel an in-flight task
Step 1: Discover the Agent Card
GET https://a2a.tenzro.network/.well-known/agent.json
{
"name": "Tenzro Node",
"description": "Verifiable AI infrastructure — wallets, identity, payments, inference, bridges.",
"url": "https://a2a.tenzro.network",
"version": "0.1.0",
"capabilities": {
"streaming": true,
"pushNotifications": false,
"stateTransitionHistory": true
},
"defaultInputModes": ["application/json", "text/plain"],
"defaultOutputModes": ["application/json", "text/plain"],
"skills": [
{ "id": "wallet", "name": "Wallet Operations" },
{ "id": "identity", "name": "TDIP Identity" },
{ "id": "inference", "name": "Model Inference" },
{ "id": "settlement", "name": "Settlement" },
{ "id": "verification", "name": "ZK / TEE Verification" },
{ "id": "staking", "name": "Staking" },
{ "id": "token", "name": "Token Operations" },
{ "id": "contract", "name": "Contract Deployment" },
{ "id": "ap2-payments", "name": "AP2 Mandates" },
{ "id": "agent_spawning", "name": "Agent Spawning" },
{ "id": "swarm_orchestration", "name": "Swarm Orchestration" },
{ "id": "task_marketplace", "name": "Task Marketplace" },
{ "id": "agent_marketplace", "name": "Agent Templates" },
{ "id": "join", "name": "Network Participation" },
{ "id": "nft", "name": "NFT Operations" },
{ "id": "bridge", "name": "Cross-Chain Bridge" },
{ "id": "compliance", "name": "Compliance" },
{ "id": "crosschain", "name": "Cross-Chain Ops" },
{ "id": "events", "name": "Event Stream" }
]
}Step 2: Send a Task
POST https://a2a.tenzro.network/a2a
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": "1",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [
{ "type": "text", "text": "Transfer 10 TNZO to 0xRecipient..." }
]
}
}
}
-> {
"jsonrpc": "2.0",
"id": "1",
"result": {
"task": {
"id": "task-01HR...",
"state": "submitted",
"session": "sess-01HR..."
}
}
}Step 3: Poll for Completion
POST /a2a body: { "jsonrpc":"2.0","id":"2","method":"tasks/get","params":{ "taskId":"task-01HR..." } }
-> {
"result": {
"task": {
"id": "task-01HR...",
"state": "completed", // submitted | working | input_required | completed | failed | cancelled
"artifacts": [
{
"type": "application/json",
"data": { "tx_hash": "0x...", "status": "included" }
}
]
}
}
}Step 4: Stream via SSE
For long-running tasks (inference, cross-chain bridges, swarm orchestration), SSE is preferred — the server emits task.state and task.artifact events as they happen:
POST https://a2a.tenzro.network/a2a/stream
Accept: text/event-stream
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": "3",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{ "type": "text", "text": "Run inference on llama-3-70b: summarize today's news" }]
}
}
}
--- SSE stream ---
event: task.state
data: { "taskId":"task-01HR...","state":"working" }
event: task.artifact
data: { "taskId":"task-01HR...","artifact":{ "type":"text/plain","data":"partial token..." } }
event: task.artifact
data: { "taskId":"task-01HR...","artifact":{ "type":"text/plain","data":"another token..." } }
event: task.state
data: { "taskId":"task-01HR...","state":"completed" }Step 5: TypeScript Client (Blocking + Poll)
// Minimal A2A client — no SDK dependency.
const BASE = "https://a2a.tenzro.network";
// 1. Discover
const card = await fetch(BASE + "/.well-known/agent.json").then(r => r.json());
console.log("skills:", card.skills.map((s: any) => s.id));
// 2. Send a task (blocking)
const send = await fetch(BASE + "/a2a", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0", id: "1", method: "message/send",
params: { message: { role: "user", parts: [{ type: "text", text: "get node status" }] } },
}),
}).then(r => r.json());
const taskId = send.result.task.id;
// 3. Poll until done
while (true) {
const r = await fetch(BASE + "/a2a", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ jsonrpc:"2.0", id:"2", method:"tasks/get", params:{ taskId } }),
}).then(r => r.json());
if (["completed","failed","cancelled"].includes(r.result.task.state)) {
console.log(r.result.task.artifacts);
break;
}
await new Promise(r => setTimeout(r, 500));
}Step 6: TypeScript Client (SSE Streaming)
// Stream tokens as they arrive using EventSource-style SSE parsing.
const resp = await fetch("https://a2a.tenzro.network/a2a/stream", {
method: "POST",
headers: { "content-type": "application/json", accept: "text/event-stream" },
body: JSON.stringify({
jsonrpc: "2.0", id: "1", method: "message/send",
params: { message: { role: "user", parts: [{ type: "text", text: "generate a haiku" }] } },
}),
});
const reader = resp.body!.getReader();
const dec = new TextDecoder();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
const chunk = dec.decode(value);
for (const line of chunk.split("\n")) {
if (line.startsWith("data: ")) {
const evt = JSON.parse(line.slice(6));
if (evt.artifact?.type === "text/plain") process.stdout.write(evt.artifact.data);
}
}
}Step 7: Python Client
# Python A2A client using requests + sseclient-py
import requests
from sseclient import SSEClient
BASE = "https://a2a.tenzro.network"
# 1. Discover skills
card = requests.get(f"{BASE}/.well-known/agent.json").json()
print("available skills:", [s["id"] for s in card["skills"]])
# 2. Streaming inference
payload = {
"jsonrpc": "2.0", "id": "1", "method": "message/send",
"params": {"message": {"role": "user", "parts": [
{"type": "text", "text": "Summarize the Tenzro whitepaper"}
]}},
}
resp = requests.post(f"{BASE}/a2a/stream", json=payload, stream=True)
for event in SSEClient(resp).events():
print(event.event, event.data)A2A vs MCP. MCP is a tool-calling protocol — Claude or other LLMs pick specific tools to run. A2A is a peer-to-peer protocol where one agent delegates whole tasks to another and streams back artifacts. Tenzro runs both on every node (MCP on 3001, A2A on 3002) so you can compose: an LLM on your laptop calls a Tenzro MCP tool, which internally delegates to another Tenzro node via A2A.
What You Learned
- Agent Card — well-known discovery endpoint listing 19 skills
- JSON-RPC dispatcher —
message/send,tasks/get,tasks/list,tasks/cancel - SSE streaming —
task.stateandtask.artifactevents over/a2a/stream - Multi-language clients — plain
fetchin TS,requests+sseclientin Python
Next Steps
- See the Agent Swarm Orchestrator tutorial to compose many A2A agents
- See the Connect with Claude tutorial for MCP-side integration
- Read the A2A reference docs