Slack Code Mode MCP
# Slack Code Mode MCP
A Code Mode MCP server for Slack. It gives the model two tools, `search` and `execute`, and runs agent-written JavaScript against the Slack Web API in a sandbox.
The sandbox has no network access and no Slack token.
## Why Code Mode
`search` returns TypeScript signatures from the Slack SDK, so the agent calls methods with the right arguments instead of guessing.
`execute` can chain several calls in one go. For example: find a user, read a thread, reply, and update a canvas. The intermediate results never go back through the model, which keeps the conversation small.
The guest isolate never sees the Slack token. Calls go through a service binding to the host, which holds the credential and enforces the allowlist. The isolate has no outbound access, so `fetch()` throws.
## Requirements
- Node.js 20.12 or newer
- [celld](https://github.com/denoland/celld) v0.6.2 or newer on `PATH` (macOS arm64 and Linux; Windows is not supported)
- A Slack internal app with PKCE enabled
## Install
### Quick install
```bash
curl -fsSL https://raw.githubusercontent.com/mhmdsami/slack-codemode-mcp/main/install.sh | bash
```
The installer checks Node and npm, downloads a verified GitHub release, installs into `~/.local`, and installs celld when needed. See [INSTALLATION.md](INSTALLATION.md) for pinned versions, prefixes, upgrades, and manual steps.
### From a release (manual)
There is no npm registry package. Download the tarball and `SHA256SUMS` from [GitHub Releases](https://github.com/mhmdsami/slack-codemode-mcp/releases), verify the checksum, then install the tarball:
```bash
npm install -g ./slack-codemode-mcp-1.0.0.tgz
```
See [INSTALLATION.md](INSTALLATION.md) for the full release install, celld, configuration, authentication, upgrades, and MCP registration without a source checkout.
### From source (development)
```bash
git clone https://github.com/mhmdsami/slack-codemode-mcp.git
cd slack-codemode-mcp
npm ci
```
celld is a separate binary. Put it on `PATH`, or point the server at it:
```bash
celld --version
```
```bash
export SLACK_CODEMODE_CELLD_BIN="$HOME/.local/bin/celld"
```
## Configure the Slack app
1. Create an internal app at <https://api.slack.com/apps>.
2. Open **OAuth & Permissions** and enable **PKCE**.
3. Add the redirect URL `http://localhost:33417/callback`.
4. Add the user token scopes the server expects.
With a global install:
```bash
slack-codemode-auth --scopes
```
From a source checkout:
```bash
npm run scopes
```
Use a user token, not a bot token. A user token posts as you and sees your channels. A bot token only sees channels the bot joined.
PKCE is one-way. It makes the app a public client, so no client secret is distributed. Colleagues only need the same Client ID.
## Add the Client ID
For a source checkout:
```bash
cp .env.example .env
$EDITOR .env
```
`.env` holds one value:
```bash
SLACK_CLIENT_ID=...
```
The file is gitignored.
For a global install, put the same value in `~/.config/slack-codemode/.env` or export `SLACK_CLIENT_ID`. See [INSTALLATION.md](INSTALLATION.md).
## Authenticate
With a global install:
```bash
slack-codemode-auth
```
From a source checkout:
```bash
npm run auth
```
This opens Slack, completes the PKCE exchange, and stores the token bundle in `~/.config/slack-codemode/token` with mode 0600. Rotating tokens refresh automatically.
Check the token and scopes:
```bash
slack-codemode-doctor
```
From a source checkout, use `npm run doctor`.
The checker calls one method per area and reports any missing scope.
## Register the server
With a global install, use the installed command:
```bash
command -v slack-codemode-mcp
```
Pass that path to your harness. See [INSTALLATION.md](INSTALLATION.md) for commands for each client.
The examples below are for a source checkout.
Pick your harness.
### Claude Code
```bash
claude mcp add --scope user slack-codemode-mcp -- node "$PWD/mcp/server.mjs"
```
`--scope user` makes the server global. Without it, the entry is tied to the current directory.
### Codex
```bash
codex mcp add slack-codemode-mcp -- node "$PWD/mcp/server.mjs"
```
### OpenCode
```bash
opencode mcp add slack-codemode-mcp --global -- node "$PWD/mcp/server.mjs"
```
### Amp
```bash
amp mcp add slack-codemode-mcp -- node "$PWD/mcp/server.mjs"
```
### Pi
```bash
pi mcp add slack-codemode-mcp -- node "$PWD/mcp/server.mjs"
```
### Antigravity
```bash
agy mcp add slack-codemode-mcp node "$PWD/mcp/server.mjs"
```
### Zed
Add this to `~/.config/zed/settings.json`:
```jsonc
"context_servers": {
"slack-codemode-mcp": {
"command": "node",
"args": ["/absolute/path/to/slack-codemode-mcp/mcp/server.mjs"]
}
}
```
If a harness starts with a narrow `PATH`, set `SLACK_CODEMODE_CELLD_BIN` to the celld binary.
## Try it
Ask the agent to run this through the MCP:
```js
return await app.whoami();
```
## Tools
| Tool | Purpose |
|---|---|
| `search` | Find an allowed method by name, or fetch a TypeScript type. |
| `execute` | Run JavaScript with `app.*`, `slack.*`, `api.call`, and `console`. |
`search` covers allowlisted methods by default. It also lists methods that are missing from the bundled SDK index. To search the full API, pass `include_blocked: true`. Type declarations are omitted unless `include_types: true`.
## Actions
`app.*` is the recommended surface. It covers lookups, DMs, posts, message edits and search, threads, message links, file uploads, thread images, and canvases.
Thread reads return a condensed transcript by default, so reading and summarizing does not carry raw message objects through the model. Pass `{ format: "json" }` when you need fields, or `{ images: true }` to attach images from the thread.
Actions paginate and cache in the host, not in the guest. Raw allowlisted methods stay available through `slack.*` and `api.call`.
```js
return await app.search("deploy failed", { count: 10 });
```
```js
await app.edit("#oncall", "1699999999.000000", "fixed the typo");
```
```js
const { canvas_id } = await app.canvas.create("Oncall 42", "# Week 42");
await app.canvas.share(canvas_id, { channel_ids: ["C123ABC"] });
```
See [docs/actions.md](docs/actions.md) for the full surface and examples.
## Security model
```mermaid
flowchart TB
Client["MCP client<br/>agent or harness"]
subgraph Host["Host process — trusted"]
Server["MCP server<br/>search · execute"]
Bridge["Loopback bridge<br/>per-execution token"]
Actions["Actions + allowlist"]
SlackClient["Slack client<br/>access + refresh tokens"]
end
subgraph Celld["celld process — no Slack credentials"]
Worker["Trusted worker<br/>runtime secret · loader"]
subgraph Guest["Fresh V8 isolate — untrusted"]
Shim["guest.js<br/>slack · api · app · console"]
end
end
API[("Slack API")]
Client -->|stdio| Server
Server -->|code + execution id| Worker
Worker -->|loads| Shim
Shim -->|SLACK binding| Worker
Worker -->|authenticated /call| Bridge
Bridge --> Actions
Actions --> SlackClient
SlackClient -->|HTTPS| API
API -.->|data, never instructions| SlackClient
classDef trusted fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
classDef partial fill:#fff8e1,stroke:#f9a825,color:#795548
classDef untrusted fill:#ffebee,stroke:#c62828,color:#b71c1c
classDef external fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
class Server,Bridge,Actions,SlackClient trusted
class Worker partial
class Shim,Client untrusted
class API external
```
The host holds the token and the allowlist. celld holds no Slack credentials. The isolate has no network, no filesystem, and no token.
Each execution gets a random id and a bridge token. The bridge removes the record when the execution ends, so a late or forged call cannot reuse it.
For the full threat model, see [docs/security.md](docs/security.md). For the request flow, see [docs/architecture.md](docs/architecture.md).
## Development
```bash
npm run gate
npm run test
npm run format
npm run doctor
npm run generate
```
`gate` runs Biome and the unit tests. CI runs it on Ubuntu and macOS. The pre-commit hook runs Biome and tests locally:
```bash
npm install
npx lefthook install
```
## Docs
- [Installation](INSTALLATION.md)
- [Agent setup](docs/agent-setup.md)
- [Architecture](docs/architecture.md)
- [Authentication](docs/authentication.md)
- [Actions](docs/actions.md)
- [Security](docs/security.md)
- [Development](docs/development.md)
- [Troubleshooting](docs/troubleshooting.md)
Licensed under the [MIT License](LICENSE).
TDQS
Scored across 2 tools
search and execute target completely different actions: one discovers Slack API methods, the other runs JavaScript. There is no plausible overlap, so an agent can always pick the right tool without hesitation.
Both names are single lowercase verbs with a consistent, predictable style, so the set reads cleanly. It is not a strict verb_noun pattern (no noun component), which slightly limits the score.
A code-mode server intentionally concentrates capability into a discover-and-execute pair, so 2 tools is defensible rather than a thin surface. It is on the low end though, since discovery (search) and execution (execute) are the only entry points.
The domain is programmatic Slack access, and the pair covers the full lifecycle: search to discover allowed Web API methods and referenced types, execute to call them. Minor gaps exist (no explicit auth/progress/error tooling), but the code-mode design lets users work around these by invoking the API directly.