KubeMQ
AiwayAI Agents (A2A)

Agent Cards

Discover agent capabilities through A2A agent cards served at well-known endpoints — platform card, individual card, skills, and tags.

An agent card is the machine-readable description of an agent's identity and capabilities. KubeMQ serves agent cards at the standard A2A /.well-known/agent-card.json endpoints so callers can discover what an agent does before sending it a request.

Overview

Following the A2A protocol convention, KubeMQ exposes two kinds of card:

  • Platform card — describes the KubeMQ gateway itself. The name is always kubemq. It carries no agent-specific skills; it advertises the gateway and the protocol versions it speaks.
  • Individual card — describes one registered agent. It is the AgentCard you supplied at registration, enriched with the server-managed registered_at and last_seen timestamps. This is the same representation returned by GET /agents/{agent_id}.

Cards are read-only discovery surfaces. You create and update them through the registry (register / heartbeat); the card endpoints just expose the current state. Both card endpoints are public — no JWT is required to read them, since discovery must work before a caller authenticates.

The two card endpoints

EndpointReturns
GET /.well-known/agent-card.jsonPlatform card — the KubeMQ gateway's own card
GET /a2a/{agent_id}/.well-known/agent-card.jsonIndividual agent's enriched card

Requesting a well-known card for a non-existent agent returns HTTP 404.

Platform card

The platform card represents the gateway, not any agent. Its name is always kubemq:

{
  "name": "kubemq",
  "description": "KubeMQ A2A Gateway",
  "version": "latest",
  "url": "http://localhost:9090/",
  "skills": [],
  "defaultInputModes": ["text"],
  "defaultOutputModes": ["text"],
  "protocolVersions": ["1.0"]
}

Individual agent card

An individual card returns the agent's registered fields plus the server-managed timestamps:

{
  "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"],
  "registered_at": "2026-04-06T10:00:00Z",
  "last_seen": "2026-04-06T10:05:00Z"
}

Card fields

The AgentCard carries the agent's identity, endpoint, and capabilities. The required fields are agent_id, name, and url.

FieldJSON keyRequiredDescription
Agent IDagent_idYesUnique identifier — 2–128 chars, lowercase alphanumeric with hyphens, starting and ending with alphanumeric
NamenameYesHuman-readable name (max 256 chars)
DescriptiondescriptionNoFree-text description (max 2048 chars)
VersionversionNoAgent version, e.g. 1.0.0 (max 64 chars)
URLurlYesAgent endpoint — an absolute http:// or https:// URL (max 2048 chars)
SkillsskillsNoList of AgentSkill entries (see below)
CapabilitiescapabilitiesNoFree-form capability map
Default input modesdefaultInputModesNoe.g. ["text"]
Default output modesdefaultOutputModesNoe.g. ["text"]
Protocol versionsprotocolVersionsNoDefaults to ["1.0"] if omitted
MetadatametadataNoKey-value string map

registered_at and last_seen are server-managed — KubeMQ sets them on registration and heartbeat. Do not send them when registering; they will be overwritten.

Skills and tags

Each entry in skills is an AgentSkill. Skills make agents discoverable: the registry filters by skill tags when listing agents, so well-chosen tags let callers find the right agent without knowing its ID.

FieldJSON keyRequiredDescription
IDidYesSkill identifier
NamenameYesSkill name
DescriptiondescriptionNoWhat the skill does
TagstagsNoTags used for discovery filtering

To find every agent advertising a tag, pass it to the list endpoint — GET /agents?skill_tags=echo — see the registry.

Fetch a card

The example below reads an individual agent's card. The curl tab hits the standard well-known endpoint; the language tabs fetch the same card representation via the registry API and print every field.

# Platform card (the gateway itself)
curl http://localhost:9090/.well-known/agent-card.json

# Individual agent card
curl http://localhost:9090/a2a/echo-agent-01/.well-known/agent-card.json
using System.Text.Json.Nodes;

const string KubeMqUrl = "http://localhost:9090";
const string AgentId = "full-info-agent-01";

using var client = new HttpClient();
var resp = await client.GetAsync($"{KubeMqUrl}/agents/{AgentId}");
Console.WriteLine($"Status: {(int)resp.StatusCode}");

var body = await resp.Content.ReadAsStringAsync();
var data = JsonNode.Parse(body)!;

Console.WriteLine("\n--- Agent Card ---");
Console.WriteLine($"  agent_id:           {data["agent_id"]}");
Console.WriteLine($"  name:               {data["name"]}");
Console.WriteLine($"  description:        {data["description"]}");
Console.WriteLine($"  version:            {data["version"]}");
Console.WriteLine($"  url:                {data["url"]}");
Console.WriteLine($"  defaultInputModes:  {data["defaultInputModes"]}");
Console.WriteLine($"  defaultOutputModes: {data["defaultOutputModes"]}");
Console.WriteLine($"  protocolVersions:   {data["protocolVersions"]}");
Console.WriteLine($"  registered_at:      {data["registered_at"]}");
Console.WriteLine($"  last_seen:          {data["last_seen"]}");

var skills = data["skills"]?.AsArray() ?? [];
Console.WriteLine($"\n--- Skills ({skills.Count}) ---");
foreach (var skill in skills)
{
    Console.WriteLine($"  [{skill!["id"]}] {skill["name"]}: {skill["description"]}");
    Console.WriteLine($"    tags: {skill["tags"]}");
}
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
)

const (
    kubemqURL = "http://localhost:9090"
    agentID   = "full-info-agent-01"
)

func main() {
    resp, err := http.Get(kubemqURL + "/agents/" + agentID)
    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)

    var data map[string]interface{}
    json.Unmarshal(body, &data)

    fmt.Println("\n--- Agent Card ---")
    fmt.Printf("  agent_id:           %v\n", data["agent_id"])
    fmt.Printf("  name:               %v\n", data["name"])
    fmt.Printf("  description:        %v\n", data["description"])
    fmt.Printf("  version:            %v\n", data["version"])
    fmt.Printf("  url:                %v\n", data["url"])
    fmt.Printf("  defaultInputModes:  %v\n", data["defaultInputModes"])
    fmt.Printf("  defaultOutputModes: %v\n", data["defaultOutputModes"])
    fmt.Printf("  protocolVersions:   %v\n", data["protocolVersions"])
    fmt.Printf("  registered_at:      %v\n", data["registered_at"])
    fmt.Printf("  last_seen:          %v\n", data["last_seen"])

    skills, _ := data["skills"].([]interface{})
    fmt.Printf("\n--- Skills (%d) ---\n", len(skills))
    for _, s := range skills {
        sk, _ := s.(map[string]interface{})
        fmt.Printf("  [%v] %v: %v\n", sk["id"], sk["name"], sk["description"])
        fmt.Printf("    tags: %v\n", sk["tags"])
    }
}
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Client {

    static final String KUBEMQ_URL = "http://localhost:9090";
    static final String AGENT_ID = "full-info-agent-01";
    static final ObjectMapper MAPPER = new ObjectMapper();

    public static void main(String[] args) throws Exception {
        var client = HttpClient.newHttpClient();
        var req = HttpRequest.newBuilder()
            .uri(URI.create(KUBEMQ_URL + "/agents/" + AGENT_ID))
            .GET().build();
        var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        System.out.println("Status: " + resp.statusCode());

        var data = MAPPER.readTree(resp.body());
        System.out.println("\n--- Agent Card ---");
        System.out.println("  agent_id:           " + data.path("agent_id").asText());
        System.out.println("  name:               " + data.path("name").asText());
        System.out.println("  description:        " + data.path("description").asText());
        System.out.println("  version:            " + data.path("version").asText());
        System.out.println("  url:                " + data.path("url").asText());
        System.out.println("  defaultInputModes:  " + data.path("defaultInputModes"));
        System.out.println("  defaultOutputModes: " + data.path("defaultOutputModes"));
        System.out.println("  protocolVersions:   " + data.path("protocolVersions"));
        System.out.println("  registered_at:      " + data.path("registered_at").asText());
        System.out.println("  last_seen:          " + data.path("last_seen").asText());

        var skills = data.path("skills");
        System.out.println("\n--- Skills (" + skills.size() + ") ---");
        for (var skill : skills) {
            System.out.println("  [" + skill.get("id").asText() + "] "
                + skill.get("name").asText() + ": " + skill.get("description").asText());
            System.out.println("    tags: " + skill.get("tags"));
        }
    }
}
"""Agent-Info example — retrieves and displays all agent card fields."""

import asyncio

import httpx

KUBEMQ_URL = "http://localhost:9090"
AGENT_ID = "full-info-agent-01"


async def main() -> None:
    async with httpx.AsyncClient() as client:
        resp = await client.get(f"{KUBEMQ_URL}/agents/{AGENT_ID}")
        print(f"Status: {resp.status_code}")
        data = resp.json()

        print("\n--- Agent Card ---")
        print(f"  agent_id:           {data.get('agent_id')}")
        print(f"  name:               {data.get('name')}")
        print(f"  description:        {data.get('description')}")
        print(f"  version:            {data.get('version')}")
        print(f"  url:                {data.get('url')}")
        print(f"  defaultInputModes:  {data.get('defaultInputModes')}")
        print(f"  defaultOutputModes: {data.get('defaultOutputModes')}")
        print(f"  protocolVersions:   {data.get('protocolVersions')}")
        print(f"  registered_at:      {data.get('registered_at')}")
        print(f"  last_seen:          {data.get('last_seen')}")

        skills = data.get("skills", [])
        print(f"\n--- Skills ({len(skills)}) ---")
        for skill in skills:
            print(f"  [{skill['id']}] {skill['name']}: {skill['description']}")
            print(f"    tags: {skill.get('tags', [])}")


if __name__ == "__main__":
    asyncio.run(main())
const KUBEMQ_URL = "http://localhost:9090";
const AGENT_ID = "info-agent-01";

async function main() {
  const resp = await fetch(`${KUBEMQ_URL}/agents/${AGENT_ID}`);
  const agent = await resp.json();

  console.log("=== Agent Card Details ===");
  console.log(`  agent_id:          ${agent.agent_id}`);
  console.log(`  name:              ${agent.name}`);
  console.log(`  description:       ${agent.description}`);
  console.log(`  version:           ${agent.version}`);
  console.log(`  url:               ${agent.url}`);
  console.log(`  registered_at:     ${agent.registered_at}`);
  console.log(`  last_seen:         ${agent.last_seen}`);
  console.log(`  protocolVersions:  ${JSON.stringify(agent.protocolVersions)}`);
  console.log(`  defaultInputModes: ${JSON.stringify(agent.defaultInputModes)}`);
  console.log(`  defaultOutputModes:${JSON.stringify(agent.defaultOutputModes)}`);

  console.log("\n=== Skills ===");
  for (const skill of agent.skills || []) {
    console.log(`  - ${skill.id}: ${skill.name} (tags: ${(skill.tags || []).join(", ")})`);
    console.log(`    ${skill.description}`);
  }

  console.log("\n=== Full JSON ===");
  console.log(JSON.stringify(agent, null, 2));
}

main().catch(console.error);

The curl tab uses the public /.well-known/agent-card.json endpoint; the language tabs read the same card via GET /agents/{agent_id} (the registry API), which returns an identical representation. Both surfaces return the enriched card.

Was this page helpful?

On this page