@mindstone/mcp-server-slack
v0.1.3
Published
Slack workspace MCP server — channels, messages, threads, reactions, users, files, bookmarks, scheduling via Slack Web API
Readme
@mindstone/mcp-server-slack
Slack workspace MCP server — channels, messages, threads, reactions, users, files, bookmarks, and scheduled messages via the Slack Web API.
Multi-workspace Slack MCP. Host-driven OAuth, per-workspace credentials files on disk, and a security review on every release.
Status
- Version: 0.1.3 · npm
- Auth: OAuth (host-orchestrated) (
SLACK_CLIENT_SECRET) - Tools: 23 (messages, channels, threads, users, files)
- Surface: cloud-api
- Hosts tested: Claude Desktop, Cursor, Mindstone Rebel
- Machine-readable:
STATUS.json
Why this exists
When we started building this connector, Slack had not yet released an official MCP server — that came later (announced February 2026, generally available April 2026). The community Slack MCPs available at the time each got parts of the job right, but none of them combined fast user-name lookups for small and medium workspaces, the correct mention format for modern Slack (most still used the deprecated link_names=true), support for more than one Slack workspace from the same user account, and tokens that stay on the user's own machine. Slack's official server now exists and is a fine choice for many use cases. We continue to maintain this one because the host application handles the login flow, each workspace has its own credentials file on disk under the user's control, and the connector goes through our own security review before each release.
Example interaction
"Find the most recent message from Alice in #q3-planning and react with :eyes:."
Tools the host calls:
lookup_user_by_email— resolves Alice's email to her Slack user ID.search_slack_messages— searches#q3-planningfor her most recent message, returning the channel + timestamp.add_slack_reaction— adds theeyesreaction to that message.
Response (trimmed):
{
"match": {
"channel": "C08X...",
"ts": "1715953812.004200",
"user": "U07Z...",
"text": "Pushed the updated forecast to the doc, ready for review."
},
"reaction": {
"ok": true,
"name": "eyes"
}
}Requirements
- Node.js 20+
- npm
- A host application that performs the Slack OAuth flow and writes per-workspace token files to
${SLACK_CONFIG_PATH}/workspaces/{teamId}.json. This server reads those files; it does not initiate OAuth itself.
One-click install
After clicking the button, your host will prompt you to fill: SLACK_CONFIG_PATH, SLACK_TEAM_ID, SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_REQUEST_TIMEOUT_MS, SLACK_MAX_RETRIES.
{
"mcpServers": {
"Slack": {
"command": "npx",
"args": [
"-y",
"@mindstone/mcp-server-slack"
],
"env": {
"SLACK_CONFIG_PATH": "",
"SLACK_TEAM_ID": "",
"SLACK_CLIENT_ID": "",
"SLACK_CLIENT_SECRET": "",
"SLACK_REQUEST_TIMEOUT_MS": "60000",
"SLACK_MAX_RETRIES": "10"
}
}
}
}Quick Start
Install & build
cd <path-to-repo>/connectors/slack
npm install
npm run buildnpx (once published)
npx -y @mindstone/mcp-server-slackLocal
node dist/index.jsConfiguration
This server is designed to run alongside a host application that performs the Slack OAuth flow on its own. The host writes credentials to disk; this server reads them.
Required environment variables
SLACK_CONFIG_PATH— Path to the Slack config directory (host-managed). Containsconfig.json(workspace metadata) andworkspaces/{teamId}.json(per-workspace tokens, mode 0600).SLACK_TEAM_ID— Workspace team ID (per-workspace instance).SLACK_CLIENT_ID— OAuth Connected App client ID.SLACK_CLIENT_SECRET— OAuth Connected App client secret.
Optional environment variables
SLACK_DISABLE_REFRESH— Set to1to disable token refresh on this surface. The server will fail-closed with a structuredauth_requiredresponse on token expiry instead of attempting anoauth.v2.accessrefresh. Use this on the cloud surface so desktop remains the sole refresh authority and avoids racing for single-use refresh tokens.SLACK_REQUEST_TIMEOUT_MS— Override the default 60s upstream timeout. Must be a positive integer ≤ 300000 (5 minutes).
Authentication flow
The host calls the authenticate_slack_workspace tool. The OSS server returns a structured auth_required response of the form:
{
"status": "auth_required",
"user_action": {
"id": "slack.connect_workspace",
"label": "Connect Slack",
"instruction": "Click \"Connect Slack\" in the side panel to authorise the workspace."
},
"agent_action": {
"instruction": "Tell the user to click the Connect Slack button in the connector settings to authorise. Then call list_slack_workspaces to verify."
},
"setupToolName": "authenticate_slack_workspace"
}The host's MCP service recognises this shape and dispatches to its registered Slack OAuth orchestrator (the desktop browser flow). Once the user signs in, the host writes tokens to ${SLACK_CONFIG_PATH}/workspaces/{teamId}.json and the server picks them up on the next call.
The OSS server never initiates OAuth itself.
Host configuration examples
This server is designed for host-orchestrated OAuth: the host writes per-workspace token files to disk and the server reads them. The examples below show the env shape — your host application is responsible for populating ${SLACK_CONFIG_PATH}/workspaces/{teamId}.json before tool calls succeed.
Claude Desktop / Cursor
{
"mcpServers": {
"Slack": {
"command": "npx",
"args": ["-y", "@mindstone/mcp-server-slack"],
"env": {
"SLACK_CONFIG_PATH": "/absolute/path/to/slack-config",
"SLACK_TEAM_ID": "T0123ABCD",
"SLACK_CLIENT_ID": "your-slack-app-client-id",
"SLACK_CLIENT_SECRET": "your-slack-app-client-secret"
}
}
}
}Until the host has written ${SLACK_CONFIG_PATH}/workspaces/T0123ABCD.json for that team, every tool call returns a structured auth_required response (see the Authentication flow above).
Local development (no npm publish needed)
{
"mcpServers": {
"Slack": {
"command": "node",
"args": ["<path-to-repo>/connectors/slack/dist/index.js"],
"env": {
"SLACK_CONFIG_PATH": "/absolute/path/to/slack-config",
"SLACK_TEAM_ID": "T0123ABCD",
"SLACK_CLIENT_ID": "your-slack-app-client-id",
"SLACK_CLIENT_SECRET": "your-slack-app-client-secret"
}
}
}
}Tools (23)
Authentication
authenticate_slack_workspace— Returns structured auth_required response; the host drives OAuth.list_slack_workspaces— Check Slack connection status (connected, token health, near-expiry).
Messages
search_slack_messages— Search across all channels (Slack search modifiers supported).get_slack_saved_messages— Get messages saved for later (usesis:saved).get_slack_message_by_link— Retrieve a message from its permalink URL.post_slack_message— Post a message; DM recipient verification baked in.reply_to_slack_thread— Reply to an existing thread.schedule_slack_message— Schedule a message for the future.
Channels
list_slack_channels— List channels (filterable, paginated).get_slack_channel_history— Get recent messages from a channel.create_slack_channel— Create a new channel (public or private).mark_slack_channel_as_read— Mark messages read up to a timestamp.get_slack_unread_messages— Get unread messages based on your read position.invite_user_to_channel— Add users to a channel (bulk).
Threads
get_slack_thread_replies— Get all replies in a thread.
Reactions
add_slack_reaction— Add an emoji reaction to a message.
Users
list_slack_users— List active users (auto-paginates name filter).get_slack_user_profile— Get detailed profile for a user.lookup_user_by_email— Find a user by exact email (preferred resolution method).open_slack_dm— Open a DM with a user (returns DM channel + verified recipient identity).
Files
download_slack_file— Download a file attachment by ID or URL.
Workspace
add_slack_bookmark— Add a bookmark to a channel.add_slack_reminder— [EXPERIMENTAL] Create a reminder (Slack API partially deprecated; preferschedule_slack_message).
Security notes
- Slack-owned download URL guard —
download_slack_filevalidates that the Slack-suppliedurl_private_downloadis HTTPS and onslack.com/*.slack.combefore attaching the workspace bearer token. The download helper also setsredirect: 'manual'and re-validates every redirect hop, so a 302 from a compromised Slack edge to an attacker-controlled host can never replay the bearer token (added in 0.1.3 to close findingslack-010). - Untrusted-content envelopes — every tool that returns external text wraps it in
<untrusted-content source="…">…</untrusted-content>envelopes per AGENTS.md invariant #6, with close-tag breakout escaping. This applies to message text, channel topics/purposes, search results, thread replies, unread messages, and downloaded file content/names. Hosts must keep the envelopes intact when surfacing tool output to the model (added in 0.1.3 to close findingsslack-001..007). - Atomic, durable token persistence — token files are written via temp-file +
fsync+rename, thenchmod 0600, so a crash mid-write cannot lose Slack's single-use refresh token. - Refresh-failure differentiation — transient network errors, HTTP 429 rate-limits, Slack auth rejections (
invalid_grant), and malformed responses produce distinct error codes so hosts can react correctly without retrying unrecoverable failures. - No host-internal vocabulary — host-side bridge identifiers and bundled HTTP paths are explicitly absent from the published artefact (enforced by
scripts/check-no-bridge-strings.shduringprepublishOnly).
Full implementation-level notes (request-timeout composition, MSW request manifest, token-file error states, server-version drift checks, etc.) live in docs/connectors/slack-cohort-hygiene.md.
Live probe
A live probe gate is committed at test/live-probe.ts. It is not auto-run; trigger it manually:
LIVE_PROBE_BOT_TOKEN=xoxb-... \
LIVE_PROBE_USER_TOKEN=xoxp-... \
LIVE_PROBE_TEAM_ID=T... \
npm run probe:liveThe probe runs against the packed tarball (not the workspace source), exercising initialize + tools/list + 5 read-only + 2 write tool calls, and logs search.messages P95 latency to validate the 60s timeout default.
By default the probe is read-only: write probes (post_slack_message, add_slack_reaction) only run when LIVE_PROBE_TEST_CHANNEL_ID is set, otherwise they are skipped and the probe still exits OK.
Publish-gate mode
For pre-publish certification, run:
LIVE_PROBE_BOT_TOKEN=xoxb-... \
LIVE_PROBE_USER_TOKEN=xoxp-... \
LIVE_PROBE_TEAM_ID=T... \
LIVE_PROBE_TEST_CHANNEL_ID=C... \
npm run probe:live:gateprobe:live:gate sets LIVE_PROBE_REQUIRE_WRITES=1, which causes the probe to fail rather than skip if LIVE_PROBE_TEST_CHANNEL_ID is missing or any write probe doesn't complete cleanly. Use this before cutting a release.
Licence
FSL-1.1-MIT — Functional Source License, Version 1.1, with MIT future licence. The software converts to MIT licence on 2030-04-08.
