# ultron-wa-mcp

A **Model Context Protocol (MCP) server** that exposes the Ultron WhatsApp CRM — flows,
campaigns, warmup, numbers, messages and access control — as tools any MCP client (Claude Code,
the Claude app, etc.) can call.

It reuses the **exact same RPCs, tables and supervisor control API** the dashboard uses
(`dashboard/server.js`), so behaviour stays in lockstep with the web UI. Nothing here is a new
data path — it is the dashboard's write/read surface, spoken as MCP tools.

- **Transport:** stdio (the deliverable — works with `claude mcp add`). The code is
  transport-agnostic (`src/server.js` `buildServer()`) so an HTTP/SSE entry can be added later
  without touching the tools.
- **Data:** Postgres via a pooler-aware `pg` pool (same setup as `ultron-wa/pool.js`). RPCs are
  called by named args; table CRUD is parameterized and identifier-whitelisted.
- **Live WhatsApp:** the ultron-wa supervisor's control API over HTTP (`WA_CTRL_URL` + Bearer
  `WA_CTRL_TOKEN`) for live number status and sends. DB-only tools work even if it's unset.

## Requirements

- Node.js ≥ 18 (uses global `fetch` / `AbortSignal.timeout`).
- Access to the Supabase Postgres DB (`SUPABASE_DB_PASSWORD`, and on IPv4-only networks
  `SUPABASE_POOLER_HOST`).
- Optional, for live status/sends: `WA_CTRL_URL` + `WA_CTRL_TOKEN`.

## Install

```bash
cd /Users/ahmed/devFolder/Ultron/ultron-wa-mcp
npm install
node --check index.js      # syntax check
```

If `npm install` can't reach the network, copy a `node_modules` with `@modelcontextprotocol/sdk`,
`pg` and `dotenv` into this directory — the code is otherwise self-contained.

## Configuration

The server auto-loads env from, in order: a local `./.env` (next to the server), then the
**repo-root `../.env`** (the single source of truth). Real `process.env` always wins. So on the
Mac / VPS you usually need **no `.env` here at all** — it inherits the repo's.

Only two extra vars are WhatsApp-live specific and may need adding (they live in Railway +
`/root/ultron/.env`, not the Mac `.env`):

```
WA_CTRL_URL=http://72.61.190.110:3100
WA_CTRL_TOKEN=<the supervisor token>
```

Role/identity context (optional):

| Var | Effect |
|---|---|
| *(unset)* | **Owner ADMIN context** — sees every client's flows/campaigns/numbers. Default. |
| `MCP_USER_ID=<app_users.id uuid>` | Scope every tool to that user's clients/numbers (agent view). |
| `MCP_ADMIN=1` (with `MCP_USER_ID`) | Keep admin see-all, but attribute writes to that user. |

See `.env.example` for the full list.

## Register with Claude Code (`claude mcp add`)

### Mac-local (dev — connects to Supabase via the IPv4 pooler)

The repo `.env` already has `SUPABASE_DB_PASSWORD` + `SUPABASE_POOLER_HOST`, so this just works:

```bash
claude mcp add ultron-wa \
  --scope user \
  -- node /Users/ahmed/devFolder/Ultron/ultron-wa-mcp/index.js
```

To also enable live number status / sends from the Mac, pass the supervisor vars:

```bash
claude mcp add ultron-wa \
  --scope user \
  --env WA_CTRL_URL=http://72.61.190.110:3100 \
  --env WA_CTRL_TOKEN=<token> \
  -- node /Users/ahmed/devFolder/Ultron/ultron-wa-mcp/index.js
```

### VPS (has IPv6 → uses the direct DB host; supervisor is local)

Deploy the folder to the VPS (e.g. `rsync` into `/root/ultron/ultron-wa-mcp/`, `npm install`
there), then — since `/root/ultron/.env` already carries `SUPABASE_DB_PASSWORD`, `WA_CTRL_URL`
and `WA_CTRL_TOKEN`, and the box has IPv6 (leave `SUPABASE_POOLER_HOST` unset):

```bash
claude mcp add ultron-wa \
  --scope user \
  -- node /root/ultron/ultron-wa-mcp/index.js
```

### Verify

```bash
claude mcp list          # ultron-wa should show "connected"
```

Then in a Claude Code session: *"list my WhatsApp flows"*, *"show campaign stats for <id>"*, etc.

### Removing / re-adding

```bash
claude mcp remove ultron-wa
```

## Tools

29 tools, grouped:

### Flows
| Tool | What it does |
|---|---|
| `list_flows` | All flows in context, with node + run tallies. |
| `get_flow` | One flow: config + graph JSON + client KB + run counts. |
| `create_flow` | New flow for a client (graph optional; defaults to draft). |
| `update_flow` | Patch name / client / graph / status. |
| `delete_flow` | Delete a flow (runs cascade). |
| `activate_flow` | status → active. |
| `deactivate_flow` | status → draft (or archived). |
| `get_flow_runs` | Live runs monitor for a flow. |
| `test_flow` | Trigger a test run against a phone from a connected number (`start_flow_run`). |

### Campaigns
| Tool | What it does |
|---|---|
| `list_campaigns` | All campaigns in context with recipient tallies. |
| `get_campaign` | One campaign: config + numbers + counts + recipient sample. |
| `campaign_stats` | Recipient status tally. |
| `create_campaign` | New campaign (client + flow + numbers + schedule; flow required). |
| `update_campaign` | Patch config / numbers. |
| `delete_campaign` | Delete a campaign (cascade). |
| `start_campaign` | status → running (requires a flow). |
| `pause_campaign` | status → paused. |
| `add_campaign_recipients` | Queue recipients from explicit `place_ids` or a lead filter (Saudi-mobile only). |

### Messages (read)
| Tool | What it does |
|---|---|
| `list_threads` | Team-visible threads, optional account/search filter. |
| `read_conversation` | Message history for one peer on one number. |

### Numbers
| Tool | What it does |
|---|---|
| `list_numbers` | `wa_accounts` merged with the supervisor's live worker status. |
| `number_status` | Live status of one number. |

### Access (sharing)
| Tool | What it does |
|---|---|
| `list_shares` | Who a number is shared with (name/email + role). |
| `share_number` | Share a number with a teammate as `viewer` or `manager` (upsert). |
| `unshare_number` | Revoke a teammate's access. |

### Warmup
| Tool | What it does |
|---|---|
| `get_warmup` | Warmup config + peers per actionable number. |
| `set_warmup` | Enable/disable, mode (`maintenance`/`ramp`), msgs/day, partner peers. |
| `delete_warmup` | Remove a number's warmup config + peers. |

### Send (optional)
| Tool | What it does |
|---|---|
| `send_message` | Manual send from a connected number via the supervisor. |

## Design notes

- **Safety:** every tool validates its inputs and returns a clean `isError` result on a bad call —
  the process never crashes. SQL identifiers are whitelist-checked; all values are parameterized.
- **Secrets** live only in `.env` (never committed; `.gitignore`d here). stdout is reserved for the
  MCP transport — all logs go to stderr.
- **Phone normalization** (`src/util.js` `normPhone`) mirrors `public.norm_phone` (05→9665). Keep
  the two in sync if either changes.

## HTTP/SSE later

`buildServer()` in `src/server.js` returns a fully-wired `Server` with no transport attached.
An HTTP entry would import it and call `server.connect(new SSEServerTransport(...))` or the
Streamable-HTTP transport — the tool table and all handlers are reused unchanged.
