Getting Started
Register an A2A agent and send your first message/send through the KubeMQ gateway in under ten minutes.
The A2A connector turns any HTTP server into a callable agent. You register an agent's
URL with the gateway, then route JSON-RPC message/send requests to it through
POST /a2a/<agent_id> — no KubeMQ SDK on the agent. This walkthrough takes you from a
running server to a verified round-trip.
Prerequisites
- A running kubemq-server with the shared HTTP server reachable on port 9090.
- An HTTP endpoint to register as your agent. For this guide, run one of the example
echo agents from
.kb/integration-a2a/examples(each agent registers itself on startup and echoes the request body back). curl(or one of the language clients below) to send the message.
Confirm the gateway is live by fetching the platform agent card:
curl http://localhost:9090/.well-known/agent-card.jsonEnable / disable
The A2A connector is enabled by default. Start kubemq-server and the /a2a/* and
/agents/* routes are live immediately — there is no =true flag to set.
To disable A2A, set its enable variable to false:
docker run -d -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY -e CONNECTORSA2_A_ENABLE=false europe-docker.pkg.dev/kubemq/images/kubemq:nextThe disable variable name is irregular by design — the config key Connectors.A2A.Enable
snake-cases to CONNECTORSA2_A_ENABLE, not KUBEMQ_A2A_ENABLE. See
Shared HTTP server for why, and never rely on the
older off-by-default behavior.
How it works
A message/send request travels from the caller to the gateway, across the broker to the
agent's virtual subscriber, out to the agent over HTTP, and back the same way — the
gateway relays the agent's JSON-RPC response to the caller unchanged.
The gateway proxies the request to the agent over the broker and relays the reply unchanged.
Steps
Register an agent
An agent registers itself by POSTing its agent card — including the absolute
url the gateway will call — to POST /agents/register. The example echo agents do
this on startup; the snippets below show the registration call.
curl -X POST http://localhost:9090/agents/register \
-H "Content-Type: application/json" \
-d '{
"agent_id": "echo-agent-01",
"name": "Echo Agent",
"description": "A simple echo agent for testing",
"version": "1.0.0",
"url": "http://localhost:18080/",
"skills": [
{"id": "echo", "name": "Echo", "description": "Echoes back the received message", "tags": ["test", "echo"]}
],
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"protocolVersions": ["1.0"]
}'var card = new JsonObject
{
["agent_id"] = "echo-agent-01",
["name"] = "Echo Agent",
["description"] = "A simple echo agent for testing",
["version"] = "1.0.0",
["url"] = "http://localhost:18080/",
["skills"] = new JsonArray(new JsonObject
{
["id"] = "echo", ["name"] = "Echo",
["description"] = "Echoes back the received message",
["tags"] = new JsonArray("test", "echo")
}),
["defaultInputModes"] = new JsonArray("text"),
["defaultOutputModes"] = new JsonArray("text"),
["protocolVersions"] = new JsonArray("1.0")
};
using var client = new HttpClient();
var resp = await client.PostAsync(
"http://localhost:9090/agents/register",
new StringContent(card.ToJsonString(), System.Text.Encoding.UTF8, "application/json"));
Console.WriteLine($"Registered: {(int)resp.StatusCode}");card := map[string]interface{}{
"agent_id": "echo-agent-01",
"name": "Echo Agent",
"description": "A simple echo agent for testing",
"version": "1.0.0",
"url": "http://localhost:18080/",
"skills": []map[string]interface{}{
{
"id": "echo",
"name": "Echo",
"description": "Echoes back the received message",
"tags": []string{"test", "echo"},
},
},
"defaultInputModes": []string{"text"},
"defaultOutputModes": []string{"text"},
"protocolVersions": []string{"1.0"},
}
data, _ := json.Marshal(card)
resp, err := http.Post("http://localhost:9090/agents/register", "application/json", bytes.NewReader(data))
if err != nil {
fmt.Fprintf(os.Stderr, "Registration failed: %v\n", err)
return
}
defer resp.Body.Close()
fmt.Printf("Registered: %d\n", resp.StatusCode)var card = Map.of(
"agent_id", "echo-agent-01",
"name", "Echo Agent",
"description", "A simple echo agent for testing",
"version", "1.0.0",
"url", "http://localhost:18080/",
"skills", List.of(Map.of(
"id", "echo", "name", "Echo",
"description", "Echoes back the received message",
"tags", List.of("test", "echo")
)),
"defaultInputModes", List.of("text"),
"defaultOutputModes", List.of("text"),
"protocolVersions", List.of("1.0")
);
var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:9090/agents/register"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(card)))
.build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println("Registered: " + resp.statusCode());card = {
"agent_id": "echo-agent-01",
"name": "Echo Agent",
"description": "A simple echo agent for testing",
"version": "1.0.0",
"url": "http://localhost:18080/",
"skills": [
{
"id": "echo",
"name": "Echo",
"description": "Echoes back the received message",
"tags": ["test", "echo"],
}
],
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"protocolVersions": ["1.0"],
}
async with httpx.AsyncClient() as client:
resp = await client.post("http://localhost:9090/agents/register", json=card)
print(f"Registered: {resp.status_code}")const card = {
agent_id: "echo-agent-01",
name: "Echo Agent",
description: "A simple echo agent for testing",
version: "1.0.0",
url: "http://localhost:18080/",
skills: [
{
id: "echo",
name: "Echo",
description: "Echoes back the received message",
tags: ["test", "echo"],
},
],
defaultInputModes: ["text"],
defaultOutputModes: ["text"],
protocolVersions: ["1.0"],
};
const resp = await fetch("http://localhost:9090/agents/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(card),
});
console.log("Registered:", resp.status);A 200 response means the agent is registered. The gateway returns the stored card
with server-managed registered_at and last_seen fields populated.
Send a message
Route a JSON-RPC 2.0 message/send request to the agent through
POST /a2a/<agent_id>. The gateway forwards it to the agent and relays the reply back.
curl -X POST http://localhost:9090/a2a/echo-agent-01 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"parts": [{"text": "Hello, agent!"}]
}
}
}'var payload = new JsonObject
{
["jsonrpc"] = "2.0",
["id"] = 1,
["method"] = "message/send",
["params"] = new JsonObject
{
["message"] = new JsonObject
{
["parts"] = new JsonArray(new JsonObject { ["text"] = "Hello, agent!" })
}
}
};
using var client = new HttpClient();
var resp = await client.PostAsync(
"http://localhost:9090/a2a/echo-agent-01",
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
Console.WriteLine($"Status: {(int)resp.StatusCode}");
var body = await resp.Content.ReadAsStringAsync();
var data = JsonNode.Parse(body)!;
Console.WriteLine(JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }));payload := map[string]interface{}{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": map[string]interface{}{
"message": map[string]interface{}{
"parts": []map[string]interface{}{{"text": "Hello, agent!"}},
},
},
}
data, _ := json.Marshal(payload)
resp, err := http.Post("http://localhost:9090/a2a/echo-agent-01", "application/json", bytes.NewReader(data))
if err != nil {
fmt.Fprintf(os.Stderr, "Request failed: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Status: %d\n", resp.StatusCode)
fmt.Println(string(body))var payload = Map.of(
"jsonrpc", "2.0",
"id", 1,
"method", "message/send",
"params", Map.of(
"message", Map.of(
"parts", List.of(Map.of("text", "Hello, agent!"))
)
)
);
var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:9090/a2a/echo-agent-01"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(payload)))
.build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + resp.statusCode());
var data = MAPPER.readTree(resp.body());
System.out.println(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(data));payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"parts": [{"text": "Hello, agent!"}],
},
},
}
async with httpx.AsyncClient() as client:
resp = await client.post("http://localhost:9090/a2a/echo-agent-01", json=payload)
print(f"Status: {resp.status_code}")
data = resp.json()
print(json.dumps(data, indent=2))
assert "result" in dataconst request = {
jsonrpc: "2.0",
id: 1,
method: "message/send",
params: {
message: {
parts: [{ text: "Hello, agent!" }],
},
},
};
const resp = await fetch("http://localhost:9090/a2a/echo-agent-01", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
});
const data = await resp.json();
console.log("Response:", JSON.stringify(data, null, 2));Verify the reply
The echo agent returns the full request body inside result.echo, confirming the
round-trip through the gateway:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"echo": {
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": { "parts": [{ "text": "Hello, agent!" }] }
}
}
}
}A response containing result is a successful round-trip. A response with an error
object instead means the gateway or agent reported a problem — see
Error handling for the codes and how to
distinguish transport failures from application errors.
What's next
Configuration
Tune timeouts, concurrency limits, TTL, and the agent response-size cap.
Synchronous messaging
Context IDs, custom methods, header forwarding, and concurrent requests.
Agent registry
Register, list, heartbeat, and deregister agents over the REST management API.
Building agents
Write a compliant HTTP agent server — no KubeMQ SDK required.
Was this page helpful?