Two-way Slack channel for Claude Code via Socket Mode + MCP
View the Project on GitHub jeremylongshore/claude-code-slack-channel
Two-way Slack channel for Claude Code — chat from Slack DMs and channels, approve tool calls from your phone.
A claude/channel implementation for Slack. Socket Mode (outbound WebSocket, no public URL) bridges Slack into a running Claude Code session via MCP stdio. Permission relay with Block Kit buttons, per-thread session isolation, hash-chained tamper-evident audit journal, policy-gated MCP tools, real-time audit projection to Slack threads, and a five-layer prompt-injection defense. Three runtime options: Bun, Node.js, Docker.
Links: GitHub · Gist One-Pager · Release Notes
If you’re using Claude Code, you’re chained to the terminal. Step away from your desk and the conversation stops — worse, Claude may be waiting for permission to run a tool, and you can’t approve it from your phone. Your phone has Slack, but Slack can’t talk to your Claude session.
Anthropic shipped channels for Telegram and Discord, but not yet for Slack — the tool most teams already live in. The channel protocol (claude/channel) is documented and ready for community implementations. Slack is the obvious next channel — and this project fills that gap, including the permission relay protocol for remote tool approvals.
A two-file MCP server (server.ts ~1100 lines of stateful wiring, lib.ts ~915 lines of pure functions) that connects Slack to Claude Code bidirectionally. Inbound messages arrive via Socket Mode (Slack’s outbound WebSocket — no public URL, works behind any firewall). Outbound replies go through the Slack Web API. Permission prompts render as Block Kit messages with Allow/Deny/Details buttons — tap your phone to approve a tool call from anywhere.
Security is defense-in-depth: inbound gate drops ungated messages before MCP, outbound gate restricts replies to delivered (channel, thread) pairs, file exfiltration guard blocks state-directory leaks (including sibling-path misconfigurations), system prompt hardening tells Claude to refuse manipulation attempts from messages, and tokens are locked down (0o600, atomic writes, never logged). Every security-relevant event appends to a hash-chained audit journal for tamper-evident forensics.
| Aspect | Details |
|---|---|
| Who | Developers using Claude Code who collaborate via Slack |
| What | MCP channel server — pushes Slack events into Claude Code sessions, replies back, relays permission prompts, isolates state per thread |
| Where | Runs locally (your machine), connects to Slack via Socket Mode WebSocket |
| When | AFK coding — approve tool calls from your phone, review PRs from the couch, pair with Claude from any device with Slack |
| Why | Only Slack channel for Claude Code. Permission relay with Block Kit. No public URL. Five-layer prompt-injection defense. Tamper-evident audit journal. Real-time audit projection to Slack. |
| Layer | Technology | Purpose |
|---|---|---|
| Runtime | Bun / Node.js / Docker | Flexible execution — pick your preference |
| Protocol | MCP (stdio) | Standard Claude Code channel transport |
| Connection | @slack/socket-mode v2 | Outbound WebSocket to Slack — no public URL |
| API | @slack/web-api v7 | Send messages, upload files, add reactions, Block Kit interactions |
| Validation | zod v3 | Schema validation for permission relay + audit journal |
| Security | Custom gate + allowlist + policy engine | 5-layer defense: inbound gate, outbound gate, exfiltration guard, system prompt hardening, token lockdown |
| Auditability | Hash-chained append-only journal + Slack projection | Tamper-evident log of every gate decision, policy evaluation, and session transition — with optional real-time Slack visibility |
claude-code-slack-channel is a production-oriented MCP server that bridges Slack workspaces to Claude Code sessions. The implementation is split across server.ts (~1100 lines of stateful runtime wiring) and lib.ts (~915 lines of pure, testable functions), with policy.ts, journal.ts, and supervisor.ts providing the policy engine, audit log, and session state machine. Four runtime dependencies (@modelcontextprotocol/sdk, @slack/web-api, @slack/socket-mode, zod) — no frameworks, no middleware, no build step for Bun.
v0.7.0 adds per-channel audit projection: operators configure audit: 'off' |
'compact' |
'full' per channel. When enabled, pre-execution audit receipts appear in Slack threads showing tool name, decision, and (in full mode) redacted input preview. The authoritative hash-chained log is unchanged — projection is best-effort visibility, not the record. Memory-bounded via a 500-entry LRU on the auditReceipts map. Self-echo regression tests ensure projected receipts don’t re-enter the gate. ~500 tests. |
v0.6.0 wired the declarative policy engine into production: evaluate() runs on every tool-call permission request, routing to auto_allow / deny / require_human / default_human. Multi-approver quorum with NIST two-person integrity — votes accumulate on verified user_id, same human cannot double-satisfy quorum. Four new journal EventKinds: policy.allow, policy.deny, policy.require, policy.approved. Boot-time linter warns on overly broad auto_approve rules. ~471 tests.
v0.5.1 wires the supervisor and journal into production: SessionSupervisor boots at startup, activate() fires on every inbound message, idle reaper runs on 60s interval, and 10 of 19 journal EventKinds now emit (gate.*, session.*, exfil.block, system.*). Six security fixes from the pre-release audit. ~430 tests.
v0.5.0 landed the big-picture redesign: per-thread session isolation, hash-chained tamper-evident audit journal, policy engine with monotonicity invariant + shadow-rule linter, thread-scoped outbound gate, and slack/list_sessions MCP tool.
| Category | Technology | Version | Purpose |
|---|---|---|---|
| Runtime | Bun | 1.x | Primary execution runtime (also supports Node.js via tsx) |
| Protocol | @modelcontextprotocol/sdk | 1.27.x | MCP server + stdio transport |
| Connection | @slack/socket-mode | 2.0.x | Outbound WebSocket to Slack (no public URL) |
| API | @slack/web-api | 7.15.x | Slack REST API (messages, files, reactions, interactions) |
| Validation | zod | 3.25.x | Schema validation for permission relay + audit journal events |
| Language | TypeScript | 5.9.x | Strict mode, type-checked |
| Container | Docker (oven/bun:1-slim) | — | Optional isolated runtime |
Slack workspace (cloud)
↕ WebSocket (Socket Mode — outbound only, no public URL)
server.ts + lib.ts + policy.ts + journal.ts + supervisor.ts (local MCP server)
↕ stdio (MCP transport)
Claude Code session
| Layer | Function | Implementation |
|---|---|---|
| Inbound Gate | Drop unauthorized messages | gate() checks allowFrom and channel opt-in; bot messages dropped by default with per-channel allowBotIds opt-in |
| Outbound Gate | Restrict reply targets | assertOutboundAllowed() gates on (channel, thread_ts) delivered pairs |
| File Exfiltration Guard | Block state-directory uploads | assertSendable(): allowlist roots + basename/parent denylist + state-root denylist + symlink resolution |
| System Prompt Hardening | Refuse manipulation in messages | Peer-bot messages flagged as same-risk as human |
| Token Security | Protect credentials | 0o600 permissions, atomic writes, never logged |
Every security-relevant event appends to a hash-chained audit journal (audit.log) with per-event fsync. verifyJournal() validates integrity.
| Metric | Value |
|---|---|
| Test Coverage | ~500 tests, security-critical functions |
| TypeScript | Strict mode, zero errors |
| Dependencies | 4 production deps |
| Lines of Code | ~2000 total |
| CI | GitHub Actions — typecheck, test, CodeQL, Gemini review, OpenSSF Scorecard |
| Default DM Policy | allowlist (restrictive) |
What’s Working
claude/channel + claude/channel/permission capabilitiesverifyJournal() integrity checkauto_approve / deny / require_approval effectsRoadmap (v0.8.0+)
pairing.accepted, pairing.expiredargEquals / pathPrefix predicates on policy rules/slack-channel:policy skill)allowBotIds (v0.3.1, v0.4.0)