Slack MCP Agents: Split Read-Only from Send
Before you use a Slack MCP agent for channel summaries or mention triage, do not grant read (search/history) and write (send) as one blob. Slack’s official MCP server exposes search, channel/thread reads, send, drafts, reactions, and more on one server—so “connected ⇒ may post” becomes the accidental default.
This post covers only starting read-only, send confirmation UX, and why channel invites matter. No pricing, plans, real token values, or invented Slack UI click-tours. Grounded in the Slack MCP server overview, conversations.history, and groups:history.
How do you start read-only?
One-line answer: Enable read scopes and tools (history, search, membership) first; leave chat:write (and create/send-equivalent tools) for a later phase. Put “summarize and draft only—no send until the user confirms” in agent rules.
The official OAuth scope table draws a clean boundary:
| Intent | Representative scopes (docs) | Mode |
|---|---|---|
| Read channel/thread | channels:history, groups:history, mpim:history, im:history | Read |
| Search messages/channels | search:read.public and other search:read.* | Read |
| List channels/members | channels:read, groups:read, … | Read |
| Send messages | chat:write | Write |
| Create conversations | channels:write / groups:write, … | Write |
| Reactions | reactions:write | Write |
Three layers make “read-only” real in practice:
- Minimal scopes — Omit send/create scopes at install time, or approve a read-oriented app/client first. Official MCP expects admins to approve and manage client integrations.
- Tool allowlists — Disable or deny send/create/reaction tools in the MCP client. Community servers often default post tools off or offer a
READ_ONLYflag; with the official server, use the client’s tool allow/deny for the same effect. - Agent policy text — “Slack: read, search, summarize, draft only. Call send-equivalent tools only after an explicit user OK.” Policy only—never paste token strings into rules.
# Slack MCP — phase 1 (read-only)
- Allowed: search, read channel/thread history, list channels/members, draft text in chat (do not post).
- Forbidden until explicit user OK: send message, create channel, add reaction, upload+share that posts.
- Never paste Slack tokens into rules, commits, or chat.
Channel summaries, decision hunting, and mention context are valuable at this stage. Turn on send after the team trusts draft quality and confirmation UX.
What does send confirmation UX look like?
One-line answer: Even after write is enabled, do not let the agent call chat:write immediately. Lock the order: draft → human confirm → send, via rules and tool gates.
Official MCP separates Send messages from Draft messages (draft, format, and preview inside the AI client). Use that split as the UX axis:
| Step | Agent | Human |
|---|---|---|
| 1. Gather | Search/history for evidence | Scope (channels, window) |
| 2. Draft | Write in chat/draft tools | Tone, destination, mentions |
| 3. Confirm | Ask “Post this draft?” | Explicit approval (channel + body) |
| 4. Send | Only then call send | Check result / thread |
Minimum rules for confirmation UX:
- Deny by default: Keep send tools off the allowlist, or require a per-call approval gate.
- Restate destination: Before send, re-show channel/thread identity and a body summary, then ask for approval. A vague “just post it” is not enough.
- Stricter for high-blast channels:
#general, customer-facing, and on-call channels need an extra confirm even at draft time. - No blind retries: After rate-limit or permission errors, do not auto-repost. Docs note MCP tools share the same rate limits as the Web API.
Drafts should live in the agent chat until approval; no Slack tool call should fire before that. Ambiguous asks like “share the summary to the channel” should produce a draft + confirm first.
Why does a channel invite matter?
One-line answer: Scopes alone are not enough. History and membership-based tools assume the principal is a member of that conversation. Private channels especially stay blocked without an invite.
Official MCP “List user channels” lists channels you belong to. conversations.history-family methods depend on token type, scopes, and membership. The groups:history scope description is about content in private channels your app has been added to. Without membership you typically hit errors such as not_in_channel.
Keep scopes and invites on the same checklist:
- Name the channels first — Only the summary targets. Workspace-wide search needs
search:read.*plus a separate policy. - Invite the app/bot (or OAuth user) into those channels — Private channels usually need a member to invite. Public channels often still need membership for reads.
- Verify via “channels I’m in” — Use list-channels-style MCP tools. If targets are missing, suspect invite before scopes.
- Send = membership +
chat:write+ confirm UX — Do not enable send for channels you cannot read.
Treat invited channels = the permission boundary before asking the agent to “read everything.” Adding invite to the workflow when new project channels appear cuts empty-summary debugging later.
What should you remember?
Slack MCP packs read and write on one server. Start with history, search, and drafts; turn on chat:write only with confirmation UX. Independently of scopes, channel invite (membership) sets what can be read or posted. Keep token values out of rules, chat, and commits. Pricing, plans, and click-path tours are out of scope here.
Where are the official sources?
- Slack MCP server overview — tools, OAuth scopes, draft/send, admin approval
- conversations.history — history, membership, scopes
- groups:history — private channels the app was added to
- Using the Conversations API — membership and scope filters