@jtalk22/slack-mcp 4.0.0 → 4.1.0

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/README.md CHANGED
@@ -10,11 +10,13 @@ Give your AI agent full Slack access. No app registration, no admin approval, no
10
10
  npx -y @jtalk22/slack-mcp --setup
11
11
  ```
12
12
 
13
- ![demo](docs/images/demo-readme.gif)
13
+ [![Slack MCP Server Demo](docs/images/demo-poster.png)](https://jtalk22.github.io/slack-mcp-server/public/demo-video.html)
14
14
 
15
- > **Ask Claude to catch you up on #engineering from the last 24 hours.** Search for that deployment thread from last week. Find every message mentioning the API key. Send a reply. All from your editor.
15
+ **[▶ Watch the demo](https://jtalk22.github.io/slack-mcp-server/public/demo-video.html)** 7 scenarios, from 47 unreads to inbox zero, without opening Slack.
16
16
 
17
- [Interactive demo](https://jtalk22.github.io/slack-mcp-server/public/demo.html) · [Latest release](https://github.com/jtalk22/slack-mcp-server/releases/latest)
17
+ > **Ask your AI to catch you up on #engineering from the last 24 hours.** Search for that deployment thread from last week. Find the printer admin PIN nobody can remember. Send a reply. All from your editor.
18
+
19
+ [Interactive demo](https://jtalk22.github.io/slack-mcp-server/public/demo-slack-mcp.html) · [Latest release](https://github.com/jtalk22/slack-mcp-server/releases/latest)
18
20
 
19
21
  ## Why This Exists
20
22
 
@@ -22,7 +24,90 @@ Slack's official MCP server requires a registered app, admin approval, and [does
22
24
 
23
25
  This server uses your browser's session tokens instead. If you can see it in Slack, your AI agent can see it too. No app install, no scopes, no admin.
24
26
 
25
- ![OAuth vs Session](docs/images/diagram-oauth-comparison.svg)
27
+ **Stealth Mode:** Session tokens leave zero footprint in your workspace admin panel. No bot user appears, no app install shows up, no audit trail. Your AI agent operates with the same invisibility as your browser tab.
28
+
29
+ ![OAuth vs Chrome DB Decryption](docs/images/diagram-oauth-comparison.svg)
30
+
31
+ | | Slack Official MCP | This Server |
32
+ |---|---|---|
33
+ | OAuth app required | Yes | **No** |
34
+ | Admin approval | Yes | **No** |
35
+ | Works with Claude Code | No (DCR incompatible) | **Yes** |
36
+ | Works with Cursor | No | **Yes** |
37
+ | Works with Copilot | No | **Yes** |
38
+ | Works with Windsurf | No | **Yes** |
39
+ | Works with Gemini CLI | No | **Yes** |
40
+ | Works with Codex CLI | No | **Yes** |
41
+ | Setup time | ~30 min | **~2 min** |
42
+ | Tools | Limited | **16** |
43
+ | Visible to admins | Yes | **No — Stealth Mode** |
44
+
45
+ ## Quick Start per Client
46
+
47
+ <details>
48
+ <summary><strong>Claude Desktop / Claude Code</strong></summary>
49
+
50
+ Add to `~/.claude.json` or Claude Desktop settings:
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "slack": { "command": "npx", "args": ["-y", "@jtalk22/slack-mcp"] }
55
+ }
56
+ }
57
+ ```
58
+ </details>
59
+
60
+ <details>
61
+ <summary><strong>Cursor</strong></summary>
62
+
63
+ Add to `.cursor/mcp.json`:
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "slack": { "command": "npx", "args": ["-y", "@jtalk22/slack-mcp"] }
68
+ }
69
+ }
70
+ ```
71
+ </details>
72
+
73
+ <details>
74
+ <summary><strong>Windsurf</strong></summary>
75
+
76
+ Add to `~/.codeium/windsurf/mcp_config.json`:
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "slack": { "command": "npx", "args": ["-y", "@jtalk22/slack-mcp"] }
81
+ }
82
+ }
83
+ ```
84
+ </details>
85
+
86
+ <details>
87
+ <summary><strong>Gemini CLI</strong></summary>
88
+
89
+ Add to `~/.gemini/settings.json`:
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "slack": { "command": "npx", "args": ["-y", "@jtalk22/slack-mcp"] }
94
+ }
95
+ }
96
+ ```
97
+ </details>
98
+
99
+ <details>
100
+ <summary><strong>Codex CLI</strong></summary>
101
+
102
+ Add to `~/.codex/config.toml`:
103
+ ```toml
104
+ [mcp_servers.slack]
105
+ command = "npx"
106
+ args = ["-y", "@jtalk22/slack-mcp"]
107
+ ```
108
+
109
+ Or via CLI: `codex mcp add slack -- npx -y @jtalk22/slack-mcp`
110
+ </details>
26
111
 
27
112
  ## Tools
28
113
 
@@ -145,13 +230,7 @@ On macOS, tokens are auto-extracted from Chrome — `env` block is optional.
145
230
  <details>
146
231
  <summary><strong>Claude Web / Remote MCP</strong></summary>
147
232
 
148
- For browser-based clients that can't run local processes, use the hosted HTTP endpoint:
149
-
150
- ```
151
- https://mcp.revasserlabs.com/oauth/mcp
152
- ```
153
-
154
- Add this as a remote MCP server in your client's settings. Transport: Streamable HTTP. Auth: OAuth 2.1 + PKCE.
233
+ Hosted version with permanent OAuth tokens coming soon. See [mcp.revasserlabs.com](https://mcp.revasserlabs.com) for updates.
155
234
 
156
235
  </details>
157
236
 
@@ -247,4 +326,4 @@ Not affiliated with Slack Technologies, Inc. Uses browser session credentials
247
326
 
248
327
  ---
249
328
 
250
- Managed hosting available — [mcp.revasserlabs.com](https://mcp.revasserlabs.com)
329
+ Hosted version with semantic search, AI summaries, and permanent OAuth coming soon at [mcp.revasserlabs.com](https://mcp.revasserlabs.com)
package/docs/SETUP.md CHANGED
@@ -1,40 +1,8 @@
1
1
  # Setup Guide
2
2
 
3
- ## Cloud
3
+ ## Hosted (Coming Soon)
4
4
 
5
- Use **Slack MCP Cloud** when you want a managed endpoint rather than local token handling:
6
-
7
- 1. Go to [Slack MCP Cloud](https://mcp.revasserlabs.com) and purchase a plan ($19/mo Solo, $49/mo Team)
8
- 2. After checkout, you'll receive an API key and ready-to-paste config for Claude Desktop / Claude Code
9
- 3. Use the hosted endpoint and API key — one URL, 15 managed tools. Team adds 3 AI workflows.
10
-
11
- **Claude Desktop config (Cloud):**
12
- ```json
13
- {
14
- "mcpServers": {
15
- "slack": {
16
- "url": "https://mcp.revasserlabs.com/oauth/mcp"
17
- }
18
- }
19
- }
20
- ```
21
-
22
- **Claude Code config (Cloud):**
23
- ```json
24
- {
25
- "mcpServers": {
26
- "slack": {
27
- "type": "sse",
28
- "url": "https://mcp.revasserlabs.com/mcp",
29
- "headers": {
30
- "Authorization": "Bearer YOUR_API_KEY"
31
- }
32
- }
33
- }
34
- }
35
- ```
36
-
37
- If you prefer self-hosting, continue below.
5
+ A hosted version with permanent OAuth tokens, semantic search, and AI summaries is in development at [mcp.revasserlabs.com](https://mcp.revasserlabs.com). The current release is self-hosted only — continue below.
38
6
 
39
7
  ---
40
8
 
@@ -29,31 +29,9 @@ If `--version` fails here, the issue is install/runtime path, not Slack credenti
29
29
 
30
30
  ---
31
31
 
32
- ## Cloud Issues
32
+ ## Hosted Version
33
33
 
34
- ### API Key Not Working
35
-
36
- **Symptom:** `401 Unauthorized` or `403 Forbidden` when using Cloud endpoint.
37
-
38
- **Solutions:**
39
- 1. Verify your API key starts with `stmh_` (team) or `smsh_` (solo)
40
- 2. Check the key hasn't been revoked — contact support@revasserlabs.com for key issues
41
- 3. Ensure you're using the correct endpoint: `https://mcp.revasserlabs.com/mcp`
42
-
43
- ### Cloud Tools Not Available
44
-
45
- **Symptom:** Only seeing fewer tools than expected.
46
-
47
- **Cause:** AI compound tools (`slack_channel_summary`, `slack_extract_action_items`, `slack_find_decisions`) are Team plan only ($49/mo).
48
-
49
- **Solution:** Upgrade to Team plan for AI compound tools, or use the 15 standard managed tools available on all Cloud plans.
50
-
51
- ### Cloud Endpoint Health Check
52
-
53
- ```bash
54
- curl -s https://mcp.revasserlabs.com/health | jq .
55
- # Expected: {"status":"healthy","server":"slack-mcp-hosted","version":"0.5.0"}
56
- ```
34
+ A hosted version with permanent OAuth tokens, semantic search, and AI summaries is coming soon at [mcp.revasserlabs.com](https://mcp.revasserlabs.com). The current release is self-hosted only.
57
35
 
58
36
  ---
59
37
 
package/lib/handlers.js CHANGED
@@ -216,7 +216,7 @@ export async function handleRefreshTokens() {
216
216
  code: extractionError.code,
217
217
  message: extractionError.message,
218
218
  detail: extractionError.detail,
219
- next_action: "In Chrome: View > Developer > Allow JavaScript from Apple Events, then retry."
219
+ next_action: "In Chrome: View > Developer > Allow JavaScript from Apple Events, then retry. (Only needed for token — cookie is extracted from Chrome's database automatically.)"
220
220
  }, true);
221
221
  }
222
222
 
@@ -15,7 +15,7 @@ const ICON_URL = `${GITHUB_PAGES_ROOT}/docs/assets/icon-512.png`;
15
15
  const NPM_URL = "https://www.npmjs.com/package/@jtalk22/slack-mcp";
16
16
  const RELEASES_URL = `${PUBLIC_METADATA.canonicalRepoUrl}/releases/latest`;
17
17
  const SETUP_URL = `${PUBLIC_METADATA.canonicalRepoUrl}/blob/main/docs/SETUP.md`;
18
- const DEMO_VIDEO_URL = `${GITHUB_PAGES_ROOT}/docs/videos/demo-claude-mobile-20s.mp4`;
18
+ const DEMO_VIDEO_URL = `${GITHUB_PAGES_ROOT}/docs/videos/demo-slack-mcp-mobile-20s.mp4`;
19
19
 
20
20
  function template(name) {
21
21
  return readFileSync(resolve(TEMPLATE_DIR, name), "utf8");
@@ -58,28 +58,28 @@ function shareLinks() {
58
58
  <a href="${GITHUB_PAGES_ROOT}/" rel="noopener">Autoplay Demo Landing</a>
59
59
  <a href="${DEMO_VIDEO_URL}" rel="noopener">20s Mobile Clip</a>
60
60
  <a href="${NPM_URL}" rel="noopener">npm Package</a>
61
- <a href="${PUBLIC_METADATA.canonicalSiteUrl}" rel="noopener" style="background:rgba(240,194,70,0.18);border-color:rgba(240,194,70,0.45);color:#f0c246">Cloud</a>
61
+ <a href="${PUBLIC_METADATA.canonicalSiteUrl}" rel="noopener" style="background:rgba(240,194,70,0.18);border-color:rgba(240,194,70,0.45);color:#f0c246">Hosted</a>
62
62
  `.trim();
63
63
  }
64
64
 
65
65
  function shareNote() {
66
- return `<strong>Verify in 30 seconds:</strong> <code>--version</code>, <code>--doctor</code>, <code>--status</code>. Self-host gives ${PUBLIC_METADATA.selfHostedToolCount} tools with session-based auth. Works with any MCP client — Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf. Managed hosting available at <a href="${PUBLIC_METADATA.canonicalSiteUrl}">mcp.revasserlabs.com</a>.`;
66
+ return `<strong>Verify in 30 seconds:</strong> <code>--version</code>, <code>--doctor</code>, <code>--status</code>. Self-host gives ${PUBLIC_METADATA.selfHostedToolCount} tools with session-based auth. Works with any MCP client — Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf. Hosted version coming soon at <a href="${PUBLIC_METADATA.canonicalSiteUrl}">mcp.revasserlabs.com</a>.`;
67
67
  }
68
68
 
69
69
  function demoLinks() {
70
70
  return `
71
- <a href="${PUBLIC_METADATA.canonicalSiteUrl}" target="_blank" rel="noopener noreferrer" style="background:rgba(240,194,70,0.18);border-color:rgba(240,194,70,0.45);color:#f0c246">Cloud</a>
71
+ <a href="${PUBLIC_METADATA.canonicalSiteUrl}" target="_blank" rel="noopener noreferrer" style="background:rgba(240,194,70,0.18);border-color:rgba(240,194,70,0.45);color:#f0c246">Hosted</a>
72
72
  <a href="${NPM_URL}" target="_blank" rel="noopener noreferrer">npm Install</a>
73
73
  <a href="${SETUP_URL}" target="_blank" rel="noopener noreferrer">Setup Guide</a>
74
74
  `.trim();
75
75
  }
76
76
 
77
77
  function demoNote() {
78
- return `Self-host free for ${PUBLIC_METADATA.selfHostedToolCount} tools with session-based auth. Works with Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf, and any other MCP client. No OAuth app, no admin approval. Managed hosting available at <a href="${PUBLIC_METADATA.canonicalSiteUrl}" target="_blank" rel="noopener noreferrer">mcp.revasserlabs.com</a>.`;
78
+ return `Self-host free for ${PUBLIC_METADATA.selfHostedToolCount} tools with session-based auth. Works with Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf, and any other MCP client. No OAuth app, no admin approval. Hosted version coming soon at <a href="${PUBLIC_METADATA.canonicalSiteUrl}" target="_blank" rel="noopener noreferrer">mcp.revasserlabs.com</a>.`;
79
79
  }
80
80
 
81
81
  function demoFooterLinks() {
82
- return `<a href="${PUBLIC_METADATA.canonicalRepoUrl}">GitHub</a> · <a href="${NPM_URL}" style="color:#94a3b8;text-decoration:none;font-size:0.875rem">npm</a> · <a href="${PUBLIC_METADATA.canonicalSiteUrl}" style="color:#f0c246;text-decoration:none;font-size:0.875rem">Cloud</a>`;
82
+ return `<a href="${PUBLIC_METADATA.canonicalRepoUrl}">GitHub</a> · <a href="${NPM_URL}" style="color:#94a3b8;text-decoration:none;font-size:0.875rem">npm</a> · <a href="${PUBLIC_METADATA.canonicalSiteUrl}" style="color:#f0c246;text-decoration:none;font-size:0.875rem">Hosted</a>`;
83
83
  }
84
84
 
85
85
  function commonTokens() {
@@ -113,10 +113,10 @@ function commonTokens() {
113
113
  SELF_HOSTED_TOOL_COUNT: String(PUBLIC_METADATA.selfHostedToolCount),
114
114
  CLOUD_MANAGED_TOOL_COUNT: "15",
115
115
  TEAM_AI_WORKFLOW_COUNT: "3",
116
- CLOUD_SOLO_PRICE: "$19/mo",
117
- CLOUD_TEAM_PRICE: "$49/mo",
118
- CLOUD_TURNKEY_LAUNCH_PRICE: "$2.5k+",
119
- CLOUD_MANAGED_RELIABILITY_PRICE: "$800/mo+",
116
+ CLOUD_SOLO_PRICE: "coming soon",
117
+ CLOUD_TEAM_PRICE: "coming soon",
118
+ CLOUD_TURNKEY_LAUNCH_PRICE: "contact us",
119
+ CLOUD_MANAGED_RELIABILITY_PRICE: "contact us",
120
120
  SUPPORT_EMAIL: PUBLIC_METADATA.supportEmail,
121
121
  ROOT_DECISION_PANEL: rootDecisionPanel(),
122
122
  SHARE_LINKS: shareLinks(),
@@ -134,6 +134,6 @@ export function buildPublicPages() {
134
134
  "public/share.html": replaceTokens(template("share.html.tpl"), tokens),
135
135
  "public/demo.html": replaceTokens(template("demo.html.tpl"), tokens),
136
136
  "public/demo-video.html": replaceTokens(template("demo-video.html.tpl"), tokens),
137
- "public/demo-claude.html": replaceTokens(template("demo-claude.html.tpl"), tokens),
137
+ "public/demo-slack-mcp.html": replaceTokens(template("demo-slack-mcp.html.tpl"), tokens),
138
138
  };
139
139
  }
@@ -8,10 +8,11 @@
8
8
  * 4. Chrome auto-extraction (fallback)
9
9
  */
10
10
 
11
- import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync, chmodSync } from "fs";
12
- import { homedir, platform } from "os";
11
+ import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync, chmodSync, copyFileSync, mkdtempSync } from "fs";
12
+ import { homedir, platform, tmpdir } from "os";
13
13
  import { join } from "path";
14
- import { execSync, execFileSync } from "child_process";
14
+ import { execFileSync } from "child_process";
15
+ import { pbkdf2Sync, createDecipheriv } from "crypto";
15
16
 
16
17
  const TOKEN_FILE = join(homedir(), ".slack-mcp-tokens.json");
17
18
  const KEYCHAIN_SERVICE = "slack-mcp-server";
@@ -102,32 +103,31 @@ export function saveToFile(token, cookie) {
102
103
 
103
104
  // Multiple localStorage paths Slack might use (for robustness)
104
105
  const SLACK_TOKEN_PATHS = [
105
- // Current known path
106
106
  `JSON.parse(localStorage.localConfig_v2).teams[Object.keys(JSON.parse(localStorage.localConfig_v2).teams)[0]].token`,
107
- // Potential future paths
108
107
  `JSON.parse(localStorage.localConfig_v3).teams[Object.keys(JSON.parse(localStorage.localConfig_v3).teams)[0]].token`,
109
- // Redux store path (older Slack)
110
108
  `JSON.parse(localStorage.getItem('reduxPersist:localConfig'))?.teams?.[Object.keys(JSON.parse(localStorage.getItem('reduxPersist:localConfig'))?.teams || {})[0]]?.token`,
111
- // Direct boot data
112
109
  `window.boot_data?.api_token`,
113
110
  ];
114
111
 
112
+ // Chrome profile directories to search (in priority order)
113
+ const CHROME_PROFILES = ['Default', 'Profile 1', 'Profile 2', 'Profile 3'];
114
+
115
115
  function normalizeExtractionError(error) {
116
116
  const raw = String(error?.message || error || "");
117
117
 
118
118
  if (raw.includes("Executing JavaScript through AppleScript is turned off")) {
119
119
  return {
120
120
  code: "apple_events_javascript_disabled",
121
- message: "Chrome blocked JavaScript execution from Apple Events.",
122
- detail: "Enable it in Chrome: View > Developer > Allow JavaScript from Apple Events."
121
+ message: "Chrome needs one setting enabled for token extraction.",
122
+ detail: "In Chrome: View > Developer > Allow JavaScript from Apple Events. Cookie extraction works without this — only the token needs it."
123
123
  };
124
124
  }
125
125
 
126
126
  if (raw.includes("Application isn't running") || raw.includes("Google Chrome got an error")) {
127
127
  return {
128
128
  code: "chrome_not_ready",
129
- message: "Chrome is not ready for token extraction.",
130
- detail: "Open Google Chrome with an active Slack tab at app.slack.com."
129
+ message: "Chrome is not running or has no windows open.",
130
+ detail: "Open Google Chrome with a Slack tab at app.slack.com."
131
131
  };
132
132
  }
133
133
 
@@ -139,6 +139,14 @@ function normalizeExtractionError(error) {
139
139
  };
140
140
  }
141
141
 
142
+ if (raw.includes("Chrome Safe Storage")) {
143
+ return {
144
+ code: "keychain_access_denied",
145
+ message: "Could not access Chrome's encryption key in Keychain.",
146
+ detail: "You may need to allow terminal access in System Settings > Privacy > Full Disk Access."
147
+ };
148
+ }
149
+
142
150
  return {
143
151
  code: "chrome_extraction_failed",
144
152
  message: "Chrome token extraction failed.",
@@ -147,13 +155,133 @@ function normalizeExtractionError(error) {
147
155
  }
148
156
 
149
157
  /**
150
- * Extract tokens from Chrome (macOS only, uses AppleScript)
151
- * Returns null on non-macOS platforms
158
+ * Extract the Slack session cookie from Chrome's encrypted cookie database.
159
+ * The `d` cookie is HttpOnly — JavaScript cannot access it via document.cookie.
160
+ * This reads Chrome's SQLite cookie store and decrypts using the Keychain-stored key.
161
+ */
162
+ function extractCookieFromChromeDB() {
163
+ const chromeBase = join(homedir(), 'Library', 'Application Support', 'Google', 'Chrome');
164
+
165
+ // Find the first profile with a Slack d cookie
166
+ for (const profile of CHROME_PROFILES) {
167
+ const cookiesPath = join(chromeBase, profile, 'Cookies');
168
+ if (!existsSync(cookiesPath)) continue;
169
+
170
+ // Copy DB to temp location (Chrome holds a WAL lock on the original)
171
+ const tmpDir = mkdtempSync(join(tmpdir(), 'slack-mcp-'));
172
+ const tmpDb = join(tmpDir, 'Cookies');
173
+ try {
174
+ copyFileSync(cookiesPath, tmpDb);
175
+
176
+ // Query for the encrypted d cookie
177
+ const queryResult = execFileSync('sqlite3', [
178
+ tmpDb,
179
+ "SELECT hex(encrypted_value) FROM cookies WHERE host_key LIKE '%.slack.com%' AND name = 'd' LIMIT 1;"
180
+ ], { encoding: 'utf-8', timeout: 5000 }).trim();
181
+
182
+ // Clean up temp files
183
+ try { unlinkSync(tmpDb); unlinkSync(tmpDir); } catch {}
184
+
185
+ if (!queryResult) continue;
186
+
187
+ // Convert hex back to buffer
188
+ const encrypted = Buffer.from(queryResult, 'hex');
189
+ if (encrypted.length < 4) continue;
190
+
191
+ // Get Chrome Safe Storage password from Keychain
192
+ const safeStoragePassword = execFileSync('security', [
193
+ 'find-generic-password', '-s', 'Chrome Safe Storage', '-w'
194
+ ], { encoding: 'utf-8', timeout: 5000 }).trim();
195
+
196
+ // Chrome macOS cookies: v10 prefix + AES-128-CBC
197
+ const prefix = encrypted.subarray(0, 3).toString('utf-8');
198
+ if (prefix !== 'v10') continue;
199
+
200
+ const ciphertext = encrypted.subarray(3);
201
+
202
+ // Derive key: PBKDF2-SHA1, 1003 iterations, salt 'saltysalt', 16-byte key
203
+ const key = pbkdf2Sync(safeStoragePassword, 'saltysalt', 1003, 16, 'sha1');
204
+ const iv = Buffer.alloc(16, ' '); // 16 space characters
205
+
206
+ const decipher = createDecipheriv('aes-128-cbc', key, iv);
207
+ let decrypted;
208
+ try {
209
+ decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
210
+ } catch {
211
+ continue; // Decryption failed for this profile, try next
212
+ }
213
+
214
+ // Find xoxd- in decrypted data (Chrome prepends internal metadata bytes)
215
+ const text = decrypted.toString('utf-8');
216
+ const xoxdIndex = text.indexOf('xoxd-');
217
+ if (xoxdIndex < 0) continue;
218
+
219
+ return text.substring(xoxdIndex);
220
+ } catch (e) {
221
+ // Clean up on error and try next profile
222
+ try { unlinkSync(tmpDb); } catch {}
223
+ try { unlinkSync(tmpDir); } catch {}
224
+ continue;
225
+ }
226
+ }
227
+
228
+ return null;
229
+ }
230
+
231
+ /**
232
+ * Extract Slack token from Chrome via AppleScript (reads localStorage).
233
+ * Uses strict URL matching to avoid hitting non-Slack tabs.
234
+ */
235
+ function extractTokenFromChrome() {
236
+ // Prefer /client URLs (active workspace), fall back to any app.slack.com
237
+ const urlChecks = [
238
+ 'URL of t starts with "https://app.slack.com/client"',
239
+ 'URL of t starts with "https://app.slack.com"',
240
+ ];
241
+
242
+ const tokenPathsJS = SLACK_TOKEN_PATHS.map((path, i) =>
243
+ `try { var t${i} = ${path}; if (t${i} && t${i}.startsWith('xoxc-')) return t${i}; } catch(e) {}`
244
+ ).join(' ');
245
+
246
+ for (const urlCheck of urlChecks) {
247
+ try {
248
+ const script = `tell application "Google Chrome"
249
+ repeat with w in windows
250
+ repeat with t in tabs of w
251
+ if ${urlCheck} then
252
+ return execute t javascript "(function() { ${tokenPathsJS} return ''; })()"
253
+ end if
254
+ end repeat
255
+ end repeat
256
+ return ""
257
+ end tell`;
258
+
259
+ const token = execFileSync('osascript', ['-e', script], {
260
+ encoding: 'utf-8', timeout: 8000
261
+ }).trim();
262
+
263
+ if (token && token.startsWith('xoxc-')) return token;
264
+ } catch {
265
+ continue;
266
+ }
267
+ }
268
+
269
+ return null;
270
+ }
271
+
272
+ /**
273
+ * Extract tokens from Chrome (macOS only).
274
+ *
275
+ * Token: AppleScript executes JS in Chrome to read localStorage (requires
276
+ * "Allow JavaScript from Apple Events" in Chrome > View > Developer).
277
+ * Cookie: Reads Chrome's encrypted SQLite cookie database directly. The `d`
278
+ * session cookie is HttpOnly and cannot be accessed via document.cookie.
279
+ * Decryption uses the Chrome Safe Storage key from macOS Keychain.
152
280
  */
153
281
  function extractFromChromeInternal() {
154
282
  lastExtractionError = null;
283
+
155
284
  if (!IS_MACOS) {
156
- // AppleScript/osascript is macOS-only
157
285
  lastExtractionError = {
158
286
  code: "unsupported_platform",
159
287
  message: "Chrome auto-extraction is only available on macOS.",
@@ -162,68 +290,51 @@ function extractFromChromeInternal() {
162
290
  return null;
163
291
  }
164
292
 
293
+ // Extract cookie from Chrome's encrypted cookie database
294
+ let cookie;
165
295
  try {
166
- // Extract cookie
167
- const cookieScript = `
168
- tell application "Google Chrome"
169
- repeat with w in windows
170
- repeat with t in tabs of w
171
- if URL of t contains "slack.com" then
172
- return execute t javascript "document.cookie.split('; ').find(c => c.startsWith('d='))?.split('=')[1] || ''"
173
- end if
174
- end repeat
175
- end repeat
176
- return ""
177
- end tell
178
- `;
179
- const cookie = execSync(`osascript -e '${cookieScript.replace(/'/g, "'\"'\"'")}'`, {
180
- encoding: 'utf-8', timeout: 5000
181
- }).trim();
182
-
183
- if (!cookie || !cookie.startsWith('xoxd-')) {
184
- lastExtractionError = {
185
- code: "cookie_not_found",
186
- message: "Could not extract Slack cookie from Chrome.",
187
- detail: "Ensure a logged-in Slack tab is open at app.slack.com."
188
- };
189
- return null;
190
- }
296
+ cookie = extractCookieFromChromeDB();
297
+ } catch (e) {
298
+ lastExtractionError = normalizeExtractionError(e);
299
+ return null;
300
+ }
301
+
302
+ if (!cookie) {
303
+ lastExtractionError = {
304
+ code: "cookie_not_found",
305
+ message: "Could not extract Slack session cookie from Chrome.",
306
+ detail: "Ensure you are logged into Slack at app.slack.com in Chrome."
307
+ };
308
+ return null;
309
+ }
191
310
 
192
- // Try multiple token extraction paths
193
- const tokenPathsJS = SLACK_TOKEN_PATHS.map((path, i) =>
194
- `try { var t${i} = ${path}; if (t${i}?.startsWith('xoxc-')) return t${i}; } catch(e) {}`
195
- ).join(' ');
196
-
197
- const tokenScript = `
198
- tell application "Google Chrome"
199
- repeat with w in windows
200
- repeat with t in tabs of w
201
- if URL of t contains "slack.com" then
202
- return execute t javascript "(function() { ${tokenPathsJS} return ''; })()"
203
- end if
204
- end repeat
205
- end repeat
206
- return ""
207
- end tell
208
- `;
209
- const token = execSync(`osascript -e '${tokenScript.replace(/'/g, "'\"'\"'")}'`, {
210
- encoding: 'utf-8', timeout: 5000
211
- }).trim();
212
-
213
- if (!token || !token.startsWith('xoxc-')) {
311
+ // Extract token via AppleScript (localStorage)
312
+ let token;
313
+ try {
314
+ token = extractTokenFromChrome();
315
+ } catch (e) {
316
+ lastExtractionError = normalizeExtractionError(e);
317
+ // If we got the cookie but not the token, give a specific error
318
+ if (cookie && !token) {
214
319
  lastExtractionError = {
215
- code: "token_not_found",
216
- message: "Could not extract Slack token from Chrome.",
217
- detail: "Refresh Slack in Chrome and retry extraction."
320
+ code: "apple_events_javascript_disabled",
321
+ message: "Cookie extracted, but token extraction requires a Chrome setting.",
322
+ detail: "In Chrome: View > Developer > Allow JavaScript from Apple Events. Then retry."
218
323
  };
219
- return null;
220
324
  }
325
+ return null;
326
+ }
221
327
 
222
- return { token, cookie };
223
- } catch (e) {
224
- lastExtractionError = normalizeExtractionError(e);
328
+ if (!token) {
329
+ lastExtractionError = {
330
+ code: "token_not_found",
331
+ message: "Could not extract Slack token from Chrome.",
332
+ detail: "Ensure a Slack workspace is open in Chrome (not just the landing page). If Chrome blocks AppleScript, enable View > Developer > Allow JavaScript from Apple Events."
333
+ };
225
334
  return null;
226
335
  }
336
+
337
+ return { token, cookie };
227
338
  }
228
339
 
229
340
  /**
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@jtalk22/slack-mcp",
3
3
  "mcpName": "io.github.jtalk22/slack-mcp-server",
4
- "version": "4.0.0",
5
- "description": "Slack MCP server. Session-based auth, 16 tools, stdio transport. Works with any MCP client.",
4
+ "version": "4.1.0",
5
+ "description": "Slack MCP without OAuth no app registration, no admin approval. Works with Claude Code, Cursor, Copilot (where the official server doesn't). 16 tools, one command.",
6
6
  "type": "module",
7
7
  "main": "src/server.js",
8
8
  "bin": {
@@ -30,6 +30,7 @@
30
30
  "build:demo-mobile:gif": "node scripts/build-mobile-demo.js --gif",
31
31
  "record-demo": "node scripts/record-demo.js",
32
32
  "social-preview:update": "node scripts/update-github-social-preview.js --headed",
33
+ "bookmarklet": "node scripts/generate-bookmarklet.js",
33
34
  "cf:browser": "node scripts/cloudflare-browser-tool.js",
34
35
  "verify:attribution-guardrail": "node scripts/verify-attribution-guardrail.js",
35
36
  "verify:public-pages": "node scripts/verify-generated-public-pages.js",
@@ -61,7 +62,14 @@
61
62
  "slack-integration",
62
63
  "ai-agents",
63
64
  "automation",
64
- "productivity"
65
+ "productivity",
66
+ "oauth-free",
67
+ "no-admin-approval",
68
+ "cursor",
69
+ "copilot",
70
+ "windsurf",
71
+ "codex-cli",
72
+ "stealth-mode"
65
73
  ],
66
74
  "author": {
67
75
  "name": "Revasser",
package/public/index.html CHANGED
@@ -255,7 +255,7 @@
255
255
  <div class="container">
256
256
  <h1>Slack Web API <span id="status" class="status"></span></h1>
257
257
  <div style="background:rgba(240,194,70,0.08);border:1px solid rgba(240,194,70,0.2);border-radius:8px;padding:8px 14px;margin-bottom:16px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;font-size:13px;color:#d4c48a">
258
- <span>Skip local setup? <strong style="color:#f0c246">Slack MCP Cloud</strong> gives you 15 managed tools with one URL. Team adds 3 AI workflows.</span>
258
+ <span>Hosted version coming soon — <strong style="color:#f0c246">permanent OAuth, semantic search, AI summaries</strong></span>
259
259
  <a href="https://mcp.revasserlabs.com" style="color:#f0c246;font-weight:600;text-decoration:none;white-space:nowrap" target="_blank">Learn more &rarr;</a>
260
260
  </div>
261
261
  <div class="grid">
package/public/share.html CHANGED
@@ -4,17 +4,17 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Slack MCP Server</title>
7
- <meta name="description" content="Session-based Slack MCP for Claude. Self-host locally or use the managed Cloud path with Gemini CLI support, pricing, security/procurement review, deployment review, and hosted credentials.">
7
+ <meta name="description" content="No OAuth. No admin. 16 Slack tools for Claude, Cursor, Copilot, Gemini, and any MCP client. One command: npx -y @jtalk22/slack-mcp --setup">
8
8
  <meta property="og:type" content="website">
9
- <meta property="og:title" content="Slack MCP Server">
10
- <meta property="og:description" content="Session-based Slack MCP for Claude. Self-host 16 tools for free or use Cloud for 15 managed tools, Gemini CLI support, security/procurement review, deployment review, and support.">
9
+ <meta property="og:title" content="Slack MCP Server — No OAuth, no admin, just your browser session">
10
+ <meta property="og:description" content="Slack's official MCP needs OAuth + admin. This one uses your browser session. 16 tools, works with Claude, Cursor, Copilot, Gemini.">
11
11
  <meta property="og:url" content="https://jtalk22.github.io/slack-mcp-server/public/share.html">
12
12
  <meta property="og:image" content="https://jtalk22.github.io/slack-mcp-server/docs/images/social-preview-v3.png">
13
13
  <meta property="og:image:width" content="1280">
14
14
  <meta property="og:image:height" content="640">
15
15
  <meta name="twitter:card" content="summary_large_image">
16
- <meta name="twitter:title" content="Slack MCP Server">
17
- <meta name="twitter:description" content="Session-based Slack MCP for Claude. Self-host 16 tools for free or use Cloud for 15 managed tools, Gemini CLI support, security/procurement review, deployment review, and support.">
16
+ <meta name="twitter:title" content="Slack MCP Server — No OAuth, no admin, just your browser session">
17
+ <meta name="twitter:description" content="16 tools for Claude, Cursor, Copilot, Gemini. npx -y @jtalk22/slack-mcp --setup">
18
18
  <meta name="twitter:image" content="https://jtalk22.github.io/slack-mcp-server/docs/images/social-preview-v3.png">
19
19
  <link rel="icon" href="https://jtalk22.github.io/slack-mcp-server/docs/assets/icon-512.png" type="image/png">
20
20
  <style>
@@ -107,7 +107,7 @@
107
107
  <body>
108
108
  <main class="wrap">
109
109
  <h1>Slack MCP Server</h1>
110
- <p class="sub">Give Claude full access to your Slack. Self-host 16 tools for free, or use Cloud for 15 managed tools, Gemini CLI support, hosted credentials, rollout support, and buyer-facing security review.</p>
110
+ <p class="sub">Give Claude full access to your Slack. Self-host 16 tools for free. Hosted version with semantic search, AI summaries, and permanent OAuth coming soon.</p>
111
111
 
112
112
  <a class="preview" href="https://github.com/jtalk22/slack-mcp-server" rel="noopener">
113
113
  <img src="https://jtalk22.github.io/slack-mcp-server/docs/images/social-preview-v3.png" alt="Slack MCP Server social preview card">
@@ -118,12 +118,12 @@
118
118
  <a href="https://github.com/jtalk22/slack-mcp-server/blob/main/docs/SETUP.md" rel="noopener">Verify (`--version/--doctor/--status`)</a>
119
119
  <a href="https://github.com/jtalk22/slack-mcp-server/releases/latest" rel="noopener">Latest Release</a>
120
120
  <a href="https://jtalk22.github.io/slack-mcp-server/" rel="noopener">Autoplay Demo Landing</a>
121
- <a href="https://jtalk22.github.io/slack-mcp-server/docs/videos/demo-claude-mobile-20s.mp4" rel="noopener">20s Mobile Clip</a>
121
+ <a href="https://jtalk22.github.io/slack-mcp-server/docs/videos/demo-slack-mcp-mobile-20s.mp4" rel="noopener">20s Mobile Clip</a>
122
122
  <a href="https://www.npmjs.com/package/@jtalk22/slack-mcp" rel="noopener">npm Package</a>
123
- <a href="https://mcp.revasserlabs.com" rel="noopener" style="background:rgba(240,194,70,0.18);border-color:rgba(240,194,70,0.45);color:#f0c246">Cloud</a>
123
+ <a href="https://mcp.revasserlabs.com" rel="noopener" style="background:rgba(240,194,70,0.18);border-color:rgba(240,194,70,0.45);color:#f0c246">Hosted</a>
124
124
  </div>
125
125
 
126
- <p class="note"><strong>Verify in 30 seconds:</strong> <code>--version</code>, <code>--doctor</code>, <code>--status</code>. Self-host gives 16 tools with session-based auth. Works with any MCP client — Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf. Managed hosting available at <a href="https://mcp.revasserlabs.com">mcp.revasserlabs.com</a>.</p>
126
+ <p class="note"><strong>Verify in 30 seconds:</strong> <code>--version</code>, <code>--doctor</code>, <code>--status</code>. Self-host gives 16 tools with session-based auth. Works with any MCP client — Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf. Hosted version coming soon at <a href="https://mcp.revasserlabs.com">mcp.revasserlabs.com</a>.</p>
127
127
  </main>
128
128
  </body>
129
129
  </html>
@@ -131,10 +131,18 @@ async function runMacOSSetup(rl) {
131
131
  }
132
132
  print();
133
133
  if (extractionError?.code === "apple_events_javascript_disabled") {
134
- print("Fix and retry:");
135
- print(" 1. In Chrome menu: View > Developer > Allow JavaScript from Apple Events");
136
- print(" 2. Keep Slack open in a Chrome tab (app.slack.com)");
137
- print(" 3. Re-run: npx -y @jtalk22/slack-mcp --setup");
134
+ print();
135
+ printBox([
136
+ "Chrome needs one setting enabled (one-time only):",
137
+ "",
138
+ "1. Open Chrome",
139
+ "2. Menu bar: View → Developer → Allow JavaScript",
140
+ " from Apple Events ✓",
141
+ "3. Run this command again",
142
+ ], 55);
143
+ print();
144
+ print("Once enabled, --setup extracts tokens automatically.");
145
+ print("No DevTools, no copy-paste, just one command.");
138
146
  } else {
139
147
  print("Make sure:");
140
148
  print(" 1. Chrome is running");
@@ -180,42 +188,83 @@ async function runManualSetup(rl) {
180
188
  print();
181
189
  if (IS_MACOS) {
182
190
  info("Switching to manual token entry...");
191
+ info("Note: On macOS, the session cookie can be extracted automatically.");
183
192
  } else {
184
193
  info(`Detected platform: ${platform()}`);
185
194
  warn("Auto-extraction not available on this platform.");
186
195
  }
196
+
197
+ const consoleHotkey = IS_MACOS ? "Cmd+Option+J" : "Ctrl+Shift+J";
198
+ // Token-only one-liner (cookie is HttpOnly and cannot be read via document.cookie)
199
+ const oneLiner = `copy(JSON.parse(localStorage.localConfig_v2).teams[Object.keys(JSON.parse(localStorage.localConfig_v2).teams)[0]].token)`;
200
+
187
201
  print();
188
- print("Follow these steps to extract tokens from Chrome:");
189
- print();
190
- print(`${colors.bold}Step 1:${colors.reset} Open Chrome and navigate to your Slack workspace`);
191
- print(" https://app.slack.com");
202
+ print(`${colors.bold}Quick extract (recommended):${colors.reset}`);
192
203
  print();
193
- print(`${colors.bold}Step 2:${colors.reset} Press F12 to open DevTools`);
204
+ print(` 1. Open Chrome → ${colors.cyan}app.slack.com${colors.reset} (must be logged in)`);
205
+ print(` 2. Press ${colors.cyan}${consoleHotkey}${colors.reset} to open the Console`);
206
+ print(` 3. Paste this one-liner and press Enter:`);
194
207
  print();
195
- print(`${colors.bold}Step 3:${colors.reset} Go to the ${colors.cyan}Console${colors.reset} tab and paste this:`);
208
+ printBox([oneLiner], oneLiner.length + 4);
196
209
  print();
197
- printBox([
198
- "JSON.parse(localStorage.localConfig_v2).teams[",
199
- " Object.keys(JSON.parse(localStorage.localConfig_v2)",
200
- " .teams)[0]].token",
201
- ], 55);
210
+ print(` 4. Your token is now on the clipboard. Paste below.`);
211
+ if (IS_MACOS) {
212
+ print(` ${colors.dim}(Cookie will be extracted automatically from Chrome)${colors.reset}`);
213
+ }
202
214
  print();
203
- print(` Copy the token (starts with ${colors.cyan}xoxc-${colors.reset})`);
215
+ print(`${colors.dim}(Or paste a JSON object with token+cookie, or a raw xoxc- token)${colors.reset}`);
204
216
  print();
205
217
 
206
- const token = await question(rl, `${colors.bold}Paste your token:${colors.reset} `);
218
+ const input = await question(rl, `${colors.bold}Paste token:${colors.reset} `);
219
+ const trimmed = input.trim();
207
220
 
208
- if (!token.startsWith('xoxc-')) {
209
- error("Invalid token. Token should start with 'xoxc-'");
210
- return false;
221
+ let token, cookie;
222
+
223
+ // Try JSON parse first (legacy one-liner output or manual JSON)
224
+ if (trimmed.startsWith('{')) {
225
+ try {
226
+ const parsed = JSON.parse(trimmed);
227
+ if (parsed.token) token = parsed.token;
228
+ if (parsed.cookie) cookie = parsed.cookie;
229
+ } catch (_) {
230
+ // Not valid JSON — fall through to raw token
231
+ }
211
232
  }
212
233
 
213
- print();
214
- print(`${colors.bold}Step 4:${colors.reset} Go to ${colors.cyan}Application${colors.reset} tab → ${colors.cyan}Cookies${colors.reset} → slack.com`);
215
- print(` Find the '${colors.cyan}d${colors.reset}' cookie and copy its value`);
216
- print();
234
+ // Treat as raw token
235
+ if (!token) {
236
+ token = trimmed;
237
+ if (!token.startsWith('xoxc-')) {
238
+ error("Invalid input. Expected a token starting with 'xoxc-'");
239
+ return false;
240
+ }
241
+ }
242
+
243
+ // On macOS, try to extract cookie from Chrome's cookie database automatically
244
+ if (!cookie && IS_MACOS) {
245
+ print();
246
+ print("Extracting session cookie from Chrome...");
247
+ const chromeTokens = extractFromChrome();
248
+ if (chromeTokens?.cookie) {
249
+ cookie = chromeTokens.cookie;
250
+ success("Cookie extracted from Chrome automatically");
251
+ } else {
252
+ warn("Could not extract cookie automatically.");
253
+ print(` Paste the cookie manually. In Chrome: ${colors.cyan}Application${colors.reset} tab → ${colors.cyan}Cookies${colors.reset} → find '${colors.cyan}d${colors.reset}'`);
254
+ print();
255
+ const cookieInput = await question(rl, `${colors.bold}Paste cookie (xoxd-...):${colors.reset} `);
256
+ cookie = cookieInput.trim();
257
+ }
258
+ }
217
259
 
218
- const cookie = await question(rl, `${colors.bold}Paste your cookie:${colors.reset} `);
260
+ // Non-macOS: always ask for cookie
261
+ if (!cookie) {
262
+ print();
263
+ print(`Paste the cookie. In Chrome: ${colors.cyan}Application${colors.reset} tab → ${colors.cyan}Cookies${colors.reset} → find '${colors.cyan}d${colors.reset}'`);
264
+ print();
265
+ const cookieInput = await question(rl, `${colors.bold}Paste cookie (xoxd-...):${colors.reset} `);
266
+ cookie = cookieInput.trim();
267
+ }
219
268
 
220
269
  if (!cookie.startsWith('xoxd-')) {
221
270
  error("Invalid cookie. Cookie should start with 'xoxd-'");
package/server.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.jtalk22/slack-mcp-server",
4
4
  "title": "Slack MCP Server",
5
- "description": "Slack MCP server. Session-based auth, 16 tools, stdio transport. Works with any MCP client.",
5
+ "description": "Slack MCP without OAuth no app registration, no admin approval. Works with Claude Code, Cursor, Copilot (where the official server doesn't). 16 tools, one command.",
6
6
  "websiteUrl": "https://mcp.revasserlabs.com",
7
7
  "icons": [
8
8
  {
@@ -17,7 +17,7 @@
17
17
  "url": "https://github.com/jtalk22/slack-mcp-server",
18
18
  "source": "github"
19
19
  },
20
- "version": "4.0.0",
20
+ "version": "4.1.0",
21
21
  "remotes": [
22
22
  {
23
23
  "type": "streamable-http",
@@ -28,7 +28,7 @@
28
28
  {
29
29
  "registryType": "npm",
30
30
  "identifier": "@jtalk22/slack-mcp",
31
- "version": "4.0.0",
31
+ "version": "4.1.0",
32
32
  "transport": {
33
33
  "type": "stdio"
34
34
  },