FA Dev Tools

English中文

Giving Claude access to Slack — every option

ai/claude-slack-access-options · Updated · Tags: ai, claude, slack


Giving Claude access to Slack — every option

TL;DR

  • Easiest route for Claude Code: Slack's official MCP server, if your workspace admin allows it.
  • If the MCP is blocked, create your own Slack app with a read-only user token (xoxp-) and let Claude call the Web API.
  • Claude in Slack (@Claude) works inside Slack only. It doesn't give Claude Code in your terminal or IDE any Slack access.
  • The Slack CLI and webhook or bot tokens you may already have can't read message history.
  • Posting needs chat:write: a user token posts as you, a bot token posts as the app, and a webhook posts to one fixed channel.
  • Start with the MCP. If it's blocked, request a private read-only app; don't scrape the desktop app's session token.

Contents

  1. The options at a glance
  2. How to choose
  3. Option A — Slack's official MCP server
  4. Option B — Your own Slack app with a user token
  5. Option C — Your own Slack app with a bot token
  6. Option D — A community MCP server with your token
  7. Option E — Claude in Slack (@Claude)
  8. Writing messages
  9. Things that look like options but aren't
  10. Scopes reference
  11. Security notes

The options at a glance

OptionWhere Claude runsReads channelsReads DMsSearchPosts messagesAdmin approvalEffort
A. Slack MCP serverClaude Code, other MCP clientsYesYes (yours)YesYes, as youUsuallyLow
B. Own app + user tokenClaude Code via a script or skillYesYes (yours)YesWith chat:write, as youUsuallyMedium
C. Own app + bot tokenClaude Code via a script or skillOnly channels the bot is invited toNoNoWith chat:write, as the appUsuallyMedium
D. Community MCP + tokenClaude CodeSame as the token you give itSameSameIf the token and server allow itSame as B or CMedium
E. Claude in SlackInside SlackThread it's tagged in, plus public searchOnly its own DM with youPublic channelsReplies in the thread, as @ClaudeYes (Team/Enterprise plan)Low
F. Incoming webhookAnywhere that can send an HTTP POSTNoNoNoYes, to one fixed channelUsuallyVery low

Every token-based option sees at most what the token's owner can see. A user token acts as you; a bot token acts as the bot.

How to choose

Your situationPick
Your admin allows Slack's MCP serverA
The MCP is blocked, but your admin approves custom appsB
You only need a few shared channels, not DMs or searchC
You want MCP tools instead of shell calls, and you trust the projectD, using the token from B or C
You want to ask Claude about a thread while you're in SlackE
Claude should post to Slack as you (replies, updates)A, or B with chat:write
Claude or a script should post as an app (alerts, summaries, reports)C with chat:write
You only need notifications posted to one channelF
Nothing is approvedPaste the thread text or export into Claude by hand

Option A — Slack's official MCP server

Slack hosts an MCP server at https://mcp.slack.com/mcp. Claude Code connects to it over HTTP and logs in with OAuth as you.

Setup in Claude Code

  1. Check Slack's MCP documentation for the client ID and callback port to use with Claude Code. Slack's server needs a client it has registered in advance, so a plain URL isn't enough.

  2. Add the server:

    claude mcp add --transport http --client-id <client-id> --callback-port <port> \
      slack https://mcp.slack.com/mcp
    
  3. In an interactive Claude Code session, run /mcp, pick slack, and finish the browser login.

  4. Ask something that needs Slack, for example "summarise yesterday in #dev".

Gotchas

  • /mcp showing Needs authentication means the OAuth step hasn't been done. Non-interactive sessions (claude -p, cron) can't complete it.
  • Workspace admins can block the MCP app. When they have, the login fails and nothing on your side fixes it. Move on to B.
  • If claude.ai offers a Slack connector, it probably goes through Slack's MCP too, so the same admin block would apply.
  • The server includes tools that send messages as you, not just read ones. To make Claude ask before posting, leave those tools out of the allow list in settings.json, so each send prompts you for approval.

Option B — Your own Slack app with a user token

This is the fallback that works when the MCP is blocked but your admin approves custom apps. You create a private Slack app that requests only user read permissions, install it, and give Claude the resulting xoxp- token.

1. Create the app from a manifest

Go to https://api.slack.com/apps, click Create New App, choose From a manifest, pick your workspace, and paste:

display_information:
  name: Claude Reader
  description: Read-only access to my own Slack for Claude Code
oauth_config:
  scopes:
    user:
      - channels:read
      - channels:history
      - groups:read
      - groups:history
      - im:read
      - im:history
      - mpim:read
      - mpim:history
      - users:read
      - search:read
settings:
  token_rotation_enabled: false

Drop any permissions you don't need. For example, remove im:* and mpim:* if Claude shouldn't read DMs. A shorter list is also easier to get approved.

To let Claude post too, add write permissions under the same user: list:

      - chat:write        # post, edit and delete your own messages
      - reactions:write   # add emoji reactions
      - im:write          # open a DM with someone before posting to it
      - files:write       # upload files

These permissions let Claude post as you. Consider a separate app (or a bot token, option C) for writing, so a read-only token stays read-only. See Writing messages.

2. Install it

  1. Open OAuth & Permissions and click Install to Workspace.
  2. If the button says Request to Install, your admin has to approve it. Explain in one line why you need it and that it has no write permissions.
  3. Once installed, copy the User OAuth Token (xoxp-…) from the same page.

Keep token rotation off. With rotation on, the token expires every 12 hours and you'd need a refresh flow.

3. Store the token outside your shell history

OSStoreRead
macOSsecurity add-generic-password -a "$USER" -s SLACK_READ_TOKEN -w (prompts for the value)security find-generic-password -a "$USER" -s SLACK_READ_TOKEN -w
WindowsCredential Manager (cmdkey or the CredentialManager PowerShell module)The same module's Get-StoredCredential
Linuxsecret-tool store --label=slack service SLACK_READ_TOKENsecret-tool lookup service SLACK_READ_TOKEN

Don't paste the token into a prompt, a committed file or a shared doc.

4. Test it

T=$(security find-generic-password -a "$USER" -s SLACK_READ_TOKEN -w)   # macOS
curl -s -H "Authorization: Bearer $T" https://slack.com/api/auth.test
curl -s -H "Authorization: Bearer $T" "https://slack.com/api/search.messages?query=deploy&count=5"

auth.test should return "ok": true with your user name. A missing_scope error names the permission you still need to add.

5. Connect it to Claude Code

Pick one, or combine them:

ApproachWhat it isGood for
Skill (~/.claude/skills/slack/SKILL.md)Instructions telling Claude where the token is and which API methods to callLoads automatically whenever you mention Slack
Wrapper script (e.g. slack.sh history <channel> <n>)Turns raw JSON into short readable textSaves context; easy to allow in permissions
Community MCP serverSee option DWhen you'd rather have tools than shell calls

A minimal skill could look like this:

---
name: slack
description: Read Slack channels, threads, DMs and search results with my read-only user token. Use when I ask about anything in Slack.
---

Read the token with `security find-generic-password -a "$USER" -s SLACK_READ_TOKEN -w`
and send it as `Authorization: Bearer`. Never print it.

| Need | Method |
|---|---|
| Find a channel ID | `conversations.list` (types=public_channel,private_channel) |
| Recent messages | `conversations.history?channel=<id>&limit=<n>` |
| A thread | `conversations.replies?channel=<id>&ts=<thread_ts>` |
| Search | `search.messages?query=<q>` |
| A user's name | `users.info?user=<id>` |

Read-only: never call chat.*, reactions.* or any other write method.

For a skill that can also post, swap the last line for a confirmation rule, e.g. "Before calling chat.postMessage, show me the channel and the exact text and wait for my OK."

The Web API methods you'll use

MethodReturns
conversations.list / users.conversationsChannels (all of them, or only the ones you're in)
conversations.historyA channel's messages, newest first, paged with cursor
conversations.repliesEvery message in one thread
search.messagesSearch results, using the same syntax as the Slack search box (in:#dev from:@x after:2026-01-01)
users.info / users.listTurns user IDs (U…) into names

Rate limits: in 2025 Slack tightened the limits on conversations.history and conversations.replies for apps outside its Marketplace. Its announcement exempted apps a workspace builds for its own use. If reads seem throttled, check the current limits page.

Option C — Your own Slack app with a bot token

The setup is the same as B, but you add bot permissions (channels:history, channels:read, users:read, …) and use the Bot User OAuth Token (xoxb-…).

  • The bot only sees channels it's been invited to (/invite @YourApp).
  • It can't see your DMs.
  • It can't use search.messages, because search is only available to user tokens.

Pick C when you want Claude's access limited to a few shared channels and not tied to your own account.

C is also the usual choice for posting. Messages show the app's name and icon, so readers can see they came from automation, not from a person. Useful bot write permissions:

ScopeLets the bot
chat:writePost in channels it has been invited to
chat:write.publicPost in any public channel without being invited
chat:write.customizeChange the name and icon shown on each message

Option D — A community MCP server with your token

Open-source Slack MCP servers, such as korotovsky/slack-mcp-server, take the token from B or C and expose it to Claude Code as MCP tools.

  • Pros: proper tools (channels_list, conversations_history, search), so you don't maintain your own script.
  • Cons: third-party code holds your token. Read the source, pin a version, and give it the read-only token from B, never one with write permissions.
  • Some of these servers also accept browser session tokens. See the warning under Things that look like options but aren't.
  • Message-posting tools are often turned off by default and switched on with a setting or environment variable. Check the project's README, and only switch them on if the token has chat:write.

Option E — Claude in Slack (@Claude)

Anthropic's Slack app, also called Claude Tag, puts Claude inside Slack. It needs a Claude Team or Enterprise plan, and a workspace admin installs it and links it to your Claude organisation.

WhereHowWhat Claude sees
A channel threadTag @Claude with a requestThat thread, recent channel history, public-channel search
A private channelRun /invite @Claude firstThat channel
Its DMMessage the appYour own Claude account, if you linked it on the app's Home tab
Channels with guests—Workspace search is turned off

It runs separately from Slack's MCP server, so a block on the MCP doesn't affect it. It doesn't give Claude Code in your terminal or IDE any Slack access.


Writing messages

Which identity posts

RouteMessage appears asCan post to
User token with chat:write (A, B)YouAnywhere you can post, including DMs
Bot token with chat:write (C)The appChannels it's invited to, or any public channel with chat:write.public
Incoming webhook (F)The appOne channel, chosen when the webhook was created
Claude in Slack (E)@ClaudeThe thread it was tagged in

Option F — Incoming webhook

This is the simplest way to post, and it can't read anything. In your app's settings, turn on Incoming Webhooks, click Add New Webhook to Workspace, and pick a channel. You get a URL like https://hooks.slack.com/services/T…/B…/….

curl -s -X POST -H 'Content-Type: application/json' \
  --data '{"text":"Deploy finished :white_check_mark:"}' "$SLACK_WEBHOOK_URL"
  • The URL is the secret. Anyone who has it can post to that channel, so store it like a token.
  • A webhook can't post to another channel, read messages, reply in a thread it didn't start, or edit or delete messages.

Web API methods for writing

MethodDoesScope
chat.postMessagePost a message. Pass thread_ts to reply in a threadchat:write
chat.update / chat.deleteEdit or delete a message the same token postedchat:write
chat.scheduleMessagePost at a set time (post_at, a Unix timestamp)chat:write
chat.postEphemeralShow a message to one person in a channel, visible only to themchat:write
conversations.openGet the DM channel ID for a user before posting to themim:write
reactions.addAdd an emoji reactionreactions:write
files.getUploadURLExternal + files.completeUploadExternalUpload a file (the older files.upload is retired)files:write
# Reply in a thread
curl -s -X POST https://slack.com/api/chat.postMessage \
  -H "Authorization: Bearer $T" -H 'Content-Type: application/json; charset=utf-8' \
  --data '{"channel":"C0123456789","thread_ts":"1735689600.123456","text":"Fixed in the latest deploy."}'
  • channel takes an ID (C…, G…, D…). Look IDs up with conversations.list, or open a channel's details in Slack and copy the ID from the bottom.
  • Slack returns HTTP 200 even when a call fails, so check "ok" in the response. Common errors are not_in_channel (invite the bot), channel_not_found and missing_scope.
  • Keep posts to about one message per second per channel. Slack rate-limits bursts beyond that.

Formatting

Slack uses mrkdwn, which isn't standard Markdown. Claude writes standard Markdown by default, so say so in your skill or prompt.

Wantmrkdwn
Bold / italic / strike*bold* _italic_ ~strike~
Link<https://example.com|label> (one pipe, between URL and label)
Mention a person / channel<@U0123456789> <#C0123456789>
Mention a group<!here> <!channel> <!subteam^S0123456789>
Code`inline` and ``` blocks

There are no headings or tables in mrkdwn. For richer layouts, send blocks (Block Kit) and keep a plain text fallback for notifications.

Keeping Claude's posting safe

  • Ask before every send. Don't add the post command or the MCP's send tools to the Claude Code allow list, so each one asks for your approval. Your skill should also tell Claude to show the channel and text first.
  • Draft, then send. Have Claude write the message into the chat, then send it yourself or say "send it". Many people only need drafting help.
  • Separate the tokens. Use a read-only user token for research and a bot token for posting. A mistake in a read step then can't post as you.
  • Never let unattended runs post as you. Cron jobs, claude -p and CI should use a bot token or a webhook.
  • Watch the mentions. <!channel> and <!here> notify everyone in the channel. Tell Claude never to use them unless you ask.

Things that look like options but aren't

ThingWhy it doesn't work
Slack CLI (slack login)It's for building Slack apps. Its login token has a fixed set of permissions (list channels and users, manage apps) and can't be given *:history, so conversations.history fails with missing_scope
Incoming webhook or "post-only" bot token (as a way to read)Fine for posting (option F), but they can't read. Check with auth.test and look at the x-oauth-scopes response header
The desktop app's session token (xoxc- plus the d cookie)Technically it can read everything, but it gets around your admin's app controls, probably breaks workspace policy, and stops working when you log out. Don't use it as a workaround when your admin has said no
Workspace data exportAdmin-only, a one-off snapshot, and usually public channels only

Scopes reference

ScopeLets youToken types
channels:read / channels:historyList and read public channelsUser, bot
groups:read / groups:historyList and read private channels you're inUser, bot
im:read / im:historyList and read direct messagesUser (yours); bot (its own DMs only)
mpim:read / mpim:historyList and read group DMsUser, bot
users:readTurn user IDs into namesUser, bot
search:readsearch.messagesUser only
chat:writePost, edit and delete messagesUser (as you), bot (as the app)
chat:write.publicPost to public channels without being invitedBot only
chat:write.customizeChange the name and icon on each messageBot only
im:write / mpim:writeOpen DMs and group DMs to post toUser, bot
reactions:writeAdd emoji reactionsUser, bot
files:writeUpload filesUser, bot
incoming-webhookCreate a webhook URL for one channelAdded when you set up a webhook

To see what a token can do, run curl -sD - -o /dev/null -H "Authorization: Bearer $T" https://slack.com/api/auth.test | grep -i x-oauth-scopes.

Security notes

  • Prefer read-only permissions. A token with chat:write lets Claude post as you. Add write permissions only when you need them, ideally on a separate app or a bot token.
  • Treat webhook URLs as secrets. Anyone who has one can post to its channel.
  • Keep tokens in the OS credential store, never in prompts, repos, .env files that get committed, or docs.
  • A user token sees your DMs. If Claude's output ends up somewhere shared, leave the im:* permissions out.
  • To revoke access, go to api.slack.com/apps, open your app, and click Revoke tokens, or uninstall it from the workspace.
  • If a token leaks, revoke it straight away and reinstall the app to get a new one.

Disclaimer: These documents are personal notes. The information may be incomplete, out of date or wrong, so check it before you rely on it.