·

What Is Slack MCP

Learn what Slack MCP is and how it lets your AI agent read and post messages and manage channels.

Slack MCP servers give your AI coding agent a real mailbox into your team's workspace: it can read what people are saying, post what it just did, and stitch a scattered thread into a decision. That sounds simple until you actually wire it up to a workspace with 40 channels and a #incidents channel that pages people at 3 a.m. — then every scope you grant and every tool you expose becomes a decision with consequences.

Two implementations dominate real deployments today. The reference @modelcontextprotocol/server-slack (published under the modelcontextprotocol/servers GitHub org, now maintained in the archived-but-still-widely-used community fork) wraps the official Slack Web API with a bot token and a fixed OAuth scope list. The second, slack-mcp-server by Aleksei Korotovskikh (github.com/korotovsky/slack-mcp-server), takes a different route entirely — it can authenticate as a real user session (xoxc/xoxd tokens lifted from a logged-in browser) instead of installing a bot app. Both are legitimate, but they carry very different risk profiles, and picking the wrong one for your situation is the single most common mistake teams make in month one.

This topic covers the tool surface, the auth model, what's realistic to automate, and — because this is the part everyone skips until an agent spams #general — how to keep an LLM from becoming the coworker nobody wants on the team.


Core Slack MCP Tools: Channels, Messages, Threads, Search, and Users

The reference server exposes eight tools that map almost one-to-one onto Slack Web API methods. Knowing the underlying API call matters because it tells you the rate limit tier and the exact scope required — the MCP tool name alone won't tell you either.

slack_list_channels      -> conversations.list
slack_post_message       -> chat.postMessage
slack_reply_to_thread    -> chat.postMessage (with thread_ts)
slack_add_reaction       -> reactions.add
slack_get_channel_history -> conversations.history
slack_get_thread_replies -> conversations.replies
slack_get_users          -> users.list
slack_get_user_profile   -> users.profile.get

Community servers extend this list. korotovsky/slack-mcp-server adds channels_add_message (its own naming for posting), plus, notably, a full-text search_messages tool backed by search.messages — the official reference server doesn't ship search at all, which surprises people who assume "MCP for Slack" automatically means "ask the agent to find that message from last Tuesday." If your use case is search-heavy (pulling requirements out of scattered threads, building an incident timeline), verify the server you picked actually implements it before you design a workflow around it.

A few things that trip up mid-level engineers on first contact:

  • conversations.history and conversations.replies are Tier 3 rate-limited (roughly 50+ requests/minute, but Slack tunes this per-workspace and can drop it hard on paid-tier downgrades). An agent that re-fetches history on every turn of a long conversation will eventually hit ratelimited errors mid-task.
  • chat.postMessage is also Tier 3, but Slack additionally enforces a per-channel burst limit that isn't documented precisely — in practice, posting more than roughly one message per second to the same channel from the same token starts producing ratelimited responses.
  • Channel IDs, not names, are the real identifier. Tools that accept a "channel name" resolve it via conversations.list under the hood, which means every "post to #deploys" prompt costs an extra API call unless the ID is cached.
  • conversations.history only returns messages the bot's token can see — for private channels, the bot must be invited (/invite @your-bot) even if it holds the groups:history scope. Scope grants access to the API method; channel membership grants access to that specific channel's data.
{
  "channel": "C0123ABCXYZ",
  "ts": "1700000000.123456",
  "text": "Deploy to production finished in 4m12s",
  "thread_ts": "1699999000.000100"
}

That ts field is the message's unique ID and doubles as the sort key — Slack has no auto-incrementing message ID, so every "find the last message" or "reply to this thread" operation is built around comparing these timestamp strings.

Tips
- Confirm which tools your chosen server actually implements before designing a workflow — "Slack MCP" is not one fixed toolset, and search support in particular varies.
- Cache channel-name-to-ID mappings in your prompt or system context for a long session; re-resolving names on every call burns rate-limit budget for no benefit.
- Treat ts and thread_ts as opaque strings, never as sortable numbers you reformat — floating-point rounding will silently break thread matching.


Slack MCP Authentication: Bot Tokens, OAuth Scopes, and App Installation

Every Slack MCP setup starts with a Slack app, created at api.slack.com/apps. You define OAuth scopes on that app, install it to a workspace, and Slack hands back a bot token (xoxb-...) that the MCP server uses for every API call. The scopes you grant at install time are permanent until you reinstall — adding a scope later requires re-running the OAuth flow, which for a workspace-wide install means an admin has to approve it again.

Minimum viable scope set for read + post + thread workflows:

channels:history     # read messages in public channels the bot is in
channels:read        # list public channels, get channel metadata
groups:history       # read messages in private channels the bot is in
groups:read          # list private channels the bot is in
chat:write           # post messages as the bot
reactions:write       # add emoji reactions
users:read           # resolve user IDs to names/emails
users:read.email     # read email addresses (needed for @-mention resolution by email)
im:history           # read DMs sent to the bot
mpim:history         # read group DM history

The reference MCP server reads two environment variables directly — SLACK_BOT_TOKEN and SLACK_TEAM_ID — and optionally SLACK_CHANNEL_IDS to hard-restrict which channels it will ever touch, regardless of what the bot token could technically reach:

{
  "mcpServers": {
    "slack": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-slack"],
      "env": {
        "SLACK_BOT_TOKEN": "xoxb-0000000000-0000000000-XXXXXXXXXXXXXXXXXXXXXXXX",
        "SLACK_TEAM_ID": "T0123456",
        "SLACK_CHANNEL_IDS": "C0123ABCXYZ,C0456DEFUVW"
      }
    }
  }
}

SLACK_CHANNEL_IDS is worth calling out on its own: it's an allowlist enforced by the MCP server process itself, independent of Slack's own permission model. This is the single most useful lever you have for constraining an agent's blast radius, and it costs nothing to set.

The user-token alternative (korotovsky/slack-mcp-server) skips app creation entirely. It reads xoxc- and xoxd- tokens extracted from your browser's Slack session (via devtools, documented in the project's README) and acts as you — posting under your name, reading everything you can read, no admin approval, no scope negotiation. It's genuinely useful for solo/personal automation or for read-only research inside a workspace where you can't get admin buy-in for a bot install. It is a materially worse choice for anything that posts on a team's behalf, because there is no separate identity, no audit trail distinguishing "the agent said this" from "you said this," and no scope boundary at all — the agent inherits every permission your human account has, full stop.

xoxb-...   -> distinct bot identity, scoped, auditable, revocable independently

xoxc-...   -> acts as you, no scope boundary, expires with your session
xoxd-...   -> companion cookie-derived token, same caveats

App installation itself is a one-time OAuth dance: create the app from a manifest (or the UI), add scopes under OAuth & Permissions, click Install to Workspace, and copy the xoxb- token from the resulting screen. If you're distributing this across multiple workspaces (e.g., an internal tool used by several teams), use the App Manifest JSON to keep scope definitions in version control instead of clicking through the UI each time:

oauth_config:
  scopes:
    bot:
      - channels:history
      - channels:read
      - chat:write
      - groups:history
      - groups:read
      - reactions:write
      - users:read
      - users:read.email

Tips
- Set SLACK_CHANNEL_IDS even when the bot token technically has broader access — it's the cheapest blast-radius control you have and it lives outside Slack's own permission surface.
- Avoid user-token (xoxc/xoxd) auth for anything that posts on behalf of a team; reserve it for personal, read-only research where impersonation risk is acceptable.
- Store the manifest in git if you manage more than one workspace install — reconstructing scopes by clicking through the UI from memory is how scope creep happens.


What AI Can Automate: Notifications, Summaries, and Status Updates

The workflows that actually hold up in production cluster around three shapes: push (the agent tells humans something happened), pull (the agent reads Slack to inform its own next action), and transform (the agent turns a messy conversation into a structured artifact).

Push automations that work well:
- CI/CD status: post to #deploys when a build finishes, fails, or a deploy completes, with the commit SHA, author, and duration.
- Test failure triage: post a failing test's stack trace to a dedicated channel the moment a pipeline goes red, tagging the last committer via users.profile.get email lookup.
- Standing status pings: a daily "here's what shipped yesterday" digest built from git log + Slack post, run on a cron trigger from your agent's host environment.

Pull automations:
- Requirement extraction: read a #product-requests channel's history and turn a week of scattered asks into a prioritized backlog doc.
- Incident context gathering: at the start of an on-call investigation, pull the last 50 messages from #incidents plus any linked thread replies to reconstruct what's already been tried.

Transform automations — the highest-value, hardest-to-get-right category:
- Thread summarization: take a 60-message thread and produce a 5-bullet "decision made / open questions / owner" summary, ideally posted back into the thread itself so context isn't lost.
- Timeline reconstruction: for an incident channel, order messages by ts, correlate with deploy events pulled from a separate MCP server (GitHub, Datadog), and produce a single chronological writeup.

A concrete prompt pattern that works reliably for transform tasks:

Read the last 50 messages in channel C0123ABCXYZ using slack_get_channel_history.
Group them by sub-topic. For each sub-topic, output:
- one-line summary
- decision reached (or "unresolved" if none)
- names of people who need to follow up (resolve user IDs via slack_get_users)
Do not post anything yet — show me the draft first.

That last line — "do not post anything yet" — is not boilerplate caution. It's the difference between a useful tool and an agent that broadcasts a wrong summary to forty people before you've read it.

Tips
- Build transform workflows to draft-then-confirm by default; only graduate a workflow to auto-post once you've reviewed its output quality over several real runs.
- Resolve user IDs to display names explicitly in your prompt — raw <@U0123ABC> mentions in a summary read as broken to a human reviewer.
- Combine Slack MCP with a second MCP server (GitHub, Datadog, Sentry) for timeline work; Slack alone only has the human side of the story.


Preventing Noisy, Spammy, or Unsafe Agent Posting

An LLM with chat:write and a loose leash will, sooner or later, post something wrong, post something twice, or post to the wrong channel. This isn't a hypothetical — it's the predictable failure mode of giving a non-deterministic process write access to a shared, permanent, highly visible communication channel. Guard against it structurally, not by hoping the prompt is good enough.

Channel allowlisting. Set SLACK_CHANNEL_IDS (reference server) to the exact channels the agent is allowed to touch. Don't grant it #general or #random access just because it's convenient — scope it to #deploys, #incidents, or a dedicated #agent-notifications channel.

Rate-limit your own agent, separately from Slack's limits. Slack will eventually throttle a runaway poster, but by then the damage — forty duplicate messages in a customer-facing channel — is already done. Add an application-level guard: no more than N posts per channel per minute, enforced before the MCP call fires, not after Slack rejects it.

Require structured confirmation for destructive-adjacent actions. Posting to a broad channel, @-here, or @-channel mention should require an explicit human "yes, send it" step. A safe default prompt pattern:

Draft the message. Show it to me in full, including any @mentions.
Only call slack_post_message after I reply "send".
Never use @here or @channel unless I explicitly ask for it in this exact turn.

Deduplicate before posting. A common failure: an agent re-runs a task (retry after a timeout, or a badly written loop) and posts the same notification twice. Have the agent check the last N messages in the target channel for a near-duplicate before posting — a simple text-similarity check on the last 5 messages via slack_get_channel_history catches most of this cheaply.

Format defensively. Plain-text walls of text in a channel read as spam even when the content is fine. Use Block Kit for anything posted more than a couple of times a week, so it renders as a scannable card instead of a paragraph:

{
  "channel": "C0123ABCXYZ",
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Deploy finished:* `api-service` v2.14.3\n*Duration:* 3m48s\n*Triggered by:* <@U0456DEF>"
      }
    },
    {
      "type": "context",
      "elements": [
        { "type": "mrkdwn", "text": "Commit `a1b2c3d` · <https://github.com/org/repo/commit/a1b2c3d|View diff>" }
      ]
    }
  ]
}

Separate the bot identity from your human identity. This is the strongest argument against user-token auth for any team-facing automation: if the agent posts as "AI Deploy Bot," a wrong message is obviously a bot mistake and easy to correct with a follow-up post or a deletion. If it posts as you, a wrong message looks like you said something wrong, and the trust cost is entirely different.

Tips
- Enforce channel allowlists and duplicate-detection in application code, not just in the system prompt — prompts get ignored under context pressure, code doesn't.
- Never grant @here/@channel posting rights by default; treat it as an explicit, per-message opt-in the human confirms.
- Give the bot its own identity and avatar; a visibly-automated message is far less damaging when wrong than one that reads as a human's error.


Tips

Tips
- Pick the reference bot-token server for anything team-facing; reserve user-token servers for solo, read-only research where impersonation risk doesn't matter.
- Start every new workflow in draft-then-confirm mode and only automate the send step after you trust the output over several real runs.
- Lock down blast radius with SLACK_CHANNEL_IDS and app-level rate limiting before you ever connect the server to a coding agent with a long-running session.