·

Slack MCP With Gemini CLI

Set up Slack MCP in Gemini CLI so your AI agent can read and post messages and manage channels right from your editor.

Gemini CLI's MCP implementation is close in shape to Claude Code's but has its own settings file, its own tool-confirmation defaults, and — worth knowing before you build around it — its own quirks in how it handles long tool outputs like a 200-message channel history dump. This topic covers setup, search-and-extract workflows, a full incident-timeline example, and where its behavior diverges from Claude Code's in ways that matter for how you write prompts.


Installing and Connecting Slack MCP to Gemini CLI

Gemini CLI reads MCP servers from settings.json, either project-scoped (.gemini/settings.json) or user-scoped (~/.gemini/settings.json):

{
  "mcpServers": {
    "slack": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-slack"],
      "env": {
        "SLACK_BOT_TOKEN": "$SLACK_BOT_TOKEN",
        "SLACK_TEAM_ID": "$SLACK_TEAM_ID",
        "SLACK_CHANNEL_IDS": "$SLACK_CHANNEL_IDS"
      },
      "trust": false
    }
  }
}

The trust field is the important one here: false (the default, and what you want) means Gemini CLI prompts for confirmation before every tool call from this server. Setting trust: true auto-approves every tool the server exposes, which for a server that includes slack_post_message is not a setting to flip casually — it removes your last line of defense against a bad draft going out.

Export the token vars in your shell before launching, same as any other CLI tool reading $VAR-style interpolation:

export SLACK_BOT_TOKEN="xoxb-0000000000-0000000000-XXXXXXXXXXXXXXXXXXXXXXXX"
export SLACK_TEAM_ID="T0123456"
export SLACK_CHANNEL_IDS="C0123ABCXYZ"
gemini

Verify inside the CLI:

/mcp list

/mcp desc slack

A setup issue specific to Gemini CLI: it launches MCP servers using the same Node.js runtime version Gemini CLI itself was installed with, which can be older than what you'd get from a fresh npx invocation in your interactive shell — if @modelcontextprotocol/server-slack requires a newer Node than Gemini CLI's bundled runtime, the server fails to start with a cryptic syntax error rather than a clear version mismatch message. If /mcp list shows the server erroring immediately, check node --version inside the context Gemini CLI runs, not just your terminal's default.

Tips
- Keep trust: false on the Slack server entry; auto-trusting a server that can post messages removes your only pre-send checkpoint.
- Run /mcp desc slack after connecting to confirm which tools the running server version actually exposes — don't assume it matches the docs you read.
- If the server fails to start with an unclear error, check the Node.js version Gemini CLI itself is running under, not your shell's default node.


Searching Channel History and Extracting Requirements from Gemini CLI

Gemini CLI is a strong fit for the "extract structured signal from unstructured chat" class of task — it tends to be thorough about walking through every message rather than skimming, which is exactly what you want for requirement extraction and exactly what you don't want for quick tone-check summaries (more on that trade-off below).

A requirement-extraction prompt that works well:

Fetch the last 100 messages from channel C0234FEATREQ using
slack_get_channel_history. Extract every distinct feature request or
bug report mentioned. For each one, output:
- requester (resolve via slack_get_users)
- one-line description
- message permalink (construct from channel ID and ts:
  https://yourteam.slack.com/archives/{channel}/p{ts without the dot})
- rough priority signal (how many people reacted or replied to it)

Group duplicates. Skip pure acknowledgments ("thanks", "+1" with no new content).

That permalink construction detail matters in practice — Slack doesn't return a ready-made permalink from conversations.history; you build it from the channel ID and the message ts with the decimal point stripped, or you call the separate chat.getPermalink API method if your server exposes it. Reference MCP servers usually don't wrap chat.getPermalink at all, so telling the model the manual construction pattern up front saves it from either fabricating a link or telling you it can't do this part.

If the channel doesn't expose full-text search (most reference-server setups don't), "search for messages about rate limiting" has to become a request to fetch a bounded history window and filter client-side — be explicit about the window size, because an unscoped "find messages about X" prompt will otherwise default to whatever history window the model guesses is reasonable, which is inconsistent run to run:

Fetch the last 200 messages from C0234FEATREQ (paginate with
slack_get_channel_history if needed — it returns has_more and a
next cursor). Filter for any message mentioning rate limiting,
throttling, or 429 errors. Show me the matches with context
(the 2 messages before and after each match).

Tips
- Give the model the manual permalink-construction formula explicitly; most reference MCP servers don't expose chat.getPermalink, so it either fabricates a link or skips this without being told.
- Always bound "search" requests with an explicit history window and pagination instruction — without search tooling, an unscoped ask produces inconsistent results across runs.
- Lean on Gemini CLI for thorough, walk-every-message extraction tasks; it's a better fit for this than for quick single-glance tone checks.


Practical Example: Turning an Incident Channel Into a Timeline

Incident postmortems live or die on an accurate timeline, and reconstructing one by scrolling a channel manually is exactly the kind of tedious, error-prone task worth automating — as long as you keep a human reviewing the output before it goes into a postmortem doc anyone will reference later.

Fetch all messages in #incidents from timestamp 1700000000 to 1700010800
(the window covering last night's outage) using slack_get_channel_history
with oldest/latest bounds. For every thread parent in that range, also
fetch its replies with slack_get_thread_replies.

Build a chronological timeline: timestamp (converted to local time,
UTC-5), who posted, and what happened. Merge thread replies into the
timeline at their actual post time, not grouped under the parent.
Flag any gap longer than 15 minutes with no update as "SILENT PERIOD —
verify nothing was missed."

A representative slice of output:

14:02  @oncall-alice   Alerts firing: p99 latency on checkout-service > 2s
14:04  @oncall-alice   Confirmed not a false positive, paging @bob
14:09  @bob            Looking at DB connection pool, looks exhausted
14:11  @bob (thread)   Pool max is 20, we're seeing 20/20 held for >30s
14:26  ---- SILENT PERIOD (15 min, no updates) — verify nothing was missed ----
14:41  @bob            Found it: a migration left a long-running lock
14:44  @bob            Killed the blocking query, pool draining
14:47  @oncall-alice   p99 back under 300ms, confirming stable

That SILENT PERIOD flag is worth insisting on explicitly — it's not something the model does unprompted, and it's exactly the kind of detail a postmortem review needs (was someone actually working during that gap, or did the response genuinely stall?). Cross-reference this timeline against a second source before treating it as ground truth: pull the actual deploy/rollback events from your CI or infra MCP server (GitHub, Datadog) for the same window and reconcile — Slack only captures what people typed, not what actually happened in the system, and those two timelines diverge more often than people expect.

Tips
- Explicitly request gap-flagging ("SILENT PERIOD") for timeline reconstruction — it surfaces real questions about response continuity that a plain chronological list hides.
- Merge thread replies into the main timeline by actual timestamp, not grouped under the parent message — the true chronology often interleaves multiple threads.
- Cross-reference the Slack-derived timeline against a systems source of truth (CI, monitoring) before treating it as the accurate record — chat logs capture what people said, not what the system did.


Comparing Slack MCP Behavior Between Gemini CLI and Claude Code

Both tools call the same underlying MCP server and the same Slack API, so the capabilities are identical — the differences are in tool-use discipline and default behavior, and they're worth knowing before you assume a prompt that works well in one will behave identically in the other.

Tool-call verbosity. Gemini CLI tends to narrate its plan before each tool call in more detail than Claude Code does by default, which is genuinely useful when you're debugging why it fetched the wrong channel, but adds noticeable overhead to long multi-step Slack workflows (fetch history, fetch threads, resolve users, draft, post) — expect more scrollback per task.

Confirmation defaults. Gemini CLI's per-server trust flag is binary (trust the whole server or confirm every call); Claude Code's tool-approval is more granular by default, letting you approve slack_get_channel_history broadly while still gating slack_post_message individually per call within the same server. If you want read/write separation in Gemini CLI, you generally need two separate MCP server entries pointed at the same underlying process configuration — awkward, but it's the workaround until per-tool trust settings land.

Pagination handling. In testing, Gemini CLI is more likely to proactively continue paginating a has_more: true response without being asked, compared to the more conservative single-page-then-stop behavior common in OpenCode and, to a lesser extent, default Claude Code sessions. This is a genuine advantage for exhaustive extraction tasks (requirement mining, timeline building) and a genuine liability for quick "what's the vibe of this channel today" checks where you didn't want it burning ten tool calls fetching the whole week's history.

Error surfacing. Both surface raw Slack API errors (missing_scope, not_in_channel, ratelimited) verbatim rather than translating them, which is the right behavior for a developer tool — but Gemini CLI is somewhat more likely to retry a ratelimited error automatically with backoff, while Claude Code more often surfaces it to you immediately and waits for direction. Neither behavior is strictly better; automatic retry is convenient until it silently burns several minutes on a rate-limited loop you'd rather have known about sooner.

Tips
- If you need per-tool (not per-server) trust separation in Gemini CLI, register the Slack server twice under different names with different trust settings rather than fighting the binary trust flag.
- For exhaustive extraction work, Gemini CLI's more aggressive auto-pagination is an advantage; for quick spot-checks, explicitly cap the fetch size so it doesn't over-fetch by default.
- Don't assume a prompt tuned on one CLI transfers behavior identically to the other — confirmation granularity and pagination defaults differ even against the identical MCP server.


Tips

Tips
- Keep trust: false on the Slack MCP entry in settings.json and verify tool exposure with /mcp desc slack after every server or config change.
- Give the model explicit formulas (permalink construction, history window bounds) rather than assuming it infers the right scope — without search tooling, vague requests produce inconsistent results.
- Know Gemini CLI's specific behavioral leanings — verbose narration, aggressive pagination, automatic rate-limit retry — and design prompts that lean into the useful ones and guard against the costly ones.