原始内容
slack-cli
A command-line interface for Slack workspaces, built on the
korotovsky/slack-mcp-server
engine — the same stealth/OAuth auth, edge API, smart-history, and caching —
exposed as ordinary subcommands instead of a long-lived MCP server.
Why a CLI instead of the MCP server?
An MCP server is one resident process per agent (stdio transport). Running
10+ agents means 10+ resident Slack servers, each warming and holding its own
user/channel cache. slack-cli is invoke-and-exit: each command is a
short-lived process that reads the shared on-disk cache, so process and
memory overhead don't scale with the number of agents. No daemon, no port, no
bearer token to manage — just a binary your agents call through their shell.
The original MCP server is still here (cmd/slack-mcp-server); the CLI is
additive and reuses the same pkg/provider engine unchanged.
How it works
slack-cli doesn't reimplement Slack — it drives the same tool handlers the
MCP server uses, in-process. Each invocation:
- Resolves credentials — explicit
SLACK_MCP_*env/flags →--profile→ default profile — and writes them into the env the engine reads. - Builds the provider (
pkg/provider): the same stealth/OAuth auth, edge API, rate limiter, and caching the MCP server uses. - Loads the shared on-disk cache of users/channels (skip with
--no-cache) so#channeland@usernames resolve to IDs. - Invokes the tool handler for the subcommand directly — no MCP transport,
no JSON-RPC — and prints its result to stdout as JSON for piping to
jq.
flowchart LR
A["slack-cli CMD"] --> B["resolve creds<br/>(env / profile)"]
B --> C["provider.New<br/>(pkg/provider)"]
C --> D["load shared<br/>on-disk cache"]
D --> E["invoke tool handler<br/>in-process"]
E --> F["JSON<br/>to stdout"]
The only glue between the CLI and the MCP toolset is internal/toolcall: it
turns a subcommand's flags into the arguments map a handler expects, calls the
handler, and returns its text. Command code never imports the MCP library, and
pkg/handler / pkg/provider are reused unchanged — so the bundled MCP server
(cmd/slack-mcp-server) keeps working and upstream updates still merge cleanly.
Because nothing stays resident, there's nothing to keep warm between calls: the
cost moves from "one server per agent, held for the whole session" to "one cache
read per command." The cache is shared across every invocation (and with the MCP
server) — refresh it explicitly with slack-cli cache refresh.
Install
brew install paymog/tap/slack-cli
The formula builds from source, so Homebrew installs a Go toolchain as a build dependency.
Go install:
go install github.com/paymog/slack-cli/cmd/slack-cli@latest
Local build:
make cli # -> ./slack-cli
# or
go build ./cmd/slack-cli
Auth
slack-cli accepts the same credentials as the MCP server. Provide one of:
SLACK_MCP_XOXP_TOKEN— user OAuth token (xoxp-…), full featuresSLACK_MCP_XOXB_TOKEN— bot token (xoxb-…), limited (invited channels, no search)SLACK_MCP_XOXC_TOKEN+SLACK_MCP_XOXD_TOKEN— browser session token + cookie (xoxc-…/xoxd-…), stealth mode
The simplest setup for CI is environment variables (the SLACK_CLI_* prefix is
also accepted):
export SLACK_MCP_XOXP_TOKEN=xoxp-...
Stored profiles
For day-to-day use, store credentials in named profiles. Tokens are kept in your
OS keyring (macOS Keychain, Linux libsecret/secret-service, Windows Credential
Manager); only non-secret metadata (auth mode, GovSlack flag) is written to
~/.config/slack-cli/profiles.yaml (XDG-aware; %AppData% on Windows).
slack-cli auth login # prompts for mode + token(s); profile "default"
slack-cli auth login work --xoxp xoxp-...
slack-cli auth list # table; * marks the default
slack-cli auth default work # set the default profile
slack-cli --profile work channels list # use a profile for one command
slack-cli auth status # show which source resolved
slack-cli auth token # print resolved tokens as SLACK_MCP_* lines
slack-cli auth logout work # remove a profile (-f to skip confirm)
login validates credentials against Slack before saving. The first profile
added becomes the default.
Credential precedence (highest first):
- explicit tokens via flags (
--xoxp/--xoxc/…) orSLACK_MCP_*env vars --profile <name>→ stored profile- default profile → stored profile
Combining explicit tokens with --profile is rejected as ambiguous.
Global flags: --govslack (route to slack-gov.com), --no-cache, --raw
(print tool output verbatim), --verbose, --timeout (default 30s).
Cache
Name lookups (#channel, @user) and channels list require a warm
user/channel cache. The cache is file-backed and shared across every
invocation (SLACK_MCP_USERS_CACHE / SLACK_MCP_CHANNELS_CACHE, TeamID-namespaced
under your OS cache dir by default).
slack-cli cache refresh # fetch users + channels from Slack, write cache
Read commands load the on-disk cache automatically (and fetch on first run).
Pass --no-cache to skip it entirely — only raw channel/user IDs will resolve.
Commands
# Channels
slack-cli channels list [--types public_channel,private_channel,im,mpim] [--query foo] [--limit 100]
slack-cli channels me
# Conversations
slack-cli conversations history <channel> [--limit 1d] [--cursor C] [--activity]
slack-cli conversations replies <channel> <thread_ts>
slack-cli conversations search [query] [--in-channel #general] [--from @user] [--after 2024-01-01]
slack-cli conversations unreads [--types all] [--mentions-only]
slack-cli conversations join <channel>
slack-cli conversations leave <channel>
# Users & groups
slack-cli users search <query> [--limit 10]
slack-cli usergroups list [--include-users]
slack-cli usergroups me <list|join|leave> [--usergroup-id S123]
slack-cli usergroups create --name "Eng" [--handle eng] [--channels C123]
slack-cli usergroups update <id> [--name ...] [--channels ...]
slack-cli usergroups users-update <id> --users U1,U2
# Saved items (browser tokens only)
slack-cli saved list [--filter saved|completed|archived]
slack-cli saved update <item_id> <ts> [--mark completed] [--date-due 0]
slack-cli saved clear-completed
# Attachments (download a file by ID; always available, no env var needed)
slack-cli attachments get <file_id> [-o path]
Write/sensitive commands (disabled by default)
Mirroring the MCP server, these require an opt-in environment variable so an agent can't post or mutate by accident:
SLACK_MCP_ADD_MESSAGE_TOOL=true slack-cli conversations add <channel> -t "hello"
SLACK_MCP_ADD_MESSAGE_TOOL=C123,D456 slack-cli conversations add C123 -t "hi" # channel allowlist
SLACK_MCP_MARK_TOOL=true slack-cli conversations mark <channel> [--ts 123.456]
SLACK_MCP_REACTION_TOOL=true slack-cli reactions add <channel> <ts> --emoji rocket
SLACK_MCP_REACTION_TOOL=true slack-cli reactions remove <channel> <ts> --emoji rocket
Output
Tool output is JSON. Table results (channels, messages, users, saved items,
user groups) print as a JSON array of objects; status/JSON handlers print their
JSON verbatim. Everything is pipeable to jq, e.g. slack-cli channels list | jq -r '.[].Name'. Table values are strings (CSV carries no types) — use jq's
tonumber when you need numbers. --raw prints the handler's bytes verbatim
(the original CSV/text the MCP server returns).
Binary attachments (attachments get) are the exception: the bytes come back
inline as base64 under .content (images included), so decode with jq -r .content | base64 --decode. Or pass -o <path> to write the decoded bytes to a
file and keep stdout to a small metadata JSON — recommended for images and large
binaries so a multi-MB blob doesn't flood the terminal.
Claude Code skill
This repo ships a Claude Code skill that
teaches the agent how to drive the CLI. It lives in
skills/slack-cli.
npx skills add paymog/slack-cli
Shape
cmd/slack-cli— CLI entrypoint.internal/cli— root command, global flags,authsubcommands.internal/cmds— one file per tool group; each subcommand invokes a handler.internal/toolcall— the only coupling to mcp-go: invokes the upstream tool handlers in-process (args map → request → text plus any image bytes), sopkg/handlerstays unchanged and upstream merges stay clean.internal/config,internal/credstore,internal/runtime,internal/output— auth resolution, keyring profiles, provider bootstrap, result printing.pkg/provider,pkg/handler, … — the upstream engine, reused as-is.
Release
Releases are tag-driven:
make release TAG=v0.1.0 # tags and pushes
GitHub Actions runs GoReleaser, then updates the source-build formula in
paymog/homebrew-tap (needs the HOMEBREW_TAP_DEPLOY_KEY secret).
License
MIT, inherited from the upstream project. Not an official Slack product.