Skip to main content
Glama

Agora — NATS + MCP

A shared communication bus so that multiple agents can access and talk to each other.

NATS is the message bus; MCP is the entry point that lets an MCP client (Claude Code, Codex, or any other) reach into the Agora. There is no fixed roster — any number of agents can join by subscribing to the bus, and any MCP client can list, message, and query them. Agents come and go freely; the control plane just tracks who is currently online.

Architecture

MCP clients (Claude Code / Codex / any MCP client)
      │
  MCP Server (FastMCP, stdio)
      │
  Control Plane (FastAPI :8100)
      │
     NATS (:4222)
   ┌──┬──┬──┬── … ──┐        ← any number of agents can join/leave
 agent  agent  agent  …       (echo, researcher, writer, your own…)
      │
  Webhooks → n8n → your stack

Quick Start

# 1. Start NATS (native binary — no Docker needed)
nats-server -js -m 8222 --name swarm-nats
# (Alternative, if you prefer Docker:)
#   docker run -d --name nats -p 4222:4222 -p 8222:8222 nats:latest -js -m 8222

# 2. Create the Python env (system Python 3.9 is too old for `mcp`; use 3.11+)
#    This repo ships a ready-to-use venv at .venv313 (Python 3.13).
#    To recreate it:  uv venv --python 3.13 .venv313 && .venv313/bin/pip install -r requirements.txt

# 3. Start the control plane (FastAPI on :8100)
export PYTHONPATH=.
.venv313/bin/python -m core.control_plane

# 4. Start agents (each in its own terminal or as a background process)
.venv313/bin/python -m agents.echo_agent
#    Team example (two cooperating agents):
#      .venv313/bin/python -m agents.team_example researcher
#      .venv313/bin/python -m agents.team_example writer

# 5. Use the MCP server from Claude Code (see mcp-config.snippet.json)
#    Add it to your Claude Code / claude_desktop_config.json mcpServers block.

There is also a run.sh helper and a Makefile for the common commands:

./run.sh nats        # start NATS
./run.sh control     # start the control plane
./run.sh echo        # start the echo agent
./run.sh bridge <id> # join a turn-based AI (Claude Code/GPT) — see JOINING-AI-AGENT.md
./run.sh test        # run the unit tests
make nats / make control / make echo / make test

Joining: long-running agent → JOINING.md · turn-based AI → JOINING-AI-AGENT.md · call over HTTP without NATS → A2A.md.

MCP Tools

Once the MCP server is wired in, an MCP client gets these tools (all take flat arguments):

Tool

Purpose

agora_list_agents

List agents currently on the bus (optional status filter)

agora_get_agent

Details for one agent

agora_send_message

Fire-and-forget direct message to an agent

agora_request_reply

Send a request and wait for the agent's reply

agora_broadcast

Message all agents (or a topic)

agora_publish

Publish to any raw NATS subject

There's also a remote MCP server (Streamable HTTP) so you can add the Agora as a connector in Claude web / ChatGPT web (search + fetch included) — see MCP-CONNECT.md.

A2A — external access (other devices / other GPTs)

The Agora also speaks the A2A protocol as its public contract, so outside agents (another machine, a custom GPT, any A2A SDK client) can discover and call Agora agents over HTTP — no NATS, no bus membership. The control plane serves Agent Cards and bridges A2A Tasks to NATS.

Method

Path

Purpose

GET

/.well-known/agent-card.json

Card for the Agora

GET

/a2a/agents

Live catalog of agent cards

GET

/a2a/{id}/.well-known/agent-card.json

One agent's card

POST

/a2a/{id}

JSON-RPC message/send · message/stream · tasks/get · tasks/cancel

Set A2A_TOKEN to require a bearer token, and A2A_BASE_URL (+ a tunnel) to expose it publicly. See A2A.md for the full guide: client example, the worker reply contract, auth, and exposing to remote/cloud clients.

NATS Subject Conventions

Pattern

Purpose

agent.{id}.inbox

Direct message to a specific agent

agent.{id}.status

Agent status updates

agora.broadcast

Message all agents

agora.topic.{topic}

Topic-based pub/sub

agora.request.{id}

Request/reply pattern

control.register

Agent registration

control.deregister

Agent deregistration

control.heartbeat

Agent heartbeats (carry full info, so a restarted control plane re-registers agents)

Creating a New Agent

Any agent can join the bus by subclassing BaseAgent — that's how "multiple agents can access" the Agora:

from core.base_agent import BaseAgent
from core.models import SwarmMessage

class MyAgent(BaseAgent):
    def __init__(self):
        super().__init__(agent_id="my-agent", agent_type="worker", capabilities=["analyze"])

    async def handle_message(self, subject: str, message: SwarmMessage) -> None:
        if message.payload.get("action") == "analyze":
            result = {"ok": True}
            # For a request, reply on the one-shot reply subject:
            await self.reply_to_request(message, result)

if __name__ == "__main__":
    import asyncio
    asyncio.run(MyAgent().start())

NATS Monitoring

NATS exposes a monitoring endpoint at http://localhost:8222 when started with -m 8222.