> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useswarm.co/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Setup

> Connect Swarm to Claude Code, Codex CLI, Cursor, or any MCP-compatible AI editor.

# MCP Server

Swarm ships an MCP (Model Context Protocol) server that plugs directly into your AI coding editor. Instead of jumping to a browser or terminal, you can drive a full UX test loop from inside Claude Code, Codex, or Cursor with one prompt.

## How it works

```
You (in your AI editor) → "test my signup flow"
        ↓
Editor calls dev_test → tunnels localhost → runs AI agents
        ↓
Editor calls dev_watch → gets structured issues + recommendations
        ↓
Editor fixes the code → calls dev_test again
```

The MCP server runs locally over stdio — no remote server, no extra services. It tunnels your localhost via Cloudflare so Swarm's cloud agents can reach your dev server.

If your account has persistent tunnels enabled, the MCP server reuses your stable localhost hostname automatically. See [Persistent Tunnels](/guides/persistent-tunnels).

## Install

One command registers the MCP with your editor **and** signs you in:

```bash theme={null}
npx @useswarm/mcp@latest setup
```

What this does:

1. Registers the MCP with **Claude Code** (via `claude mcp add`) and **Codex CLI** (writes `~/.codex/config.toml`).
2. Opens your browser to the device-code login page.
3. Stores the API key at `~/.useswarm/config.json` (mode `0600`).

After it finishes, open your editor and the four tools (`dev_test`, `dev_watch`, `dev_status`, `dev_close`) are ready to use.

### Limit to one editor

```bash theme={null}
npx @useswarm/mcp@latest setup --client claude   # Claude Code only
npx @useswarm/mcp@latest setup --client codex    # Codex CLI only
```

### Register without signing in

For CI, containers, or shared machines using `USESWARM_API_KEY` env:

```bash theme={null}
npx @useswarm/mcp@latest setup --skip-login
```

The MCP is registered but won't run until a key is provided (via env or a later `login` call).

## Authentication

Swarm uses API keys scoped to your account. Auth happens **at setup time**, in your terminal — never mid-session inside the editor.

| Command                    | What it does                                         |
| -------------------------- | ---------------------------------------------------- |
| `npx @useswarm/mcp setup`  | Register + sign in (one shot — what most users want) |
| `npx @useswarm/mcp login`  | Sign in only (you've already registered)             |
| `npx @useswarm/mcp logout` | Clear stored credentials                             |
| `npx @useswarm/mcp whoami` | Check who you're signed in as                        |

<Note>
  The MCP **does not** trigger an interactive login when the editor spawns it. If no key is found at startup, the MCP exits with a clear message telling you to run `setup` or `login` from a terminal. This avoids broken device-code prompts fighting your editor's UI.
</Note>

### Switching accounts

```bash theme={null}
npx @useswarm/mcp logout
npx @useswarm/mcp login
```

## Manual setup

If you don't want to use `setup`, you can register the MCP yourself.

### Claude Code

```bash theme={null}
claude mcp add useswarm -- npx @useswarm/mcp
npx @useswarm/mcp login   # one-time, in a terminal
```

### Codex CLI

Edit `~/.codex/config.toml`:

```toml theme={null}
[mcp_servers.useswarm]
command = "npx"
args = ["@useswarm/mcp"]
```

Then sign in once: `npx @useswarm/mcp login`.

### Cursor

Add to `.cursor/mcp.json`:

```json theme={null}
{
  "mcpServers": {
    "useswarm": {
      "command": "npx",
      "args": ["@useswarm/mcp"]
    }
  }
}
```

### VS Code (Copilot)

Add to `.vscode/settings.json`:

```json theme={null}
{
  "mcp": {
    "servers": {
      "useswarm": {
        "command": "npx",
        "args": ["@useswarm/mcp"]
      }
    }
  }
}
```

## Updating

```bash theme={null}
npx @useswarm/mcp@latest setup
```

Same command, with `@latest`. It pulls the newest release from npm and rewrites the path in your editor's config so the next launch picks up the new code. Then restart your editor — MCP processes are spawned at startup, so a running one won't see the update.

Already signed in? `setup` notices, skips the login step, and finishes faster.

### Power-user alternative

```bash theme={null}
npm install -g @useswarm/mcp
useswarm-mcp setup
# upgrades become:
npm update -g @useswarm/mcp
```

The global path is stable, so you only run `setup` once.

## Tools

The MCP exposes five tools that drive a test → fix → retest loop.

### `dev_list_swarms`

List the saved persona swarms tied to your account (personal + active org). Use this to discover swarm IDs before passing one to `dev_test`.

**Returns:** `{ swarms: Array<{ id, name, description, agentCount, personaCount, createdAt }>, count }`.

**Example prompt:**

> Use dev\_list\_swarms to show me which Useswarm persona sets are saved on my account.

### `dev_test`

Tunnel your localhost (or hit a public URL directly) and start an AI agent swarm test.

**Parameters:**

| Parameter         | Required    | Description                                                                                                                                                                         |
| ----------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `targetUrl`       | Yes         | Frontend URL (e.g. `http://localhost:3000`)                                                                                                                                         |
| `goal`            | Yes         | What agents should accomplish (e.g. "complete the signup flow")                                                                                                                     |
| `swarmId`         | Conditional | UUID of a saved swarm (from `dev_list_swarms`). When set, runs that swarm's stored personas instead of generating new ones. Mutually exclusive with `userDescription`/`agentCount`. |
| `userDescription` | Conditional | Target audience (e.g. "first-time SaaS users"). Required when `swarmId` is not set. Drives persona generation.                                                                      |
| `agentCount`      | No          | Number of AI personas to generate (1-20, default 3). Ignored when `swarmId` is set — the saved swarm's count is used.                                                               |
| `backendUrl`      | No          | Separate backend URL (e.g. `http://localhost:8080`)                                                                                                                                 |
| `backendPaths`    | No          | Extra path prefixes for backend routing (e.g. `["/ws", "/v1"]`)                                                                                                                     |
| `maxSteps`        | No          | Cap on actions per agent                                                                                                                                                            |
| `provider`        | No          | `openai` or `anthropic`                                                                                                                                                             |
| `model`           | No          | Model override                                                                                                                                                                      |
| `auth`            | No          | Login credentials or session cookies — see [Authenticated testing](#authenticated-testing)                                                                                          |

**Returns:** `{ batchId, dashboardUrl, _devLoop: { tunnel, nextStep, ... } }`.

**Example prompts:**

> Use dev\_test to test [http://localhost:3000](http://localhost:3000) with goal "sign up and reach the dashboard". The audience is first-time users.

> Use dev\_list\_swarms, then dev\_test on [http://localhost:3000](http://localhost:3000) with goal "complete checkout" using my "B2B Buyers" swarm.

### `dev_watch`

Wait for a test to finish, then return structured findings.

**Parameters:**

| Parameter | Required | Description                                                                                           |
| --------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `batchId` | Yes      | Batch ID returned by `dev_test`                                                                       |
| `wait`    | No       | Poll up to 3 minutes for completion (default `true`); `false` returns immediately with current status |

**Returns:**

```ts theme={null}
{
  status: "completed" | "failed" | "still_running" | ...,
  batchId: string,
  overallUxScore: number | null,
  issueCount: number,
  issues: Array<{
    severity: "critical" | "high" | "medium" | "low",
    title: string,
    description: string,
    recommendation?: string,
    effortSize?: "XS" | "S" | "M" | "L" | "XL",
    userImpact?: string,
    dropOffRisk?: "high" | "medium" | "low" | "none",
    agentCount?: number,
    flaggedBy?: Array<{ personaName, personaIndex, stepNumbers }>
  }>,
  recommendedActions: Array<{ ... }>,  // judge's concrete fix suggestions
  judgeVerdict: { verdict, confidence, evidenceBlocks } | null,
  synthesis: { executiveSummary, positives } | null,
  agentResults: Array<{ personaName, status, uxScore, verdict, ... }>,
  _devLoop: { tunnelClosed, previousTunnelUrl, nextStep }
}
```

Tunnel is torn down automatically when the test completes.

### `dev_status`

Diagnose the MCP setup. Returns auth state, tunnel state, and any active test.

```ts theme={null}
{
  auth: {
    configured: boolean,
    valid: boolean | null,
    apiUrl: string,
    keyHint: string | null,       // last 4 chars
    recovery: string | null       // actionable next step if something's wrong
  },
  tunnel: { active, url, provider, reachable, ... },
  activeTest: { batchId, status, startedAt }
}
```

Call this first when something seems off — `recovery` will name the exact command to run.

### `dev_close`

Tear down the active tunnel manually. Usually not needed — `dev_watch` auto-closes on completion.

## Split frontend/backend

If your app runs the frontend and backend on separate ports, pass `backendUrl` and the MCP spins up a reverse proxy automatically:

> Use dev\_test on [http://localhost:3000](http://localhost:3000) with backend at [http://localhost:8080](http://localhost:8080). Goal: "create a new project".

| Path            | Destination |
| --------------- | ----------- |
| `/api/*`        | Backend     |
| `/auth/*`       | Backend     |
| `/graphql`      | Backend     |
| `/trpc/*`       | Backend     |
| Everything else | Frontend    |

### Custom API paths

```text theme={null}
Test http://localhost:3000 with backend http://localhost:8080
and extra backend paths /ws and /socket.io. Goal: "send a message in the chat".
```

Each entry must start with `/` and be longer than just `/`.

## Authenticated testing

`auth` supports three modes. All accept an optional `startUrl` to land the agent on a specific page after authentication.

If `mode` is omitted, the MCP infers it from which fields you pass:

* `cookies` array → `cookie_injection`
* `signupEmail` → `signup`
* otherwise → `agent_login`

### Mode 1: `agent_login` (username + password)

The agent navigates to `loginUrl` (or the target URL if omitted), fills the credentials, and submits.

```json theme={null}
{
  "auth": {
    "mode": "agent_login",
    "loginUrl": "http://localhost:3000/login",
    "username": "test@example.com",
    "password": "testpass123",
    "startUrl": "http://localhost:3000/dashboard"
  }
}
```

> Use dev\_test on [http://localhost:3000](http://localhost:3000) with goal "edit profile settings". Auth: username [test@example.com](mailto:test@example.com), password testpass123, login URL [http://localhost:3000/login](http://localhost:3000/login).

### Mode 2: `signup` (sub-aliased emails per persona)

For testing sign-up flows where no account exists yet. You supply one base email; each persona registers with a unique `local+<hash>@domain` sub-alias derived from it. All confirmation emails route to your single inbox, but each persona has its own isolated address.

```json theme={null}
{
  "auth": {
    "mode": "signup",
    "signupEmail": "you@yourdomain.com",
    "signupPassword": "optional-shared-password",
    "startUrl": "http://localhost:3000/signup"
  }
}
```

| Field            | Required | Description                                                                                                                                             |
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `signupEmail`    | Yes      | Base email. Each persona-run gets a unique `local+<base36(timestamp)+random>@domain` alias derived from this.                                           |
| `signupPassword` | No       | Shared password the agent uses on every persona's sign-up form. If omitted, the runtime mints a strong random password per persona-run (not persisted). |
| `startUrl`       | No       | URL where the sign-up form lives — agent navigates here first.                                                                                          |

How the alias works:

* The base email `you@yourdomain.com` becomes `you+lwk3a8x7@yourdomain.com`, `you+lwk3a902@yourdomain.com`, etc. — one per persona.
* The timestamp half guarantees the alias can never be replicated in the future; the random half disambiguates personas spawned in the same millisecond.
* The agent is instructed to type that exact alias verbatim.
* The generated alias is persisted to the test run record (`cua_runs.generated_email`) so confirmation emails can be correlated back later.

Use any provider that supports plus-addressing — Gmail, Fastmail, custom domains all work.

> Use dev\_test on [http://localhost:3000](http://localhost:3000) with goal "sign up for a free account". Auth: signup mode, base email [you@yourdomain.com](mailto:you@yourdomain.com).

### Mode 3: `cookie_injection` (skip login entirely)

Inject session cookies and have the agent start already authenticated on a specific page.

```json theme={null}
{
  "auth": {
    "mode": "cookie_injection",
    "cookies": [
      {
        "name": "session_token",
        "value": "abc123...",
        "domain": ".example.com",
        "path": "/",
        "secure": true,
        "httpOnly": true
      }
    ],
    "startUrl": "http://localhost:3000/dashboard"
  }
}
```

Each cookie needs `name`, `value`, `domain` at minimum; everything else is optional. The easiest way to capture cookies is the [Cookie-Editor](https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) browser extension — Export → JSON.

<Note>
  **Always set `startUrl`** for cookie injection. Without it the agent lands on `targetUrl` (often the login page) and won't realize it's already authenticated. The MCP warns about this on stderr, but the agent runs cleaner if you set it explicitly to the post-login destination.

  Cookie domains captured from a different host (e.g. `.production.com`) are automatically rewritten to match the tunnel host. Cookie names, values, and other attributes are preserved unchanged.
</Note>

<Warning>
  Use test-only accounts. Credentials and cookies are sent to the Swarm API over HTTPS but should not be production credentials.
</Warning>

## The dev loop

A typical end-to-end prompt:

> Test my app at localhost:3000. The goal is "complete the checkout flow". If there are issues, fix them and re-test until it passes.

The editor will:

1. Call `dev_test` with sensible defaults.
2. Call `dev_watch` and read the `issues[]` + `recommendedActions[]`.
3. Edit code based on those findings.
4. Call `dev_test` again to verify.
5. Loop until `issues[]` is empty.

For more copy-paste prompts covering smoke tests, auth flows, multi-step loops, and diagnostics, see [Sample Prompts](/mcp/prompts).

## Troubleshooting

### "No API key found" when starting

The MCP couldn't find a key in `~/.useswarm/config.json` or `USESWARM_API_KEY`. Run **either** of these from a terminal and restart your editor:

```bash theme={null}
npx @useswarm/mcp setup    # if you haven't registered yet
npx @useswarm/mcp login    # if already registered
```

### "API 401: Unauthorized" from a tool call

Your stored key was revoked or your account was logged out elsewhere. `dev_status` will show `auth.valid: false`. Fix:

```bash theme={null}
npx @useswarm/mcp login
```

### "cloudflared not found"

Install Cloudflare's tunnel binary — used to expose localhost to Swarm's cloud agents:

```bash theme={null}
# macOS
brew install cloudflared

# Linux
curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
  -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared
```

The MCP falls back to ngrok if Cloudflare is unavailable.

### Agents see only the frontend (API calls 404)

Your app has a separate backend on another port. Re-run with `backendUrl`:

> Use dev\_test on [http://localhost:3000](http://localhost:3000), backend [http://localhost:8080](http://localhost:8080), goal "..."

### Tools aren't showing up in the editor

```bash theme={null}
# verify what's registered
cat ~/.codex/config.toml | grep -A3 useswarm   # Codex
claude mcp list                                # Claude Code

# clean re-register
npx @useswarm/mcp@latest setup
# then quit and re-open the editor
```

## Tunnel providers

The MCP uses **Cloudflare Quick Tunnels** by default (free, no account needed). Falls back to **ngrok** if Cloudflare is unavailable. See the troubleshooting section above for install instructions.

## Framework notes

| Framework                  | Notes                                               |
| -------------------------- | --------------------------------------------------- |
| **Next.js / Nuxt / Remix** | Single port — no `backendUrl` needed                |
| **React + Express**        | Use `backendUrl` for the Express server             |
| **Django / Rails**         | Add the tunnel URL to `ALLOWED_HOSTS` / CORS config |
| **Vite with proxy**        | If Vite proxies API calls, no `backendUrl` needed   |
