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.
What’s inside
Modes
Seeding
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
Everyspin slack auto-provisions a default workspace, an admin user, and a
default app, then prints a bot token and a user token:
auth.test (the real Slack method), POSTed with the token:
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: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 routes —
POST /api/chat.postMessage,conversations.*,reactions.*,users.*,search.messages,auth.test— args in the JSON body (channel,ts,timestamp,users,name, …), exactly how@slack/web-apicalls them. - The
{ ok }envelope — success is{ "ok": true, … }; failures are HTTP 200{ "ok": false, "error": "<code>" }, with cursor pagination underresponse_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_callbackenvelope with inner objects matching Slack field-for-field (message,reaction_added/removed,channel_created,channel_archive,member_joined/left_channel,user_change,presence_change).
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 returns429 + 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
Themcp-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
001_initial_schema.sql — read it to know what’s queryable.