Skip to main content
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: This guide assumes you have a running, seeded clone from the 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:
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 hone env tokens <id>.
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.

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.
1

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 password123dana@acme.test is one:
(/api/auth/* is a clone-operation, so it keeps a plain REST shape rather than the { ok } envelope.)
2

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:
3

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.
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 for the full method surface and parity breakdown.

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): 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-a1b2clone_slack_a1b2) inside the shared Postgres.
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.
Build the server once, then run it with both env vars set on the shared network:

Register it with an MCP client

Point your MCP client at the built server over stdio. For a Claude Desktop-style config:
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 and the query reference:
When the trial is done, hone env reset slack-a1b2 returns the clone to its seeded starting state for the next run.

Where to go next

Seeding

Give your agent a known starting state — fixtures vs AI data.

Slack template

The clone’s API shape, schema, and fidelity to real Slack.

query reference

Read the clone’s database to score what your agent did.

Lifecycle commands

Reset, stop, start, and destroy between trials.