# Cross Agent Teams Mcp

> MCP daemon for cross-agent collaboration

- **Type:** MCP server
- **Install:** `agentstack add mcp-jtianling-cross-agent-teams-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [jtianling](https://agentstack.voostack.com/s/jtianling)
- **Installs:** 0
- **Category:** [Communication](https://agentstack.voostack.com/c/communication)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [jtianling](https://github.com/jtianling)
- **Source:** https://github.com/jtianling/cross-agent-teams-mcp

## Install

```sh
agentstack add mcp-jtianling-cross-agent-teams-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# cross-agent-teams-mcp

[中文说明](./README.zh-CN.md)

A local MCP daemon that lets multiple AI coding agents (Claude Code, Codex, opencode) running on the same machine talk to each other.  Agents register, send 1-to-1 messages, broadcast to a team or role, and wake each other up — all over a single daemon, no external services.

## Why not just use Claude Code's agent teams?

Claude Code already ships its own agent teams feature.  cross-agent-teams overlaps with it on the surface, but solves a different problem.  Three concrete reasons to reach for this project:

**Cross-agent support.**  Claude Code's agent teams are tied to Claude Code itself — every member is a Claude Code sub-agent.  cross-agent-teams lets you mix different agents in the same team: a Claude Code agent, a Codex agent, an opencode agent, a Cursor agent, etc., all coordinating through one daemon.  Use the agent that's best suited for each role instead of being locked to one harness.

**Better persistence and controllability.**  In this design, each agent process is started and stopped manually.  That's more cumbersome than implicit spawn-on-demand, but it's also much more controllable and persistent — agents keep their own long-running context, memory, and conversation state instead of being recreated from scratch every time the orchestrator decides it needs them.  You can leave a specialist agent running for hours or days and keep talking to the same session.

**Cross-device / cross-user collaboration.**  The daemon recently grew support for building teams across physical machines (see [section 4](#4-cross-host--cross-device-collaboration)).  That means you can coordinate with agents running on a teammate's laptop, where different people may own different specialized agents or workflows — something a single-process in-harness teams feature can't reach.

## Quick start

### Recommended: let a code agent set it up

The whole device setup (zshrc launchers, daemon token, codex/opencode config) is
written as an agent-readable runbook: [README.agent.md](README.agent.md).  Paste
this to any code agent that can fetch URLs and run shell commands:

```
Read https://raw.githubusercontent.com/jtianling/cross-agent-teams-mcp/HEAD/README.agent.md
and follow it to set up xats on this device.
```

The agent will confirm a device label and the `~/.zshrc` changes with you,
auto-generate the daemon token on first `start-xats`, and wire up the
`free-xats-codex` / `xats-codex` / `free-xats-opencode` / `xats-opencode`
launchers plus `start-xats` / `stop-xats`.  Prefer doing it by hand?  Continue
below.

### Claude Code

```bash
# 1. Start the daemon (run once, keep it alive)
npx -y cross-agent-teams-mcp@latest daemon --port 9100 &

# 2. In your project, install the MCP config
npx mcpsmgr add jtianling/cross-agent-teams-mcp -a claude-code

# 3. Start Claude Code with the channel loader (manual permission prompt expected)
claude --dangerously-load-development-channels server:cross-agent-teams-channel
```

### Other agents (Codex, opencode, ...)

```bash
# 1. Start the daemon (run once, keep it alive)
npx -y cross-agent-teams-mcp@latest daemon --port 9100 &

# 2. In your project, install the MCP config (interactive picker)
npx mcpsmgr add jtianling/cross-agent-teams-mcp

# 3. Start your coding agent as usual
```

Note: Claude Code gets push wake out of the box.  Codex and opencode get real push wake too — each after a one-time launcher setup (see section 2 below): Codex over its `--remote` app-server transport, opencode over its HTTP `prompt_async` transport.  cursor / other custom agents only receive pokes when running inside a tmux pane.  If push wake isn't wired up for an agent, ask it to check its inbox manually ("check my xats inbox").

Then talk to your agent in plain language:

```
# In agent A:
Register me to xats as backend on team default.

# In agent B:
Register me to xats as frontend on team default.
Send backend a message: the API has changed.
```

That's it.  Sections below cover the details — daemon flags, manual MCP config, codex `--remote` setup, more usage patterns.

## 1. Start the daemon

Run this once on your machine and keep the process alive (dedicated terminal, `tmux`, `screen`, `launchd` — your call):

```bash
npx -y cross-agent-teams-mcp@latest daemon --port 9100
```

The daemon listens on `127.0.0.1:9100`.  MCP endpoint is `http://127.0.0.1:9100/mcp`, health endpoint is `http://127.0.0.1:9100/health`.

Common flags:

- `--port ` (default `9100`)
- `--host ` (default `127.0.0.1`)
- `--device ` (default: hostname-derived label)
- `--token ` (Bearer auth)
- `--db ` (default `~/.cross-agent-teams-mcp/data.db`)
- `--pid-file ` (default `~/.cross-agent-teams-mcp/daemon.pid`)

For multi-host / multi-device setups (LAN, tailscale, etc.), see [section 4](#4-cross-host--cross-device-collaboration) below.

## 2. Configure your agent's MCP client

### Recommended: `mcpsmgr` (shown in Quick start)

[`mcpsmgr`](https://www.npmjs.com/package/mcpsmgr) reads this repo's `mcpsmgr.json` and writes the right MCP entries into your agent's config in one shot — including the Claude Code stdio channel proxy entry, the Codex `experimental_use_rmcp_client` toggle, and the streamable-http MCP entry.

To override the daemon port:

```bash
npx mcpsmgr add jtianling/cross-agent-teams-mcp -a claude-code --port 9300
```

### Manual config

If you don't want `mcpsmgr` (private fork, custom token, custom stdio args, or you just prefer hand-edited config), the raw per-agent configs are below.

#### Claude Code (needs both entries — HTTP for tools, stdio for channel wake)

`.mcp.json` (or `~/.claude.json`):

```json
{
  "mcpServers": {
    "cross-agent-teams": {
      "type": "http",
      "url": "http://127.0.0.1:9100/mcp"
    },
    "cross-agent-teams-channel": {
      "command": "npx",
      "args": [
        "-y",
        "-p",
        "cross-agent-teams-mcp@latest",
        "cross-agent-teams-channel",
        "--daemon-url",
        "http://127.0.0.1:9100/mcp"
      ]
    }
  }
}
```

Then start Claude Code with the experimental channel loader so it subscribes to the proxy's wake notifications:

```bash
claude --dangerously-load-development-channels server:cross-agent-teams-channel
```

The `server:` suffix MUST equal the MCP server key in `.mcp.json` (`cross-agent-teams-channel` above).  If your daemon uses `--token `, add `"headers": { "Authorization": "Bearer " }` to the HTTP entry, and add `--token ` to the channel proxy args.

#### Codex CLI

Codex talks to the daemon over Streamable HTTP.  Wake-ups go through Codex's own app-server WebSocket transport — there is no channel proxy involved.

##### Minimum config (mailbox only, no push wake)

`~/.codex/config.toml`:

```toml
experimental_use_rmcp_client = true

[mcp_servers.cross-agent-teams-mcp]
type = "streamable-http"
url = "http://127.0.0.1:9100/mcp"
```

`experimental_use_rmcp_client = true` MUST sit at the top level — without it, streamable-http MCP servers fail to load.

When the daemon was started with `--token `: `export XATS_TOKEN=` in the shell that launches codex, then add `bearer_token_env_var = "XATS_TOKEN"` to the `[mcp_servers.cross-agent-teams-mcp]` block.  (Codex 0.130+ silently ignores the older `[mcp_servers.X.headers]` form — its accepted keys are `http_headers` and `bearer_token_env_var`, and `bearer_token_env_var` is preferred so the token never lands in a checked-in config.)

In this minimum mode, `send_message` to this Codex still drops a row in its mailbox, but you have to call `get_inbox` yourself to read it — no push wake.

##### Let other agents wake you (codex-appserver poke)

To let other agents **wake** this Codex thread (not just mail it), you need `codex-appserver` delivery.  The setup has one non-obvious gotcha worth calling out:

> **In `codex --remote` mode, MCP servers are loaded by the app-server, NOT by the TUI.**  The MCP entry above must therefore live in the `CODEX_HOME` that the **app-server** reads at startup — usually the global `~/.codex/config.toml`.  Setting `CODEX_HOME` on the TUI alone does nothing for MCP under `--remote`.

Start order:

```bash
# 1) Long-lived codex app-server somewhere (its CODEX_HOME decides the MCP set).
codex app-server --listen ws://127.0.0.1:8799

# 2) Codex TUI in a separate terminal, connected to the same app-server.
codex --remote ws://127.0.0.1:8799
```

If the app-server's `CODEX_HOME` doesn't have `cross-agent-teams-mcp` configured, the codex agent inside `--remote` won't see the MCP tools at all and `register_agent` will never fire.

##### Recommended: launcher with tmux pane auto-bind

For pokes to be injected directly into the running Codex thread (rather than landing as a tmux paste), the daemon needs to know which tmux pane the codex process lives in.  The launcher pre-claims a pane via the `pre-register-codex-pane` CLI before exec'ing codex.  Add to `~/.zshrc`:

```zsh
free-xats-codex() {
    local xats_agent_id codex_home search_dir
    xats_agent_id="$(uuidgen)"
    search_dir="$PWD"
    while [[ "$search_dir" != "/" ]]; do
        if [[ -f "$search_dir/.codex/config.toml" ]]; then
            codex_home="$search_dir/.codex"
            break
        fi
        search_dir="${search_dir:h}"
    done

    if [[ -n "$TMUX_PANE" ]]; then
        npx -y cross-agent-teams-mcp pre-register-codex-pane \
            --pane "$TMUX_PANE" \
            --agent-id "$xats_agent_id" \
            >/dev/null 2>&1 \
            || echo "[xats] pre-register failed (continuing without pane claim)" >&2
    fi

    if [[ -n "$codex_home" ]]; then
        CODEX_HOME="$codex_home" exec codex \
            --remote ws://127.0.0.1:8799 \
            -C "$PWD" \
            -c xats.agent_id="\"$xats_agent_id\"" "$@"
    else
        exec codex \
            --remote ws://127.0.0.1:8799 \
            -C "$PWD" \
            -c xats.agent_id="\"$xats_agent_id\"" "$@"
    fi
}
```

What the launcher does:

- Inside tmux (`$TMUX_PANE` set): pre-registers the pane → uuid mapping with the daemon (120s TTL).  When the codex agent later calls `register_agent({agent_type: "codex", thread_id: $CODEX_THREAD_ID, ...})`, the daemon resolves `tmux_pane_id` automatically by matching the pre-reg against the codex argv.
- `--remote ws://127.0.0.1:8799` connects to the long-lived app-server from step (1) above.
- `-c xats.agent_id="\"$uuid\""` exposes the uuid in codex's argv so the daemon can verify the pane.

More detail (auth headers, lower-level `register_agent` form): [docs/configs/codex-cli.md](docs/configs/codex-cli.md).

#### opencode

opencode ships a first-class headless HTTP API (`POST /session/{id}/prompt_async`) that the daemon uses as a dedicated wake-up transport — no tmux pane injection required.  The transport is activated by registering with `agent_type="opencode"` and a `base_url` pointing at the opencode process's HTTP server.

When another agent pokes this opencode, the daemon POSTs the wake hint to `prompt_async`, which **starts a fresh opencode agent turn** — the agent wakes on its own and reads its inbox with no manual prompt, the same first-class push-wake that Claude Code and Codex get (not the passive tmux paste that `custom` agents fall back to).

Add a `free-xats-opencode` zsh function to `~/.zshrc` (mirrors the `free-xats-codex` pattern):

```zsh
free-xats-opencode() {
    local port
    port="$(node -e 'const s=require("net").createServer();s.listen(0,"127.0.0.1",()=>{console.log(s.address().port);s.close()})')"
    OPENCODE_XATS_BASE_URL="http://127.0.0.1:${port}" exec opencode --port "${port}" --hostname 127.0.0.1 "$@"
}
```

Then replace plain `opencode` with `free-xats-opencode`:

```bash
free-xats-opencode            # default agent
free-xats-opencode --agent build --model glm-5.2   # args pass through
```

What the launcher does:

- Allocates a free TCP port on `127.0.0.1` (supports concurrent opencode instances without port conflicts).
- Exports `OPENCODE_XATS_BASE_URL=http://127.0.0.1:` so the agent's Bash tool can read it and pass it as `base_url` to `register_agent`.
- `exec opencode --port  --hostname 127.0.0.1` starts the TUI with its HTTP server bound to loopback.

Inside the opencode TUI say:

> 注册到 xats, name: oc-1, team: default

The agent detects `$OPENCODE_XATS_BASE_URL`, picks `agent_type="opencode"` automatically, passes the env value as `base_url`, and omits `session_id` (the daemon auto-resolves it as the most recently updated session on that base_url).  `auth_token_ref` is only required when the opencode server was started with `OPENCODE_SERVER_PASSWORD` set; in that case also pass `auth_token_ref: "OPENCODE_SERVER_PASSWORD"`.

If you launch opencode via plain `opencode` (without the wrapper), the env var is absent, the agent falls back to `agent_type="custom"` with `agent_type_name="opencode"`, and pokes are delivered via tmux pane injection (see next section).

#### Other coding agents (cursor, ...)

Anything that is not Claude Code, Codex, or opencode-via-launcher — cursor, an editor extension, your own harness — connects over plain Streamable HTTP and registers as `agent_type="custom"` (the agent figures this out for you).  There is no dedicated wake-up transport for these; cross-agent pokes are delivered by injecting text into the agent's tmux pane, so run the agent inside a tmux window and the daemon will resolve `pid → tty → pane` automatically when you register.

Per-tool config snippets live in [docs/configs/opencode.md](docs/configs/opencode.md) (and `docs/configs/` for the rest).

## 3. Use it from your agent

Once your agent is connected to the daemon, you don't have to memorize tool names.  Just talk to the agent in plain language and it will pick the right tool — the README below shows the *kinds of things you say*, not the underlying API.

> Note: always run these from inside the agent session.  Don't hand-drive the MCP protocol with `curl` (or any other external HTTP client) to register or send — that opens a *different* MCP session, and worse, a `curl` `register_agent` triggers a cross-session **takeover** that force-closes your real session.  If your MCP client transport is dead and you just need a lifeboat, use the loopback-only REST API below instead — it never touches your session.

**Lifeboat: the loopback REST API.**  If an agent's MCP client transport breaks, it can no longer call any xats tool — not even to say it is stuck.  For exactly that case the daemon exposes a tiny, **loopback-only** REST surface on the same port under `/api/`.  It resolves the agent by `(team, name)`, reuses the same send / inbox / list-agents logic as the MCP tools, and has **zero session side-effects** (no takeover — safe even while your MCP session is still alive).  Remote callers get `403` by design; if the daemon was started with `--token`, present it just like `/mcp` (`Authorization: Bearer ` or `?token=`).

```bash
# send as an already-registered agent
curl -s http://127.0.0.1:/api/send \
  -H 'content-type: application/json' \
  -d '{"from":{"team":"default","name":"alice"},
       "to":{"team":"default","name":"bob"},
       "body":"my MCP client is wedged — restarting"}'

# read your inbox — omit since_event_id to advance your read cursor,
# or pass it for a read-only peek that does NOT advance the cursor
curl -s 'http://127.0.0.1:/api/inbox?team=default&name=alice'

# list a team's agents
curl -s 'http://127.0.0.1:/api/agents?team=default'
```

There is deliberately no `register_agent` over REST — creating or rebinding an identity is the very takeover footgun this surface avoids, so an agent must have registered once (over MCP) before it can use the lifeboat.

> Security note: "loopback-only" includes a browser running on the same machine, so run the daemon with `--token` to keep a local web page from reaching `/api/`.  Without a token, the worst a malicious local page can do is advance an agent's inbox cursor via a cross-site `GET /api/inbox` — it cannot read any response (CORS), send, or i

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [jtianling](https://github.com/jtianling)
- **Source:** [jtianling/cross-agent-teams-mcp](https://github.com/jtianling/cross-agent-teams-mcp)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-jtianling-cross-agent-teams-mcp
- Seller: https://agentstack.voostack.com/s/jtianling
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
