Skip to content

parseAgentsConfig

JavaScript · package toolnexus · SPEC §7A · js/src/a2a.ts

interface AgentConfig {
card: string
headers?: Record<string, string>
timeout?: number
pollEvery?: number
enabled?: boolean
disabled?: boolean
}
type AgentsConfig = Record<string, AgentConfig>
function parseAgentsConfig(block: AgentsConfig | undefined): Agent[]

Parse an agents config block — Record<name, AgentConfig>, the same shape as an mcpServers block — into Agent[] descriptors, skipping disabled entries. The config key is only an identifier for humans editing the file; the tool name prefix a peer’s skills get comes from the fetched card’s name, not the key.

Use parseAgentsConfig when remote peers should be data, not code — declared in the same JSON file as your mcpServers, edited without a redeploy, one block per environment. This is what createToolkit({ mcpConfig }) calls when the parsed config has an agents key.

The enable/disable precedence matters and is easy to get wrong by hand: disabled: true wins over everything, then enabled: false, default is enabled — the exact isEnabled rule mcpServers already uses (§2), so an agents block reads the same way once you know one.

1. The smallest useful call — one entry, no disables

Section titled “1. The smallest useful call — one entry, no disables”
import assert from "node:assert"
import { parseAgentsConfig } from "toolnexus"
const parsed = parseAgentsConfig({
reviewer: { card: "http://127.0.0.1:9/.well-known/agent-card.json" },
})
assert.equal(parsed.length, 1)
assert.equal(parsed[0].card, "http://127.0.0.1:9/.well-known/agent-card.json")
console.log("ok:", parsed.length, "agent(s) parsed")

2. A realistic case — mixed headers, timeouts, and one disabled peer

Section titled “2. A realistic case — mixed headers, timeouts, and one disabled peer”
import assert from "node:assert"
import { parseAgentsConfig } from "toolnexus"
const parsed = parseAgentsConfig({
reviewer: {
card: "http://reviewer.internal/.well-known/agent-card.json",
headers: { Authorization: "Bearer ${TN_DOCS_TOKEN}" },
timeout: 60_000,
pollEvery: 500,
},
planner: {
card: "http://planner.internal/.well-known/agent-card.json",
disabled: true, // wound down for now — kept in config, not deleted
},
archivist: {
card: "http://archivist.internal/.well-known/agent-card.json",
enabled: true,
},
})
assert.deepEqual(
parsed.map((a) => a.card),
["http://reviewer.internal/.well-known/agent-card.json", "http://archivist.internal/.well-known/agent-card.json"],
"the disabled planner is skipped; the config key itself never appears on the Agent",
)
assert.equal(parsed[0].timeout, 60_000)
assert.equal(parsed[0].pollEvery, 500)
console.log("ok:", parsed.length, "enabled agent(s)")

3. The full surface — every precedence combination, plus malformed entries

Section titled “3. The full surface — every precedence combination, plus malformed entries”

enabled+disabled together: disabled always wins. Entries missing a card, or that aren’t objects at all, are skipped rather than throwing — a config file is user input.

import assert from "node:assert"
import { parseAgentsConfig } from "toolnexus"
const parsed = parseAgentsConfig({
a: { card: "http://x/1" }, // default: enabled
b: { card: "http://x/2", disabled: true }, // disabled wins
c: { card: "http://x/3", enabled: false }, // enabled:false ⇒ off
d: { card: "http://x/4", enabled: true, disabled: true }, // disabled STILL wins over enabled:true
e: { headers: { x: "y" } } as any, // no `card` ⇒ skipped, not thrown
})
assert.deepEqual(parsed.map((a) => a.card), ["http://x/1"])
// undefined block ⇒ empty array, never a throw — the "no agents configured" case.
assert.deepEqual(parseAgentsConfig(undefined), [])
console.log("ok:", parsed.length, "agent(s) survived precedence + malformed-entry filtering")
Field Type What it does
block AgentsConfig | undefined Record<name, AgentConfig>, mirroring mcpServers. undefined[].
block[name].card string Agent Card URL. Entries missing this are skipped.
block[name].headers Record<string, string> ${ENV}-expanding request headers.
block[name].timeout / .pollEvery number Same as agent()’s options.
block[name].enabled / .disabled boolean MCP isEnabled precedence: disabled:true wins, then enabled:false, default enabled.

Agent[] — ready to hand straight to createToolkit({ agents }) or toolkit.addAgent(), one per enabled config entry, in the block’s iteration order. Disabled and malformed entries are silently dropped, never thrown.

  • agent — Point at a remote agent’s card and use it exactly like a local tool.
  • agentTools — Expand a remote agent card into one tool per advertised skill.