Agent Radio
Communication
Agents that cannot talk to each other end up coordinated by whatever human is watching all their transcripts. This template gives a group of agents one channel: three tools to speak on it, and one subscription that pushes traffic back without anybody polling.
What you get
A NATS-backed channel, addressed by a subject prefix you choose.
- **
radio.broadcast** — say something every agent on the channel can see. Carries asubjectline, abody, and an optionalpriorityofroutineorurgent. - **
radio.direct** — address one agent by call sign. The hub routes on thetofield. - **
radio.roster** — ask who is currently on the air, with a last-seen timestamp for each call sign. - **
radio://{prefix}/feed** — the listening half. A client subscribes once; every message on the feed subject becomes anotifications/resources/updatedand the client re-reads. No polling loop, no tool call held open.
What that shape buys you, and what it costs:
- Every send is acknowledged. These are NATS request/reply calls, so a successful tool call means the hub accepted the message. It does not mean another agent has read it — read receipts are the hub's job, not the bus's.
- Every message is attributed. The channel refuses callers below verified trust, so a message on the bus always traces back to a real credential.
- You supply the hub. The gateway is the microphone and the speaker; the small service that listens on
{prefix}.broadcast, fans out, and answers{prefix}.rosteris yours. It is about fifty lines against any NATS client.
What you need
- A NATS server the gateway can reach. You will fill in NATS server host and port and a NATS auth token.
- A channel prefix — the Channel subject prefix field. Everything this template publishes and subscribes to sits under it, so scope the token to that prefix and a leaked token cannot read the rest of your bus.
- An OpenID Connect provider your agents already authenticate against, for the Identity provider issuer, Token audience and JWKS endpoint fields.
- A hub process that subscribes to these four subjects and replies:
The hub also publishes to {prefix}.feed whenever the feed changes. That publish is what wakes every subscriber.
How it works
Each tool is one dev.mcpg.backend.nats binding. The tool arguments become the request payload; the hub's reply becomes the tool result. Subjects are fixed at render time from your prefix — a caller cannot steer a message onto a different subject, and wildcards are refused outright.
The auth token is a per-call credential, not a boot-time one. On mcpg.cloud it is written as a ${cred://…} reference and resolved through the credential issuer on every call, so rotating it evicts the cached connection rather than requiring a restart.
The feed resource carries a watch block with a nats_topic strategy. That strategy is a second entity inside the same plugin artifact as the backend, so one plugins[] entry gives you both the request/reply path and the subscription path.
The first five minutes after it boots
- Start your hub and confirm it answers on
{prefix}.rosterwithnats req {prefix}.roster ''. - Connect a client and call
radio.roster. You should get the same answer through MCP that the NATS CLI just gave you. - Call
radio.broadcastwith a test subject line. Watch the hub log it. - Have the client subscribe to
radio://{prefix}/feed, then publish once to{prefix}.feedfrom the CLI. The client should receivenotifications/resources/updatedwithin a second. - Point a second agent at the same gateway and confirm both see each other in
radio.roster.
Notes
- **
dev.mcpg.backend.natsis not baked into the published gateway images.** It is pulled fromghcr.io/mcpg-dev/plugins/backend-natsat boot, so the host needs registry access on first start, or a configured mirror. - All NATS bindings in one gateway share a connection. That is a property of the plugin, not of this template: if you add another NATS binding later, give it the same server URL.
- Reply timeouts are 5 seconds. If your hub does slow work before replying, raise
timeout_mson the binding rather than letting the call fail — or have the hub acknowledge immediately and do the work after. - There is no message history here. The feed resource returns whatever the hub chooses to return. If you need durable replay, put JetStream behind the hub; nothing in this config has to change.