@jtalk22/slack-mcp 1.1.9 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026
3
+ Copyright (c) 2026 jtalk22
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,14 +1,39 @@
1
- # Slack MCP Server
2
-
3
- *Full workspace access via local session mirroring. DMs, threads, and history—no admin approval required.*
4
-
5
- [![Live Demo](https://img.shields.io/badge/LIVE%20DEMO-Try%20It%20Now-00C853?style=for-the-badge&logo=slack&logoColor=white)](https://jtalk22.github.io/slack-mcp-server/public/demo.html)
6
-
7
- [![npm](https://img.shields.io/npm/v/@jtalk22/slack-mcp?color=blue&label=npm)](https://www.npmjs.com/package/@jtalk22/slack-mcp)
8
- [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue)](https://github.com/jtalk22/slack-mcp-server/pkgs/container/slack-mcp-server)
9
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
- [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org/)
11
- [![GitHub Sponsors](https://img.shields.io/github/sponsors/jtalk22?label=Sponsor&logo=github)](https://github.com/sponsors/jtalk22)
1
+ <p align="center">
2
+ <a href="https://jtalk22.github.io/slack-mcp-server/public/demo.html"><img src="docs/assets/icon-512.png" alt="Slack MCP Server" width="80"></a>
3
+ </p>
4
+
5
+ <h1 align="center">Slack MCP Server</h1>
6
+
7
+ <p align="center">
8
+ <em>Give Claude the same Slack access you have.<br>
9
+ DMs, threads, history—no admin approval.</em>
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://jtalk22.github.io/slack-mcp-server/public/demo-video.html">
14
+ <img src="docs/images/demo-readme.gif" alt="Slack MCP tools in action" width="640">
15
+ </a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="https://www.npmjs.com/package/@jtalk22/slack-mcp"><img src="https://img.shields.io/badge/npm-Install-blue?style=for-the-badge&logo=npm&logoColor=white" alt="npm"></a>
20
+ <a href="https://github.com/jtalk22/slack-mcp-server/pkgs/container/slack-mcp-server"><img src="https://img.shields.io/badge/Docker-Pull-2496ED?style=for-the-badge&logo=docker&logoColor=white" alt="Docker"></a>
21
+ </p>
22
+
23
+ <p align="center">
24
+ <a href="https://jtalk22.github.io/slack-mcp-server/public/demo.html"><img src="https://img.shields.io/badge/LIVE%20DEMO-Try%20It%20Now-00C853?style=for-the-badge&logo=slack&logoColor=white" alt="Live Demo"></a>
25
+ <a href="https://jtalk22.github.io/slack-mcp-server/public/demo-claude.html"><img src="https://img.shields.io/badge/CLAUDE%20DEMO-See%20MCP%20Tools-da7756?style=for-the-badge" alt="Claude Demo"></a>
26
+ </p>
27
+
28
+ <br>
29
+
30
+ <p align="center">
31
+ <a href="https://www.npmjs.com/package/@jtalk22/slack-mcp"><img src="https://img.shields.io/npm/dm/@jtalk22/slack-mcp?label=downloads&color=CB3837" alt="npm downloads"></a>
32
+ <a href="https://github.com/jtalk22/slack-mcp-server/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/jtalk22/slack-mcp-server/ci.yml?label=build" alt="Build Status"></a>
33
+ <a href="https://smithery.ai/server/jtalk22/slack-mcp-server"><img src="https://img.shields.io/badge/Smithery-Registry-4A154B" alt="Smithery"></a>
34
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
35
+ <a href="https://github.com/sponsors/jtalk22"><img src="https://img.shields.io/badge/Sponsor-♡-ea4aaa?logo=github" alt="Sponsor"></a>
36
+ </p>
12
37
 
13
38
  ---
14
39
 
@@ -22,57 +47,17 @@ This server bridges the gap. It creates a secure, local bridge between Claude an
22
47
 
23
48
  ![Slack MCP Server Web UI](docs/images/demo-main.png)
24
49
 
25
- > **[Try the Interactive Demo](https://jtalk22.github.io/slack-mcp-server/public/demo.html)** - See the Web UI in action
26
-
27
50
  ---
28
51
 
29
52
  ## Architecture: Local Session Mirroring
30
53
 
31
54
  Instead of authenticating as a bot, this server leverages your existing Chrome session credentials (macOS) or manual token injection (Linux/Windows). It mirrors your user access exactly—if you can see it in Slack, Claude can see it too.
32
55
 
33
- ```mermaid
34
- sequenceDiagram
35
- participant Chrome as Chrome Browser
36
- participant Script as AppleScript
37
- participant Store as Token Store
38
- participant MCP as MCP Server
39
- participant Slack as Slack API
40
-
41
- Note over Chrome: You're logged into Slack
42
- Script->>Chrome: Execute JavaScript in Slack tab
43
- Chrome-->>Script: xoxc- token + xoxd- cookie
44
- Script->>Store: Save to ~/.slack-mcp-tokens.json
45
- Store->>Store: Encrypt in macOS Keychain
46
-
47
- Note over MCP: Claude asks for DMs
48
- MCP->>Store: Load credentials
49
- Store-->>MCP: Token + Cookie
50
- MCP->>Slack: GET conversations.history
51
- Slack-->>MCP: Full message history
52
- MCP-->>MCP: Return to Claude
53
- ```
56
+ ![Session Mirroring Flow](docs/images/diagram-session-flow.svg)
54
57
 
55
58
  ### Why Not OAuth?
56
59
 
57
- ```mermaid
58
- flowchart LR
59
- subgraph Traditional["Official Slack API (OAuth)"]
60
- A[Create App] --> B[Request Scopes]
61
- B --> C[Admin Approval]
62
- C --> D[User Authorization]
63
- D --> E[Limited Access]
64
- E --> F["No DMs without<br/>per-conversation consent"]
65
- end
66
-
67
- subgraph ThisServer["Session Mirroring"]
68
- G[Open Slack in Chrome] --> H[Mirror Session]
69
- H --> I[Full Access]
70
- I --> J["Your DMs, Channels,<br/>Search, History"]
71
- end
72
-
73
- style Traditional fill:#ffcccc
74
- style ThisServer fill:#ccffcc
75
- ```
60
+ ![OAuth vs Session Mirroring](docs/images/diagram-oauth-comparison.svg)
76
61
 
77
62
  **Trade-off:** Session tokens expire every 1-2 weeks. Auto-refresh (macOS) or manual update keeps things running.
78
63
 
@@ -113,6 +98,8 @@ flowchart LR
113
98
 
114
99
  ## Quick Start
115
100
 
101
+ **Runtime:** Node.js 20+
102
+
116
103
  ### Option A: npm (Recommended)
117
104
 
118
105
  ```bash
@@ -139,32 +126,37 @@ docker pull ghcr.io/jtalk22/slack-mcp-server:latest
139
126
 
140
127
  ### Step 1: Get Your Tokens
141
128
 
142
- #### macOS (Automatic)
129
+ #### Setup Wizard (Recommended)
130
+
131
+ The interactive setup wizard handles token extraction and validation automatically:
132
+
143
133
  ```bash
144
- # Have Chrome open with Slack (app.slack.com) logged in
145
- npx @jtalk22/slack-mcp tokens:auto
146
- # Or if cloned: npm run tokens:auto
134
+ npx @jtalk22/slack-mcp --setup
147
135
  ```
148
136
 
149
- #### Linux/Windows (Manual)
150
-
151
- Auto-refresh requires macOS + Chrome. On other platforms, extract tokens manually:
152
-
153
- 1. Open https://app.slack.com in your browser
154
- 2. Press F12 → Console → Run:
155
- ```javascript
156
- // Get token
157
- JSON.parse(localStorage.localConfig_v2).teams[Object.keys(JSON.parse(localStorage.localConfig_v2).teams)[0]].token
158
- ```
159
- 3. Press F12 → Application → Cookies → Copy the `d` cookie value (starts with `xoxd-`)
160
- 4. Create `~/.slack-mcp-tokens.json`:
161
- ```json
162
- {
163
- "SLACK_TOKEN": "xoxc-your-token-here",
164
- "SLACK_COOKIE": "xoxd-your-cookie-here",
165
- "updated_at": "2024-01-01T00:00:00.000Z"
166
- }
167
- ```
137
+ - **macOS**: Auto-extracts tokens from Chrome (have Slack open in a tab)
138
+ - **Linux/Windows**: Guides you through manual extraction step-by-step
139
+ - Validates tokens against Slack API before saving
140
+ - Stores tokens securely at `~/.slack-mcp-tokens.json`
141
+
142
+ #### Check Token Status
143
+
144
+ ```bash
145
+ npx @jtalk22/slack-mcp --status
146
+ ```
147
+
148
+ #### Alternative: Manual Token Scripts
149
+
150
+ ```bash
151
+ # macOS auto-extraction
152
+ npm run tokens:auto
153
+
154
+ # Manual entry (all platforms)
155
+ npm run tokens:refresh
156
+
157
+ # Check health
158
+ npm run tokens:status
159
+ ```
168
160
 
169
161
  ### Step 2: Configure Claude
170
162
 
@@ -285,9 +277,9 @@ finally { refreshInProgress = false; }
285
277
 
286
278
  ---
287
279
 
288
- ## Web UI (for claude.ai)
280
+ ## Web UI (for claude.ai — no MCP support)
289
281
 
290
- Since claude.ai doesn't support MCP, use the REST server:
282
+ If you're using claude.ai in a browser (which doesn't support MCP), you can use the REST server instead:
291
283
 
292
284
  ```bash
293
285
  npm run web
@@ -298,7 +290,7 @@ npm run web
298
290
 
299
291
  ```
300
292
  ════════════════════════════════════════════════════════════
301
- Slack Web API Server v1.1.7
293
+ Slack Web API Server v1.2.1
302
294
  ════════════════════════════════════════════════════════════
303
295
 
304
296
  Dashboard: http://localhost:3000/?key=smcp_xxxxxxxxxxxx
package/docs/API.md CHANGED
@@ -199,7 +199,7 @@ Search messages across the workspace.
199
199
  "ts": "1767368030.607599",
200
200
  "channel": "general",
201
201
  "channel_id": "C05GPEVH7J9",
202
- "user": "James Lambert",
202
+ "user": "Example User",
203
203
  "text": "Here's the project update...",
204
204
  "datetime": "2026-01-02T15:33:50.000Z",
205
205
  "permalink": "https://..."
package/docs/SETUP.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Prerequisites
4
4
 
5
- - Node.js 18+
5
+ - Node.js 20+
6
6
  - Google Chrome (for token extraction)
7
7
  - macOS (for Keychain storage - other platforms use file storage only)
8
8
 
Binary file
@@ -0,0 +1,27 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#4A154B"/>
5
+ <stop offset="100%" style="stop-color:#3D1140"/>
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <!-- Background rounded square -->
10
+ <rect x="0" y="0" width="512" height="512" rx="96" ry="96" fill="url(#bg)"/>
11
+
12
+ <!-- Stylized hashtag/channel symbol -->
13
+ <g fill="none" stroke="#FFFFFF" stroke-width="36" stroke-linecap="round">
14
+ <!-- Vertical lines -->
15
+ <line x1="180" y1="140" x2="160" y2="372"/>
16
+ <line x1="352" y1="140" x2="332" y2="372"/>
17
+ <!-- Horizontal lines -->
18
+ <line x1="120" y1="200" x2="392" y2="200"/>
19
+ <line x1="120" y1="312" x2="392" y2="312"/>
20
+ </g>
21
+
22
+ <!-- Slack-colored connection dots (representing MCP bridge) -->
23
+ <circle cx="420" cy="92" r="28" fill="#36C5F0"/>
24
+ <circle cx="92" cy="420" r="28" fill="#2EB67D"/>
25
+ <circle cx="420" cy="420" r="28" fill="#ECB22E"/>
26
+ <circle cx="92" cy="92" r="28" fill="#E01E5A"/>
27
+ </svg>
Binary file
Binary file
Binary file
@@ -0,0 +1,80 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 420">
2
+ <defs>
3
+ <linearGradient id="bgGrad2" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#1a1a2e"/>
5
+ <stop offset="100%" style="stop-color:#16213e"/>
6
+ </linearGradient>
7
+ <marker id="arrowRed2" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
8
+ <path d="M0,0 L0,6 L9,3 z" fill="#e94560"/>
9
+ </marker>
10
+ <marker id="arrowTeal2" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
11
+ <path d="M0,0 L0,6 L9,3 z" fill="#4ecdc4"/>
12
+ </marker>
13
+ </defs>
14
+
15
+ <!-- Background -->
16
+ <rect width="900" height="420" fill="url(#bgGrad2)" rx="12"/>
17
+
18
+ <!-- Left Side: OAuth (Red-tinted) -->
19
+ <rect x="30" y="50" width="400" height="340" rx="12" fill="#e94560" opacity="0.1" stroke="#e94560" stroke-width="2"/>
20
+ <text x="230" y="85" text-anchor="middle" fill="#fca5a5" font-family="system-ui, -apple-system, sans-serif" font-size="16" font-weight="600">Official Slack API (OAuth)</text>
21
+
22
+ <!-- OAuth Flow - Row 1 -->
23
+ <rect x="45" y="110" width="120" height="45" rx="8" fill="#2d2d2d" stroke="#e94560" stroke-width="1"/>
24
+ <text x="105" y="138" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12">Create App</text>
25
+
26
+ <path d="M 170 132 L 190 132" stroke="#e94560" stroke-width="2" marker-end="url(#arrowRed2)"/>
27
+
28
+ <rect x="195" y="110" width="120" height="45" rx="8" fill="#2d2d2d" stroke="#e94560" stroke-width="1"/>
29
+ <text x="255" y="138" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12">Request Scopes</text>
30
+
31
+ <path d="M 320 132 L 340 132" stroke="#e94560" stroke-width="2" marker-end="url(#arrowRed2)"/>
32
+
33
+ <rect x="345" y="110" width="75" height="45" rx="8" fill="#e94560" opacity="0.4" stroke="#e94560" stroke-width="1"/>
34
+ <text x="382" y="128" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="600">Admin</text>
35
+ <text x="382" y="144" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="600">Approval</text>
36
+
37
+ <!-- Arrow down from Admin Approval -->
38
+ <path d="M 382 160 L 382 195" stroke="#e94560" stroke-width="2" marker-end="url(#arrowRed2)"/>
39
+
40
+ <!-- OAuth Flow - Row 2 -->
41
+ <rect x="95" y="205" width="140" height="45" rx="8" fill="#2d2d2d" stroke="#e94560" stroke-width="1"/>
42
+ <text x="165" y="233" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12">User Authorization</text>
43
+
44
+ <path d="M 240 227 L 260 227" stroke="#e94560" stroke-width="2" marker-end="url(#arrowRed2)"/>
45
+
46
+ <rect x="265" y="205" width="140" height="45" rx="8" fill="#2d2d2d" stroke="#e94560" stroke-width="1"/>
47
+ <text x="335" y="233" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12">Limited Access</text>
48
+
49
+ <!-- OAuth Result (blocked) -->
50
+ <rect x="70" y="280" width="290" height="60" rx="8" fill="#e94560" opacity="0.15" stroke="#e94560" stroke-width="2" stroke-dasharray="4"/>
51
+ <text x="215" y="305" text-anchor="middle" fill="#fca5a5" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">No DMs without</text>
52
+ <text x="215" y="325" text-anchor="middle" fill="#fca5a5" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">per-conversation consent</text>
53
+
54
+ <!-- Right Side: Session Mirroring (Green-tinted) -->
55
+ <rect x="470" y="50" width="400" height="340" rx="12" fill="#4ecdc4" opacity="0.1" stroke="#4ecdc4" stroke-width="2"/>
56
+ <text x="670" y="85" text-anchor="middle" fill="#5eead4" font-family="system-ui, -apple-system, sans-serif" font-size="16" font-weight="600">Session Mirroring</text>
57
+
58
+ <!-- Session Mirroring Flow -->
59
+ <rect x="495" y="120" width="160" height="50" rx="8" fill="#3b82f6" opacity="0.9"/>
60
+ <text x="575" y="150" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12" font-weight="500">Open Slack in Chrome</text>
61
+
62
+ <path d="M 660 145 L 700 145" stroke="#4ecdc4" stroke-width="3" marker-end="url(#arrowTeal2)"/>
63
+
64
+ <rect x="705" y="120" width="140" height="50" rx="8" fill="#da7756" opacity="0.9"/>
65
+ <text x="775" y="150" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12" font-weight="500">Mirror Session</text>
66
+
67
+ <path d="M 775 175 L 775 210" stroke="#4ecdc4" stroke-width="3" marker-end="url(#arrowTeal2)"/>
68
+
69
+ <rect x="605" y="220" width="160" height="50" rx="8" fill="#4ecdc4" opacity="0.9"/>
70
+ <text x="685" y="250" text-anchor="middle" fill="#1a1a2e" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600">Full Access</text>
71
+
72
+ <!-- Session Mirroring Result (success) -->
73
+ <rect x="510" y="295" width="340" height="60" rx="8" fill="#4ecdc4" opacity="0.2" stroke="#4ecdc4" stroke-width="2"/>
74
+ <text x="680" y="320" text-anchor="middle" fill="#5eead4" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">Your DMs, Channels, Search, History</text>
75
+ <text x="680" y="340" text-anchor="middle" fill="#5eead4" font-family="system-ui, -apple-system, sans-serif" font-size="11">✓ Same access as your browser</text>
76
+
77
+ <!-- VS divider -->
78
+ <circle cx="450" cy="210" r="24" fill="#1a1a2e" stroke="#ffffff" stroke-width="2" opacity="0.9"/>
79
+ <text x="450" y="216" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="700">vs</text>
80
+ </svg>
@@ -0,0 +1,105 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 400">
2
+ <defs>
3
+ <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#1a1a2e"/>
5
+ <stop offset="100%" style="stop-color:#16213e"/>
6
+ </linearGradient>
7
+ <filter id="glow">
8
+ <feGaussianBlur stdDeviation="2" result="coloredBlur"/>
9
+ <feMerge>
10
+ <feMergeNode in="coloredBlur"/>
11
+ <feMergeNode in="SourceGraphic"/>
12
+ </feMerge>
13
+ </filter>
14
+ </defs>
15
+
16
+ <!-- Background -->
17
+ <rect width="900" height="400" fill="url(#bgGrad)" rx="12"/>
18
+
19
+ <!-- Title -->
20
+ <text x="450" y="35" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="18" font-weight="600">Session Mirroring Flow</text>
21
+
22
+ <!-- Participants -->
23
+ <!-- Chrome -->
24
+ <rect x="50" y="60" width="120" height="40" rx="8" fill="#3b82f6" opacity="0.9"/>
25
+ <text x="110" y="85" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">Chrome</text>
26
+ <line x1="110" y1="100" x2="110" y2="360" stroke="#3b82f6" stroke-width="2" stroke-dasharray="4"/>
27
+
28
+ <!-- AppleScript -->
29
+ <rect x="220" y="60" width="120" height="40" rx="8" fill="#8b5cf6" opacity="0.9"/>
30
+ <text x="280" y="85" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">AppleScript</text>
31
+ <line x1="280" y1="100" x2="280" y2="360" stroke="#8b5cf6" stroke-width="2" stroke-dasharray="4"/>
32
+
33
+ <!-- Token Store -->
34
+ <rect x="390" y="60" width="120" height="40" rx="8" fill="#4ecdc4" opacity="0.9"/>
35
+ <text x="450" y="85" text-anchor="middle" fill="#1a1a2e" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">Token Store</text>
36
+ <line x1="450" y1="100" x2="450" y2="360" stroke="#4ecdc4" stroke-width="2" stroke-dasharray="4"/>
37
+
38
+ <!-- MCP Server -->
39
+ <rect x="560" y="60" width="120" height="40" rx="8" fill="#da7756" opacity="0.9"/>
40
+ <text x="620" y="85" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">MCP Server</text>
41
+ <line x1="620" y1="100" x2="620" y2="360" stroke="#da7756" stroke-width="2" stroke-dasharray="4"/>
42
+
43
+ <!-- Slack API -->
44
+ <rect x="730" y="60" width="120" height="40" rx="8" fill="#e94560" opacity="0.9"/>
45
+ <text x="790" y="85" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">Slack API</text>
46
+ <line x1="790" y1="100" x2="790" y2="360" stroke="#e94560" stroke-width="2" stroke-dasharray="4"/>
47
+
48
+ <!-- Note: Logged into Slack -->
49
+ <rect x="60" y="115" width="100" height="30" rx="4" fill="#3b82f6" opacity="0.2" stroke="#3b82f6" stroke-width="1"/>
50
+ <text x="110" y="135" text-anchor="middle" fill="#93c5fd" font-family="system-ui, -apple-system, sans-serif" font-size="10">Logged into Slack</text>
51
+
52
+ <!-- Arrow 1: Script → Chrome -->
53
+ <line x1="280" y1="160" x2="130" y2="160" stroke="#8b5cf6" stroke-width="2" marker-end="url(#arrowPurple)"/>
54
+ <text x="205" y="153" text-anchor="middle" fill="#c4b5fd" font-family="system-ui, -apple-system, sans-serif" font-size="10">Execute JS in tab</text>
55
+
56
+ <!-- Arrow 2: Chrome → Script (dashed return) -->
57
+ <line x1="130" y1="185" x2="260" y2="185" stroke="#3b82f6" stroke-width="2" stroke-dasharray="5" marker-end="url(#arrowBlue)"/>
58
+ <text x="195" y="200" text-anchor="middle" fill="#93c5fd" font-family="system-ui, -apple-system, sans-serif" font-size="10">xoxc- + xoxd- tokens</text>
59
+
60
+ <!-- Arrow 3: Script → Store -->
61
+ <line x1="300" y1="225" x2="430" y2="225" stroke="#8b5cf6" stroke-width="2" marker-end="url(#arrowPurple)"/>
62
+ <text x="365" y="218" text-anchor="middle" fill="#c4b5fd" font-family="system-ui, -apple-system, sans-serif" font-size="10">Save tokens</text>
63
+
64
+ <!-- Store self-arrow (Keychain) -->
65
+ <path d="M 470 245 Q 500 245 500 260 Q 500 275 470 275" fill="none" stroke="#4ecdc4" stroke-width="2"/>
66
+ <text x="515" y="265" fill="#5eead4" font-family="system-ui, -apple-system, sans-serif" font-size="10">Keychain encrypt</text>
67
+
68
+ <!-- Divider -->
69
+ <line x1="50" y1="300" x2="850" y2="300" stroke="#ffffff" stroke-width="1" opacity="0.2"/>
70
+ <text x="60" y="295" fill="#ffffff" opacity="0.5" font-family="system-ui, -apple-system, sans-serif" font-size="10">Claude requests data</text>
71
+
72
+ <!-- Arrow 4: MCP → Store -->
73
+ <line x1="600" y1="320" x2="470" y2="320" stroke="#da7756" stroke-width="2" marker-end="url(#arrowOrange)"/>
74
+ <text x="535" y="313" text-anchor="middle" fill="#fbbf8c" font-family="system-ui, -apple-system, sans-serif" font-size="10">Load creds</text>
75
+
76
+ <!-- Arrow 5: Store → MCP (return) -->
77
+ <line x1="470" y1="340" x2="600" y2="340" stroke="#4ecdc4" stroke-width="2" stroke-dasharray="5" marker-end="url(#arrowTeal)"/>
78
+
79
+ <!-- Arrow 6: MCP → Slack -->
80
+ <line x1="640" y1="355" x2="770" y2="355" stroke="#da7756" stroke-width="2" marker-end="url(#arrowOrange)"/>
81
+ <text x="705" y="348" text-anchor="middle" fill="#fbbf8c" font-family="system-ui, -apple-system, sans-serif" font-size="10">GET messages</text>
82
+
83
+ <!-- Arrow 7: Slack → MCP (return) -->
84
+ <line x1="770" y1="375" x2="640" y2="375" stroke="#e94560" stroke-width="2" stroke-dasharray="5" marker-end="url(#arrowRed)"/>
85
+ <text x="705" y="390" text-anchor="middle" fill="#fca5a5" font-family="system-ui, -apple-system, sans-serif" font-size="10">Full history</text>
86
+
87
+ <!-- Arrow markers -->
88
+ <defs>
89
+ <marker id="arrowPurple" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
90
+ <path d="M0,0 L0,6 L9,3 z" fill="#8b5cf6"/>
91
+ </marker>
92
+ <marker id="arrowBlue" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
93
+ <path d="M0,0 L0,6 L9,3 z" fill="#3b82f6"/>
94
+ </marker>
95
+ <marker id="arrowTeal" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
96
+ <path d="M0,0 L0,6 L9,3 z" fill="#4ecdc4"/>
97
+ </marker>
98
+ <marker id="arrowOrange" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
99
+ <path d="M0,0 L0,6 L9,3 z" fill="#da7756"/>
100
+ </marker>
101
+ <marker id="arrowRed" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
102
+ <path d="M0,0 L0,6 L9,3 z" fill="#e94560"/>
103
+ </marker>
104
+ </defs>
105
+ </svg>
File without changes
@@ -24,6 +24,15 @@ const RETRYABLE_NETWORK_ERRORS = [
24
24
  'ECONNREFUSED', 'EPIPE', 'UND_ERR_CONNECT_TIMEOUT'
25
25
  ];
26
26
 
27
+ // Slack API methods that require form-encoded params instead of JSON
28
+ const FORM_ENCODED_METHODS = new Set([
29
+ "conversations.replies",
30
+ "search.messages",
31
+ "search.all",
32
+ "search.files",
33
+ "users.info",
34
+ ]);
35
+
27
36
  let lastRefreshAttempt = 0;
28
37
 
29
38
  // ============ LRU Cache with TTL ============
@@ -149,16 +158,36 @@ export async function slackAPI(method, params = {}, options = {}) {
149
158
  checkTokenHealth({ error: () => {} }).catch(() => {});
150
159
  }
151
160
 
161
+ const useForm = FORM_ENCODED_METHODS.has(method);
162
+
152
163
  let response;
153
164
  try {
165
+ const headers = {
166
+ "Authorization": `Bearer ${creds.token}`,
167
+ "Cookie": `d=${creds.cookie}`,
168
+ };
169
+
170
+ let body;
171
+ if (useForm) {
172
+ // URLSearchParams coerces non-primitives to "[object Object]",
173
+ // so stringify any arrays/objects before encoding.
174
+ const safeParams = {};
175
+ for (const [key, value] of Object.entries(params)) {
176
+ safeParams[key] = (typeof value === "object" && value !== null)
177
+ ? JSON.stringify(value)
178
+ : String(value);
179
+ }
180
+ headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8";
181
+ body = new URLSearchParams(safeParams).toString();
182
+ } else {
183
+ headers["Content-Type"] = "application/json; charset=utf-8";
184
+ body = JSON.stringify(params);
185
+ }
186
+
154
187
  response = await fetch(`https://slack.com/api/${method}`, {
155
188
  method: "POST",
156
- headers: {
157
- "Authorization": `Bearer ${creds.token}`,
158
- "Cookie": `d=${creds.cookie}`,
159
- "Content-Type": "application/json; charset=utf-8",
160
- },
161
- body: JSON.stringify(params),
189
+ headers,
190
+ body,
162
191
  });
163
192
  } catch (networkError) {
164
193
  // Retry on network errors with exponential backoff + jitter