Skip to main content
The Slack template (apps/slack-clone, name slack) is the reference clone: a NestJS backend on Postgres, an optional React frontend, and an MCP server. Its data plane is the real Slack Web API — RPC method names (chat.postMessage), args in the request body, the { "ok": true, … } envelope, Slack-format object ids (T…/U…/C…/D…/G…/A…), per-method rate-limit tiers, and byte-identical Events API webhooks. Auth works two ways: a JWT for the web UI, and Slack-style opaque bearer tokens (xoxb-… / xoxp-…) for programmatic access. It’s faithful enough at the wire-format level that a client built for @slack/web-api can drive it.
Spinning a clone auto-provisions a default app and prints a bot token and a user token you can use immediately — see Apps and tokens.

What’s inside

Modes

Seeding

AI seeding generates users, channels, and messages (the template’s declared entities) and creates them through real signup / conversations.create / chat.postMessage calls. See Seeding.

Apps and tokens

Real Slack apps authenticate with bearer tokens (xoxb- for bots, xoxp- for users) and are configured by an app manifest. The clone mirrors that: it has a first-class app entity with a stored manifest, opaque bearer tokens, and manifest scopes that are enforced on every app-token call.

Tokens you get for free

Every spin slack auto-provisions a default workspace, an admin user, and a default app, then prints a bot token and a user token:
Use either as a bearer token against the Web API — no signup/login step. The identity check is auth.test (the real Slack method), POSTed with the token:
A bot token acts as the app’s bot user (messages it posts are attributed to the bot, is_bot: true); a user token acts as the admin user. This is the fastest way to point an agent at a clone — see Connect your agent.

Get the tokens again later

Tokens are stored locally so you can reprint them any time:
See the tokens reference.

Create your own app

Post a Slack-shaped manifest to create additional apps (each gets its own bot user and bot token). App management is a clone-operation, so it uses a plain REST shape, not the { ok } envelope:
App-token scopes are enforced. Each Web API method declares the Slack scope it needs (chat:write, reactions:write, channels:history, search:read, …); a token missing it gets 403 { "ok": false, "error": "missing_scope" }. JWT / web-UI humans are exempt. OAuth install and auth.revoke are not implemented yet. Clones bind to localhost by default, so a token is not reachable off your machine unless you --expose.

API shape — read this before pointing an agent at it

The data plane is Slack’s RPC Web API, so it matches at the wire-format level, not just the capability level:
  • RPC method routesPOST /api/chat.postMessage, conversations.*, reactions.*, users.*, search.messages, auth.test — args in the JSON body (channel, ts, timestamp, users, name, …), exactly how @slack/web-api calls them.
  • The { ok } envelope — success is { "ok": true, … }; failures are HTTP 200 { "ok": false, "error": "<code>" }, with cursor pagination under response_metadata.next_cursor.
  • Slack-format ids — every object id is minted in Slack’s grammar at the DB layer (T… team, U… user, C… channel, D… IM, G… mpim, A… app). No UUIDs leak into payloads.
  • Rate-limit tiers — each method enforces its Slack tier; a breach returns HTTP 429 + Retry-After + { "ok": false, "error": "ratelimited" } (see below).
  • Byte-identical events — outgoing webhooks deliver the full Slack Events API event_callback envelope with inner objects matching Slack field-for-field (message, reaction_added/removed, channel_created, channel_archive, member_joined/left_channel, user_change, presence_change).
A quick tour against a running clone:

Implemented method surface

The data plane implements 27 Slack methods across messaging, conversations, reactions, users, search, and auth: Whole Slack families outside this core (files.*, pins.*, bookmarks.*, dnd.*, views.*, usergroups.*, admin.*, …) are unimplemented by design — this is an MVP+Beta vertical slice, not the full ~130-method Web API.

Rate-limit tiers

Each method is bound to its real Slack tier and returns 429 + Retry-After + { "ok": false, "error": "ratelimited" } on breach:

Control plane (clone-operations — intentionally REST)

Operations that real Slack configures via app manifests / OAuth rather than runtime Web API methods keep plain REST shapes and are not wrapped in the { ok } envelope:

MCP server

The mcp-server exposes the eight reference Slack MCP tools with their exact names and argument names — slack_list_channels, slack_post_message, slack_reply_to_thread, slack_add_reaction, slack_get_channel_history, slack_get_thread_replies, slack_get_users, slack_get_user_profile — with tool descriptions restored character-for-character to the reference server (modelcontextprotocol/servers-archived), plus a few clearly-labeled slack_* extensions (slack_auth_test, slack_search_messages, slack_open_dm). It binds to one SLACK_MCP_TOKEN (xoxb/xoxp), resolves it to identity + workspace + scopes, and acts strictly as that identity — gating each tool on its scope and scoping every query to the token’s workspace. The template ships an API_PARITY.md scored by the strict /verify-api slack workflow, which fetches Slack’s live docs every run and treats identifier, envelope, id-format, rate-limit tier, event-schema, and MCP-verbatim conformance as blocking checks. Check apps/slack-clone/API_PARITY.md for the per-method breakdown.
The verify command will fold this kind of live fidelity scoring into the CLI. Until then, API_PARITY.md and the /verify-api slack workflow are how fidelity is tracked.

Inspect a running clone

The schema (channels, messages, users, workspaces, apps, …) comes straight from 001_initial_schema.sql — read it to know what’s queryable.