A2A.parseAgentsConfig
Java · package io.github.muthuishere:toolnexus · SPEC §7A · A2A.java
public static List<A2A.Agent> parseAgentsConfig(Map<String, Object> block)Parses an agents config block — Map<name, AgentConfig> — into A2A.Agent descriptors,
skipping disabled entries. The config key is only an identifier; a resulting tool’s name
prefix comes from the fetched card’s name, not the key. Mirrors how a top-level mcpServers
block is parsed for MCP.
When to use it
Section titled “When to use it”When peers are declared in a config file or a parsed JSON Map alongside mcpServers —
letting an operator add/remove/disable a remote agent without touching code. Toolkit.create
already calls this for you whenever mcpConfig is a Map containing an agents key; reach for
it directly only when you want the parsed List<Agent> before it becomes tools.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — one entry, card-only
Section titled “1. The smallest useful call — one entry, card-only”import io.github.muthuishere.toolnexus.*;import java.util.List;import java.util.Map;
public class Example { public static void main(String[] args) { Map<String, Object> block = Map.of( "librarian", Map.of("card", "https://library.internal/.well-known/agent-card.json"));
List<A2A.Agent> parsed = A2A.parseAgentsConfig(block);
if (parsed.size() != 1) throw new AssertionError(parsed.size()); if (!parsed.get(0).card().equals("https://library.internal/.well-known/agent-card.json")) { throw new AssertionError(parsed.get(0).card()); } // no headers/timeout/pollEvery given ⇒ null (defaults apply at resolve time) if (parsed.get(0).timeout() != null) throw new AssertionError(parsed.get(0).timeout());
System.out.println("ok: " + parsed.get(0).card()); }}2. The realistic case — enabled/disabled precedence
Section titled “2. The realistic case — enabled/disabled precedence”The same MCP isEnabled rule applies: disabled:true wins outright, then enabled:false;
everything else (including enabled:true with no disabled) is enabled.
import io.github.muthuishere.toolnexus.*;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;
public class Example { public static void main(String[] args) { Map<String, Object> block = new LinkedHashMap<>(); block.put("keep", Map.of("card", "http://x/1")); block.put("skippedByDisabled", Map.of("card", "http://x/2", "disabled", true)); block.put("skippedByEnabledFalse", Map.of("card", "http://x/3", "enabled", false)); // disabled:true wins even when enabled:true is also present. block.put("skippedPrecedence", Map.of("card", "http://x/4", "enabled", true, "disabled", true)); block.put("keptExplicitEnabled", Map.of("card", "http://x/5", "enabled", true));
List<A2A.Agent> parsed = A2A.parseAgentsConfig(block); List<String> cards = parsed.stream().map(A2A.Agent::card).sorted().toList();
if (!cards.equals(List.of("http://x/1", "http://x/5"))) throw new AssertionError(cards);
System.out.println("ok: " + cards); }}3. The full surface — headers/timeout/pollEvery, wired into a Toolkit
Section titled “3. The full surface — headers/timeout/pollEvery, wired into a Toolkit”Parsed descriptors carry every field a hand-built A2A.agent(...) call would; feeding the block
straight through Toolkit.Options.mcpConfig(...) proves the same top-level agents key
Toolkit.create reads.
import com.sun.net.httpserver.HttpServer;import io.github.muthuishere.toolnexus.*;import java.io.OutputStream;import java.net.InetSocketAddress;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 peer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); peer.createContext("/", ex -> { try { String path = ex.getRequestURI().getPath(); if ("/.well-known/agent-card.json".equals(path)) { send(ex, 200, "{\"name\":\"planner\",\"skills\":[{\"id\":\"plan\",\"description\":\"Plan\"}]}"); return; } String body = new String(ex.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); Map<String, Object> rpc = Json.toMap(body); Object id = rpc.get("id"); send(ex, 200, "{\"jsonrpc\":\"2.0\",\"id\":\"" + id + "\",\"result\":" + "{\"id\":\"t1\",\"status\":{\"state\":\"completed\"}," + "\"artifacts\":[{\"parts\":[{\"kind\":\"text\",\"text\":\"planned\"}]}]}}"); } catch (Exception e) { send(ex, 500, "err"); } }); peer.start(); int port = peer.getAddress().getPort(); String cardUrl = "http://127.0.0.1:" + port + "/.well-known/agent-card.json";
// First: parse the block directly. Map<String, Object> block = Map.of("planner", Map.of( "card", cardUrl, "timeout", 5000, "pollEvery", 10, "headers", Map.of("X-Trace", "abc"))); List<A2A.Agent> parsed = A2A.parseAgentsConfig(block); if (parsed.get(0).timeout() != 5000L) throw new AssertionError(parsed.get(0).timeout()); if (!"abc".equals(parsed.get(0).headers().get("X-Trace"))) throw new AssertionError(parsed.get(0).headers());
// Second: the same shape, nested under a top-level `agents` key, resolved // by Toolkit.create the way it would come out of a real mcp.json. Map<String, Object> config = Map.of("agents", block); try (Toolkit tk = Toolkit.create(new Toolkit.Options().builtins(false).mcpConfig(config))) { ToolResult r = tk.execute("planner_plan", Map.of("task", "ship it")); if (r.isError() || !r.output().equals("planned")) throw new AssertionError(r.output()); System.out.println("ok: " + r.output()); } finally { peer.stop(0); } }
private static void send(com.sun.net.httpserver.HttpExchange ex, int status, String body) { try { byte[] b = body.getBytes(StandardCharsets.UTF_8); ex.getResponseHeaders().add("Content-Type", "application/json"); ex.sendResponseHeaders(status, b.length); try (OutputStream os = ex.getResponseBody()) { os.write(b); } } catch (Exception ignored) { } }}Fields and overloads
Section titled “Fields and overloads”| Member | Type | What it is |
|---|---|---|
parseAgentsConfig(block) |
List<Agent> |
One Agent per non-disabled entry; block == null ⇒ empty list. |
entry card |
String (required) |
Entries without a string card are silently skipped. |
entry headers / timeout / pollEvery |
— | Same fields as A2A.agent’s full overload. |
entry enabled / disabled |
boolean |
disabled:true wins, then enabled:false; otherwise enabled. |
See also
Section titled “See also”A2A.agent— the descriptor shape each parsed entry becomes.A2A.agentTools— resolve a descriptor into callable tools.Toolkit.create— reads a top-levelagentsblock on a parsed configMapautomatically.