Skip to content

build_agent_card

Python · package toolnexus · SPEC §7B · python/src/toolnexus/serve.py

def build_agent_card(
cfg: dict[str, Any],
skills: list[SkillInfo],
url: str,
) -> dict[str, Any]

Builds the JSON body served at GET /.well-known/agent-card.json — a peer’s first call to any A2A agent. Skills come from the toolkit’s SkillSource (SKILL.md name + description), never raw tools, filtered to cfg["skills"] when given.

  • You want to see or test exactly what card a given a2a config + skill set would publish, without spinning up a server.
  • You are composing your own HTTP layer instead of Toolkit.serve and need the card JSON to answer the well-known path yourself.
  • You are debugging why a peer’s agent_tools call sees the wrong skills, name, or url — this is the one function that decides all three.

The function is pure: no I/O, no server, no store. skills must already be resolved (a toolkit’s SkillSource.skills.values(), or hand-built SkillInfos for a test) — build_agent_card never discovers skills itself.

1. The smallest useful call — defaults with no skills

Section titled “1. The smallest useful call — defaults with no skills”
from toolnexus.serve import build_agent_card
card = build_agent_card({}, [], "http://127.0.0.1:8080/")
assert card["name"] == "toolnexus-agent"
assert card["description"] == ""
assert card["version"] == "0.1.0"
assert card["protocolVersion"] == "0.3.0"
assert card["capabilities"] == {"streaming": False, "pushNotifications": False}
assert card["defaultInputModes"] == ["text"]
assert card["defaultOutputModes"] == ["text"]
assert card["skills"] == []
assert card["url"] == "http://127.0.0.1:8080/"
assert "provider" not in card # omitted entirely when not configured
print("ok:", card["name"], card["protocolVersion"])

2. The realistic case — skills advertised, never raw tools

Section titled “2. The realistic case — skills advertised, never raw tools”

skills mirrors a SkillSource’s inventory — id and name both come from the skill’s own name.

from toolnexus.skill import SkillInfo
from toolnexus.serve import build_agent_card
skills = [
SkillInfo(name="hello-world", location="/skills/hello-world/SKILL.md",
content="...", description="Greets the caller by name."),
SkillInfo(name="transcribe", location="/skills/transcribe/SKILL.md",
content="...", description="Transcribes an audio file."),
]
card = build_agent_card(
{"name": "video-desk", "description": "Handles video intake."},
skills,
"http://127.0.0.1:8080/",
)
assert card["name"] == "video-desk"
assert card["description"] == "Handles video intake."
ids = [s["id"] for s in card["skills"]]
assert ids == ["hello-world", "transcribe"]
assert card["skills"][0] == {
"id": "hello-world", "name": "hello-world", "description": "Greets the caller by name."
}
print("ok:", card["name"], "->", ids)

3. The full surface — filtered skills, a provider block, custom version

Section titled “3. The full surface — filtered skills, a provider block, custom version”

cfg["skills"] restricts the advertised set (a toolkit may host more skills than it wants a given peer surface to see). provider appears only when configured.

from toolnexus.skill import SkillInfo
from toolnexus.serve import build_agent_card
skills = [
SkillInfo(name="hello-world", location="/skills/hello-world/SKILL.md", content="..."),
SkillInfo(name="internal-only", location="/skills/internal-only/SKILL.md", content="..."),
]
card = build_agent_card(
{
"name": "video-desk",
"version": "2.3.1",
"skills": ["hello-world"], # "internal-only" is excluded from the card
"provider": {"organization": "deemwar", "url": "https://deemwar.com"},
},
skills,
"http://127.0.0.1:8080/",
)
assert [s["id"] for s in card["skills"]] == ["hello-world"]
assert card["version"] == "2.3.1"
assert card["provider"] == {"organization": "deemwar", "url": "https://deemwar.com"}
# A skill with no description still gets a card entry — description defaults to "".
assert card["skills"][0]["description"] == ""
print("ok:", card["version"], card["provider"]["organization"])
Parameter Type What it does
cfg dict The A2AConfigname?, description?, version?, provider?, skills?: list[str], store? (unused here).
skills list[SkillInfo] The toolkit’s resolved skill inventory. Filtered to cfg["skills"] when given; None/absent ⇒ all.
url str The JSON-RPC POST endpoint peers should call — the served base URL plus /.
Field Default when omitted
name "toolnexus-agent"
description ""
version "0.1.0"
protocolVersion "0.3.0" (fixed — not configurable)
capabilities.streaming False (fixed — no streaming in core)
provider omitted entirely unless cfg["provider"] is set
  • Toolkit.serve — Publish an Agent Card and answer JSON-RPC over the client loop — your toolkit becomes someone else’s remote agent.
  • FileTaskStore — Persist inbound A2A tasks so a suspended request survives a restart.
  • build_mcp_server — The inbound MCP profile: any MCP client can call your tools.