Skip to content

A2AServer.buildAgentCard

Java · package io.github.muthuishere:toolnexus · SPEC §7B · A2AServer.java

public static Map<String, Object> buildAgentCard(A2AServer.A2AConfig cfg,
List<SkillSource.SkillInfo> skills, String url)

Builds the plain Map served at GET /.well-known/agent-card.json: name, description, version, protocol version, capabilities, and one skills[] entry per SkillSource.SkillInfo — filtered to cfg.skills when given. This is the exact function A2AServer.start (and toolkit.serve) calls on every card request.

Almost never directly — toolkit.serve(addr, opts.a2a(...)) calls this for you on every GET. Reach for it yourself when you want the card’s Map shape without standing up a server: writing it to a static file, unit-testing what a config produces, or embedding it in a different transport.

1. The smallest useful call — defaults with no config

Section titled “1. The smallest useful call — defaults with no config”
import io.github.muthuishere.toolnexus.*;
import java.util.List;
import java.util.Map;
public class Example {
public static void main(String[] args) {
A2AServer.A2AConfig cfg = new A2AServer.A2AConfig(); // every field unset
Map<String, Object> card = A2AServer.buildAgentCard(cfg, List.of(), "http://127.0.0.1:8080/");
if (!"toolnexus-agent".equals(card.get("name"))) throw new AssertionError(card.get("name"));
if (!"".equals(card.get("description"))) throw new AssertionError(card.get("description"));
if (!"0.1.0".equals(card.get("version"))) throw new AssertionError(card.get("version"));
if (!"0.3.0".equals(card.get("protocolVersion"))) throw new AssertionError(card.get("protocolVersion"));
if (!"http://127.0.0.1:8080/".equals(card.get("url"))) throw new AssertionError(card.get("url"));
if (!((List<?>) card.get("skills")).isEmpty()) throw new AssertionError("expected no skills");
System.out.println("ok: " + card.get("name") + " v" + card.get("version"));
}
}

2. The realistic case — skills advertised, filtered by allowlist

Section titled “2. The realistic case — skills advertised, filtered by allowlist”

id == name for every advertised skill; cfg.skills (when set) narrows the list — an unknown name in the allowlist is simply absent from the wanted set, never an error. SkillInfo instances come from a real SkillSource.load over the shared fixtures — the same objects toolkit.serve passes in.

import io.github.muthuishere.toolnexus.*;
import java.util.List;
import java.util.Map;
public class Example {
public static void main(String[] args) throws Exception {
SkillSource src = SkillSource.load("examples/skills");
List<SkillSource.SkillInfo> skills = List.copyOf(src.skills().values());
A2AServer.A2AConfig cfg = new A2AServer.A2AConfig()
.name("front-desk")
.description("Handles routine requests")
// "does-not-exist" in the allowlist is simply never matched — not an error.
.skills(List.of("hello-world", "does-not-exist"));
Map<String, Object> card = A2AServer.buildAgentCard(cfg, skills, "http://127.0.0.1:0/");
List<Map<String, Object>> got = (List<Map<String, Object>>) (List<?>) card.get("skills");
if (got.size() != 1) throw new AssertionError("filtered to 1: " + got);
Map<String, Object> s = got.get(0);
if (!s.get("id").equals(s.get("name"))) throw new AssertionError("id == name for skills");
if (!"hello-world".equals(s.get("id"))) throw new AssertionError(s);
System.out.println("ok: " + got);
}
}

3. The full surface — a provider block, and the card as actually served

Section titled “3. The full surface — a provider block, and the card as actually served”

Every field filled in, then verified against the card a real toolkit.serve(...) server hands back over HTTP — proving buildAgentCard is exactly what’s on the wire.

import com.sun.net.httpserver.HttpServer;
import io.github.muthuishere.toolnexus.*;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
public class Example {
public static void main(String[] args) throws Exception {
HttpServer llm = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
llm.createContext("/", ex -> {
try {
ex.getRequestBody().readAllBytes();
byte[] b = "{\"choices\":[{\"message\":{\"content\":\"ok\"}}]}".getBytes(StandardCharsets.UTF_8);
ex.getResponseHeaders().add("Content-Type", "application/json");
ex.sendResponseHeaders(200, b.length);
try (OutputStream os = ex.getResponseBody()) { os.write(b); }
} catch (Exception ignored) { }
});
llm.start();
LlmClient client = LlmClient.create(new LlmClient.Options()
.baseUrl("http://127.0.0.1:" + llm.getAddress().getPort())
.style("openai").model("test-model").apiKey("test-key"));
try (Toolkit tk = Toolkit.create(new Toolkit.Options().builtins(false).skillsDir("examples/skills"))) {
A2AServer.A2AConfig a2a = new A2AServer.A2AConfig()
.name("front-desk")
.description("Handles routine requests")
.version("2.3.0")
.provider(new A2AServer.A2AProvider("Acme Corp", "https://acme.example"));
A2AServer.ServeHandle handle = tk.serve("127.0.0.1:0",
new Toolkit.ServeOptions().client(client).a2a(a2a));
try {
HttpResponse<String> res = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create(handle.url() + "/.well-known/agent-card.json")).GET().build(),
HttpResponse.BodyHandlers.ofString());
Map<String, Object> served = Json.toMap(res.body());
if (!"front-desk".equals(served.get("name"))) throw new AssertionError(served);
if (!"2.3.0".equals(served.get("version"))) throw new AssertionError(served);
Map<String, Object> provider = (Map<String, Object>) served.get("provider");
if (!"Acme Corp".equals(provider.get("organization"))) throw new AssertionError(provider);
if (!(handle.url() + "/").equals(served.get("url"))) throw new AssertionError(served.get("url"));
System.out.println("ok: " + served.get("name") + " served by " + provider.get("organization"));
} finally {
handle.stop();
}
} finally {
llm.stop(0);
}
}
}
Member Type What it is
buildAgentCard(cfg, skills, url) Map<String,Object> Pure function; no I/O.
A2AConfig.name / .description / .version String Defaults "toolnexus-agent" / "" / "0.1.0" when unset.
A2AConfig.provider A2AProvider{organization,url} Included on the card only when configured.
A2AConfig.skills List<String> Allowlist by skill name; null ⇒ advertise all.
card protocolVersion / capabilities.streaming "0.3.0" / false Fixed — this port never streams or pushes.
card url String The JSON-RPC POST endpoint — the served base URL plus /.