toolnexus.a2a/agent-tools
Clojure (JVM) + cljgo · package net.clojars.muthuishere/toolnexus · SPEC §7A · clojure/src/toolnexus/a2a.cljc
(toolnexus.a2a/agent-tools {:card "https://peer.example/.well-known/agent-card.json" ; required :headers {"authorization" "Bearer ${PEER_TOKEN}"} :timeout 300000 :poll-every 1000})
;; => [Tool ...] one per advertised skill, :source "a2a";; => [] the card could not be fetched — the reason is discardedagent-tools is exactly (:tools (remote-agent opts)). It takes the same options, fetches the same
card, and produces the same tools — it just throws the card and the error string away and hands you
the vector, which is the shape :tools on toolnexus.core/build
already wants.
Each returned tool is named sanitize(card name)_sanitize(skill id) and takes an object with a
required string task. Executing one runs the full §7A cycle: one SendMessage, then GetTask
polling until a terminal state, a timeout or an abort. Everything documented on
remote-agent about output strings, metadata and error isolation
applies unchanged, because it is the same code path.
The isolation guarantee is what makes the one-liner safe. A peer that is down, slow to hand back its
card, or serving malformed JSON contributes [] — never a thrown exception, never a half-built
toolkit. into a vector of peers and the toolkit is whatever peers were actually reachable at
startup.
When to use it
Section titled “When to use it”- Composing a toolkit in one expression — peers spliced into
:toolsnext to your native and HTTP tools, with no intermediate bindings. - Fan-out over several peers —
(into [] (mapcat agent-tools) peers)collects every reachable peer’s skills and quietly drops the rest. - You have already checked liveness elsewhere — a health endpoint or a startup probe is
reporting peer status, so the per-call
:erroris redundant.
Why this and not the alternative
Section titled “Why this and not the alternative”Both functions fetch the card at call time, so neither is free — call them once at startup and reuse the tools, rather than per request.
Examples
Section titled “Examples”One peer, spliced into a toolkit
Section titled “One peer, spliced into a toolkit”(require '[toolnexus.a2a :as a2a] '[toolnexus.core :as core])
(def tk (core/build {:mcp "examples/mcp.json" :skills "examples/skills" :tools (a2a/agent-tools {:card "http://127.0.0.1:8080/.well-known/agent-card.json"})}))
(core/tool-names tk)Several peers, reachable ones win
Section titled “Several peers, reachable ones win”(require '[toolnexus.a2a :as a2a] '[toolnexus.core :as core])
(def peers [{:card "http://127.0.0.1:8080/.well-known/agent-card.json"} {:card "http://127.0.0.1:8081/.well-known/agent-card.json" :timeout 5000} ;; deliberately dead — contributes [] and nothing else {:card "http://127.0.0.1:9/.well-known/agent-card.json" :timeout 250}])
(def tk (core/build {:tools (into [] (mapcat a2a/agent-tools) peers)}))Two peers up and one down yields the union of the two live catalogs. Note that §7A tool names
collide the moment two cards share a name, and a later tool wins the collision — give your peers
distinct card names.
Peers from a config block
Section titled “Peers from a config block”(require '[toolnexus.a2a :as a2a] '[toolnexus.core :as core] '[koine.fs :as fs] '[koine.json :as json])
(def cfg (json/read-str (fs/read-file "config.json")))
(def tk (core/build {:tools (into [] (mapcat a2a/agent-tools) (a2a/parse-agents-config (:agents cfg)))}))parse-agents-config normalises the block into exactly the
descriptors agent-tools accepts, drops disabled entries, and orders peers by name so two runtimes
cannot disagree about who registered first.
Most of the time you do not need either call:
toolnexus.core/build takes descriptors on :agents and resolves
them itself — and reads a top-level agents block straight off a parsed :mcp config map.
(core/build {:agents [{:card "http://127.0.0.1:8080/.well-known/agent-card.json"}]})(core/build {:mcp cfg}) ;; picks up cfg's "mcpServers" AND its "agents" blockOptions
Section titled “Options”Identical to remote-agent:
| Key | Default | Meaning |
|---|---|---|
:card |
— | Required. URL of the peer’s Agent Card. |
:headers |
none | Card GET and every JSON-RPC call. ${ENV_VAR} expanded, never logged. |
:timeout |
300000 |
Per-request and overall task budget, in ms. |
:poll-every |
1000 |
GetTask interval in ms. :pollEvery also accepted. |
What you get back
Section titled “What you get back”| Shape | When |
|---|---|
[Tool ...] |
The card was fetched. One tool per entry in the card’s skills, in card order. |
[] |
The card had no skills, or the fetch failed. The two are indistinguishable here. |
See also
Section titled “See also”toolnexus.a2a/remote-agent— the same call, plus:card,:endpointand:errortoolnexus.a2a/parse-agents-config— turn a config block into descriptorstoolnexus.core/build— where the tools go