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

# Connect your agent

> Point your agent at a running clone — over the Slack Web API or the MCP server.

A clone is only useful once your agent is acting against it. There are two
surfaces to connect to, and you can use either or both:

| Surface        | What it is                                                                                                                                                  | Use it when                                                                                           |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **HTTP API**   | The clone's HTTP API at the endpoint `spin` printed. For Slack this **is** the real Slack Web API — RPC methods, the `{ok}` envelope, opaque bearer tokens. | Your agent already speaks the product's API, or you want full control. The default, fully-wired path. |
| **MCP server** | A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes the clone as a set of tools.                                                | Your agent is an MCP client (Claude Desktop, an SDK agent) and you want tool-shaped access.           |

This guide assumes you have a running, seeded clone from the
[Quickstart](/quickstart). Everything below uses the Slack template, `slack-a1b2`,
and port `3001` — substitute your own clone id and endpoint.

## Option A — the Slack Web API

`spin` hands you a base URL and two ready-to-use tokens:

```
  ✓ slack-a1b2  (api)  http://127.0.0.1:3001/api  ready in 38.2s
    db clone_slack_a1b2 · env -
    bot token   xoxb-…
    user token  xoxp-…
```

That base URL **is** your agent's target, and it's the real Slack Web API: RPC
method routes (`POST /api/chat.postMessage`), args in the JSON body, and the
`{ "ok": true, … }` envelope. The clone expects real auth — you have two ways in.

### Fastest — the provisioned bot token

Every `spin slack` auto-provisions a default workspace, admin user, and app, then
prints a **bot token** (`xoxb-…`) and a **user token** (`xoxp-…`). No signup or
login; send one as a bearer token. Reprint them any time with
[`asym tokens <id>`](/cli/tokens).

```bash theme={null}
B=http://127.0.0.1:3001/api
TOKEN=$(asym tokens slack-a1b2 --json | jq -r .botToken)

# Confirm identity with auth.test (a real Slack method)
curl -s -X POST $B/auth.test -H "authorization: Bearer $TOKEN"
# → { "ok": true, "user_id": "5f3a8b2c-…", "team_id": "9d1e7a4f-…", "user": "admin" }
# (ids are internal UUIDs — a known cosmetic deviation from Slack's `U…`/`T…`)

# List conversations
curl -s -X POST $B/conversations.list -H "authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' -d '{"types":"public_channel"}'
```

A **bot token** acts as the app's bot user (messages it posts show `is_bot: true`);
a **user token** acts as the admin. App-token **scopes are enforced** — a method
whose scope the token lacks returns `{ "ok": false, "error": "missing_scope" }` at
**HTTP 200**, like every Slack-method error (the clone never returns 4xx/5xx on a
method route — the one non-200 is `429 ratelimited`).
See [Apps and tokens](/templates/slack#apps-and-tokens).

### Per-user — JWT login

When you want to act as a specific seeded human (e.g. to test multi-user flows),
log in for a JWT instead. JWT/web-UI identities are exempt from scope enforcement,
and the access token is used exactly like the bearer token above.

<Steps>
  <Step title="Authenticate">
    Log in as a seeded user (or sign up your own) to get an access token. The
    `acme-corp` fixture creates six users who all share the password
    `password123` — `dana@acme.test` is one:

    ```bash theme={null}
    TOKEN=$(curl -s -X POST http://127.0.0.1:3001/api/auth/login \
      -H 'content-type: application/json' \
      -d '{"email":"dana@acme.test","password":"password123"}' \
      | jq -r .accessToken)
    ```

    (`/api/auth/*` is a clone-operation, so it keeps a plain REST shape rather
    than the `{ ok }` envelope.)
  </Step>

  <Step title="Act">
    Now your agent drives the Web API like any client — list channels, then post
    a message. Take a channel id from `conversations.list` (the clone uses internal
    UUID ids — a known cosmetic deviation from Slack's `C…` that doesn't change call
    signatures), and `chat.postMessage` takes its args in the body:

    ```bash theme={null}
    B=http://127.0.0.1:3001/api
    curl -s -X POST $B/conversations.list -H "authorization: Bearer $TOKEN" \
      -H 'content-type: application/json' -d '{"types":"public_channel"}'

    curl -s -X POST $B/chat.postMessage -H "authorization: Bearer $TOKEN" \
      -H 'content-type: application/json' \
      -d '{"channel":"<channel-id from conversations.list>","text":"hello from my agent"}'
    ```
  </Step>

  <Step title="Point your agent at the base URL">
    Hand `http://127.0.0.1:3001/api` to your agent as its API base, with the
    token in its auth header. Every action it takes lands in the clone's own
    database — which is exactly what you'll read back to score the run.
  </Step>
</Steps>

<Tip>
  The Slack clone matches Slack at the **wire-format level**, not just the
  capability level — RPC method names (`chat.postMessage`), the `{ ok }` envelope,
  error-as-HTTP-200 semantics, and per-method rate-limit tiers. (Object ids are the
  one known deviation — internal UUIDs rather than `C…`/`U…` — which doesn't change
  call signatures.) A client built for `@slack/web-api` can drive it. See the
  [Slack template](/templates/slack#api-shape-read-this-before-pointing-an-agent-at-it)
  for the full method surface and parity breakdown.
</Tip>

## Option B — the MCP server

The Slack template ships an MCP server (`apps/slack-clone/mcp-server`) that
exposes the clone as the reference Slack MCP tools — handy for MCP-native agents
that prefer tools over raw HTTP. It speaks stdio, connects to the clone's Postgres
database, and is **bound to a single app token** so every tool acts strictly as
that identity, inside that workspace, limited to that token's scopes.

The tools it exposes (verbatim names from the
[reference Slack MCP server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/slack)):

| Tool                        | Does                              |
| --------------------------- | --------------------------------- |
| `slack_list_channels`       | List channels in the workspace.   |
| `slack_post_message`        | Post a message to a channel.      |
| `slack_reply_to_thread`     | Reply in a thread.                |
| `slack_add_reaction`        | React to a message.               |
| `slack_get_channel_history` | Read a channel's recent messages. |
| `slack_get_thread_replies`  | Read a thread's replies.          |
| `slack_get_users`           | List workspace users.             |
| `slack_get_user_profile`    | Look up one user's profile.       |

Plus a few clearly-labeled extensions: `slack_auth_test`, `slack_search_messages`,
`slack_open_dm`.

### Wire it up

The MCP server needs two things: a **`DATABASE_URL`** pointing at the clone's
database, and a **`SLACK_MCP_TOKEN`** — the bot (`xoxb-…`) or user (`xoxp-…`) token
from `spin` that fixes its identity. The clone's database is named after its id
(`slack-a1b2` → `clone_slack_a1b2`) inside the shared Postgres.

<Warning>
  Shared Postgres deliberately publishes **no host port** — clones reach it over the
  `asym-shared` Docker network. So a process on your host can't connect to
  `localhost:5432`. Run the MCP server **on the `asym-shared` network** (as a
  container, or via `docker compose`), where the database is reachable as host
  `postgres`. The MCP server is **not** auto-provisioned by `spin` today — you run
  it yourself.
</Warning>

Build the server once, then run it with both env vars set on the shared network:

```bash theme={null}
# Build the MCP server
cd apps/slack-clone/mcp-server && bun install && bun run build

# DATABASE_URL reaches the clone's db as host `postgres` on asym-shared;
# SLACK_MCP_TOKEN is a bot/user token from `asym tokens slack-a1b2`.
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/clone_slack_a1b2 \
SLACK_MCP_TOKEN=xoxb-… \
  node dist/index.js
```

### Register it with an MCP client

Point your MCP client at the built server over stdio. For a Claude Desktop-style
config:

```json theme={null}
{
  "mcpServers": {
    "slack-clone": {
      "command": "node",
      "args": ["/abs/path/to/apps/slack-clone/mcp-server/dist/index.js"],
      "env": {
        "DATABASE_URL": "postgresql://postgres:postgres@postgres:5432/clone_slack_a1b2",
        "SLACK_MCP_TOKEN": "xoxb-…"
      }
    }
  }
}
```

Your agent now sees `slack_post_message`, `slack_list_channels`, and the rest as
tools — each operating on the clone's real data, gated by the bound token's scopes.

## Score the run

However your agent connected, its work is now rows in the clone's database. Read
them back to grade the run — see [Inspect what happened](/quickstart#5-inspect-what-happened)
and the [`query` reference](/cli/query):

```bash theme={null}
asym query slack-a1b2 "select count(*) from messages"
asym query slack-a1b2 "select user_id, text from messages order by created_at" --json
```

When the trial is done, `asym reset slack-a1b2` returns the clone to its seeded
starting state for the next run.

## Where to go next

<CardGroup cols={2}>
  <Card title="Seeding" icon="seedling" href="/concepts/seeding">
    Give your agent a known starting state — fixtures vs AI data.
  </Card>

  <Card title="Slack template" icon="slack" href="/templates/slack">
    The clone's API shape, schema, and fidelity to real Slack.
  </Card>

  <Card title="query reference" icon="database" href="/cli/query">
    Read the clone's database to score what your agent did.
  </Card>

  <Card title="Lifecycle commands" icon="rotate" href="/cli/lifecycle">
    Reset, stop, start, and destroy between trials.
  </Card>
</CardGroup>
