# Agent Cards (/aiway/a2a/agent-cards)



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 [#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](/aiway/a2a/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 [#the-two-card-endpoints]

| Endpoint                                          | Returns                                       |
| ------------------------------------------------- | --------------------------------------------- |
| `GET /.well-known/agent-card.json`                | Platform card — the KubeMQ gateway's own card |
| `GET /a2a/{agent_id}/.well-known/agent-card.json` | Individual agent's enriched card              |

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

### Platform card [#platform-card]

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

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

### Individual agent card [#individual-agent-card]

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

```json
{
  "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 [#card-fields]

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

| Field                | JSON key             | Required | Description                                                                                                 |
| -------------------- | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| Agent ID             | `agent_id`           | Yes      | Unique identifier — 2–128 chars, lowercase alphanumeric with hyphens, starting and ending with alphanumeric |
| Name                 | `name`               | Yes      | Human-readable name (max 256 chars)                                                                         |
| Description          | `description`        | No       | Free-text description (max 2048 chars)                                                                      |
| Version              | `version`            | No       | Agent version, e.g. `1.0.0` (max 64 chars)                                                                  |
| URL                  | `url`                | Yes      | Agent endpoint — an absolute `http://` or `https://` URL (max 2048 chars)                                   |
| Skills               | `skills`             | No       | List of `AgentSkill` entries (see below)                                                                    |
| Capabilities         | `capabilities`       | No       | Free-form capability map                                                                                    |
| Default input modes  | `defaultInputModes`  | No       | e.g. `["text"]`                                                                                             |
| Default output modes | `defaultOutputModes` | No       | e.g. `["text"]`                                                                                             |
| Protocol versions    | `protocolVersions`   | No       | Defaults to `["1.0"]` if omitted                                                                            |
| Metadata             | `metadata`           | No       | Key-value string map                                                                                        |

<Callout type="info">
  `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.
</Callout>

### Skills and tags [#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.

| Field       | JSON key      | Required | Description                       |
| ----------- | ------------- | -------- | --------------------------------- |
| ID          | `id`          | Yes      | Skill identifier                  |
| Name        | `name`        | Yes      | Skill name                        |
| Description | `description` | No       | What the skill does               |
| Tags        | `tags`        | No       | Tags 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](/aiway/a2a/registry).

## Fetch a card [#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.

<Tabs groupId="language" items="['curl','C#','Go','Java','Python','TypeScript']">
  <Tab value="curl">
    ```bash
    # 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
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    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"]}");
    }
    ```
  </Tab>

  <Tab value="Go">
    ```go
    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"])
        }
    }
    ```
  </Tab>

  <Tab value="Java">
    ```java
    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"));
            }
        }
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    """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())
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    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);
    ```
  </Tab>
</Tabs>

<Callout type="info">
  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.
</Callout>

## Related [#related]

<Cards>
  <Card title="Agent registry" href="/aiway/a2a/registry" description="Register, list, heartbeat, and deregister agents — the source of every card." />

  <Card title="Building agents" href="/aiway/a2a/guides/building-agents" description="Implement a compliant HTTP agent and register it with a card." />

  <Card title="Reference" href="/aiway/a2a/reference" description="Full endpoint table, AgentCard / AgentSkill schema, and error codes." />
</Cards>
