KubeMQ
AiwayAI Agents (A2A)

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.json

Enable / 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:next

The 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 data
const 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

Was this page helpful?

On this page