@jtalk22/slack-mcp 3.1.0 → 3.2.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.
Files changed (65) hide show
  1. package/README.md +45 -13
  2. package/docs/SETUP.md +64 -29
  3. package/docs/TROUBLESHOOTING.md +28 -0
  4. package/lib/handlers.js +156 -0
  5. package/lib/slack-client.js +11 -3
  6. package/lib/token-store.js +6 -5
  7. package/lib/tools.js +131 -0
  8. package/package.json +15 -8
  9. package/public/index.html +10 -6
  10. package/public/share.html +6 -5
  11. package/scripts/setup-wizard.js +1 -1
  12. package/server.json +8 -2
  13. package/src/server-http.js +16 -1
  14. package/src/server.js +31 -7
  15. package/src/web-server.js +117 -4
  16. package/docs/CLOUDFLARE-BROWSER-TOOLKIT.md +0 -67
  17. package/docs/COMMUNICATION-STYLE.md +0 -66
  18. package/docs/COMPATIBILITY.md +0 -19
  19. package/docs/DEPLOYMENT-MODES.md +0 -55
  20. package/docs/HN-LAUNCH.md +0 -72
  21. package/docs/INDEX.md +0 -41
  22. package/docs/INSTALL-PROOF.md +0 -18
  23. package/docs/LAUNCH-COPY-v3.0.0.md +0 -101
  24. package/docs/LAUNCH-MATRIX.md +0 -22
  25. package/docs/LAUNCH-OPS.md +0 -71
  26. package/docs/RELEASE-HEALTH.md +0 -77
  27. package/docs/SUPPORT-BOUNDARIES.md +0 -49
  28. package/docs/USE_CASE_RECIPES.md +0 -69
  29. package/docs/WEB-API.md +0 -303
  30. package/docs/images/demo-channel-messages.png +0 -0
  31. package/docs/images/demo-channels.png +0 -0
  32. package/docs/images/demo-claude-mobile-360x800.png +0 -0
  33. package/docs/images/demo-claude-mobile-390x844.png +0 -0
  34. package/docs/images/demo-claude-mobile-poster.png +0 -0
  35. package/docs/images/demo-main-mobile-360x800.png +0 -0
  36. package/docs/images/demo-main-mobile-390x844.png +0 -0
  37. package/docs/images/demo-main.png +0 -0
  38. package/docs/images/demo-messages.png +0 -0
  39. package/docs/images/demo-poster.png +0 -0
  40. package/docs/images/demo-sidebar.png +0 -0
  41. package/docs/images/diagram-oauth-comparison.svg +0 -80
  42. package/docs/images/diagram-session-flow.svg +0 -105
  43. package/docs/images/social-preview-v3.png +0 -0
  44. package/docs/images/web-api-mobile-360x800.png +0 -0
  45. package/docs/images/web-api-mobile-390x844.png +0 -0
  46. package/public/demo-claude.html +0 -1974
  47. package/public/demo-video.html +0 -244
  48. package/public/demo.html +0 -1196
  49. package/scripts/build-mobile-demo.js +0 -168
  50. package/scripts/build-release-health-delta.js +0 -201
  51. package/scripts/build-social-preview.js +0 -189
  52. package/scripts/capture-screenshots.js +0 -152
  53. package/scripts/check-owner-attribution.sh +0 -131
  54. package/scripts/check-public-language.sh +0 -26
  55. package/scripts/check-version-parity.js +0 -218
  56. package/scripts/cloudflare-browser-tool.js +0 -237
  57. package/scripts/collect-release-health.js +0 -162
  58. package/scripts/impact-push-v3.js +0 -781
  59. package/scripts/record-demo.js +0 -163
  60. package/scripts/release-preflight.js +0 -247
  61. package/scripts/setup-git-hooks.sh +0 -15
  62. package/scripts/update-github-social-preview.js +0 -208
  63. package/scripts/verify-core.js +0 -159
  64. package/scripts/verify-install-flow.js +0 -193
  65. package/scripts/verify-web.js +0 -273
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jtalk22/slack-mcp",
3
3
  "mcpName": "io.github.jtalk22/slack-mcp-server",
4
- "version": "3.1.0",
4
+ "version": "3.2.0",
5
5
  "description": "Session-based Slack MCP for Claude and MCP clients: local-first workflows, secure-default HTTP.",
6
6
  "type": "module",
7
7
  "main": "src/server.js",
@@ -59,7 +59,11 @@
59
59
  "automation",
60
60
  "productivity"
61
61
  ],
62
- "author": "jtalk22",
62
+ "author": {
63
+ "name": "James Lambert",
64
+ "email": "james@revasser.nyc",
65
+ "url": "https://github.com/jtalk22"
66
+ },
63
67
  "license": "MIT",
64
68
  "repository": {
65
69
  "type": "git",
@@ -79,12 +83,15 @@
79
83
  "files": [
80
84
  "src/",
81
85
  "lib/",
82
- "public/",
83
- "scripts/",
84
- "docs/*.md",
85
- "docs/assets/",
86
- "docs/images/*.png",
87
- "docs/images/*.svg",
86
+ "public/index.html",
87
+ "public/share.html",
88
+ "scripts/setup-wizard.js",
89
+ "scripts/token-cli.js",
90
+ "docs/SETUP.md",
91
+ "docs/API.md",
92
+ "docs/TROUBLESHOOTING.md",
93
+ "docs/assets/icon.svg",
94
+ "docs/assets/icon-512.png",
88
95
  "README.md",
89
96
  "LICENSE",
90
97
  "server.json",
package/public/index.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Slack Web API</title>
6
+ <title>Slack MCP Server — Web Dashboard</title>
7
7
  <style>
8
8
  @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Space+Grotesk:wght@500;600;700&display=swap');
9
9
 
@@ -253,7 +253,11 @@
253
253
  </div>
254
254
 
255
255
  <div class="container">
256
- <h1>Slack Web API <span id="status" class="status"></span></h1>
256
+ <h1>Slack Web API <span style="font-size:0.55em;opacity:0.5;font-weight:400;vertical-align:middle">v3.2.0</span> <span id="status" class="status"></span></h1>
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 16 tools with one URL — no tokens to manage.</span>
259
+ <a href="https://jtalk22.github.io/slack-mcp-server/cloud.html" style="color:#f0c246;font-weight:600;text-decoration:none;white-space:nowrap" target="_blank">Learn more &rarr;</a>
260
+ </div>
257
261
  <div class="grid">
258
262
  <div class="sidebar">
259
263
  <h3>CONVERSATIONS</h3>
@@ -267,7 +271,7 @@
267
271
  </div>
268
272
  <div class="main-panel">
269
273
  <div class="search-section">
270
- <input type="text" id="searchQuery" placeholder="Search messages...">
274
+ <input type="text" id="searchQuery" placeholder="Search messages..." onkeypress="if(event.key==='Enter')searchMessages()">
271
275
  <button onclick="searchMessages()">Search</button>
272
276
  </div>
273
277
  <h2 id="channelName">Select a conversation</h2>
@@ -377,7 +381,7 @@
377
381
  try {
378
382
  const data = await api('/conversations?types=' + types);
379
383
  list.innerHTML = data.conversations.map(c =>
380
- '<li onclick="loadHistory(\'' + c.id + '\', \'' + c.name.replace(/'/g, "\\'") + '\', this)"><div>' + c.name + '</div><div class="type">' + c.type + '</div></li>'
384
+ '<li onclick="loadHistory(\'' + c.id + '\', \'' + escapeHtml(c.name).replace(/'/g, "&#39;") + '\', this)"><div>' + escapeHtml(c.name) + '</div><div class="type">' + escapeHtml(c.type) + '</div></li>'
381
385
  ).join('');
382
386
  } catch (e) { list.innerHTML = '<li class="error-msg">' + e.message + '</li>'; }
383
387
  }
@@ -397,7 +401,7 @@
397
401
  const container = document.getElementById('messages');
398
402
  if (!messages || messages.length === 0) { container.innerHTML = '<div class="loading">No messages</div>'; return; }
399
403
  container.innerHTML = messages.map(m =>
400
- '<div class="message"><div class="header"><span class="user">' + (m.user || m.user_id || 'Unknown') + '</span><span class="time">' + new Date(m.datetime).toLocaleString() + '</span></div><div class="text">' + escapeHtml(m.text || '') + '</div></div>'
404
+ '<div class="message"><div class="header"><span class="user">' + escapeHtml(m.user || m.user_id || 'Unknown') + '</span><span class="time">' + new Date(m.datetime).toLocaleString() + '</span></div><div class="text">' + escapeHtml(m.text || '') + '</div></div>'
401
405
  ).join('');
402
406
  container.scrollTop = container.scrollHeight;
403
407
  }
@@ -423,7 +427,7 @@
423
427
  const data = await api('/search?q=' + encodeURIComponent(query));
424
428
  if (data.matches && data.matches.length > 0) {
425
429
  container.innerHTML = data.matches.map(m =>
426
- '<div class="message"><div class="header"><span class="user">' + (m.user || 'Unknown') + ' in #' + m.channel + '</span><span class="time">' + new Date(m.datetime).toLocaleString() + '</span></div><div class="text">' + escapeHtml(m.text || '') + '</div></div>'
430
+ '<div class="message"><div class="header"><span class="user">' + escapeHtml(m.user || 'Unknown') + ' in #' + escapeHtml(m.channel || '') + '</span><span class="time">' + new Date(m.datetime).toLocaleString() + '</span></div><div class="text">' + escapeHtml(m.text || '') + '</div></div>'
427
431
  ).join('');
428
432
  } else { container.innerHTML = '<div class="loading">No results found</div>'; }
429
433
  } catch (e) { container.innerHTML = '<div class="error-msg">' + e.message + '</div>'; }
package/public/share.html CHANGED
@@ -3,17 +3,17 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Slack MCP Server v3.0.0</title>
6
+ <title>Slack MCP Server v3.2.0</title>
7
7
  <meta name="description" content="Session-based Slack MCP for Claude. Local-first stdio/web with secure-default hosted HTTP in v3.">
8
8
  <meta property="og:type" content="website">
9
- <meta property="og:title" content="Slack MCP Server v3.0.0">
9
+ <meta property="og:title" content="Slack MCP Server v3.2.0">
10
10
  <meta property="og:description" content="Session-based Slack MCP for Claude. Local-first stdio/web with secure-default hosted HTTP in v3.">
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 v3.0.0">
16
+ <meta name="twitter:title" content="Slack MCP Server v3.2.0">
17
17
  <meta name="twitter:description" content="Session-based Slack MCP for Claude. Local-first stdio/web with secure-default hosted HTTP in v3.">
18
18
  <meta name="twitter:image" content="https://jtalk22.github.io/slack-mcp-server/docs/images/social-preview-v3.png">
19
19
  <style>
@@ -105,8 +105,8 @@
105
105
  </head>
106
106
  <body>
107
107
  <main class="wrap">
108
- <h1>Slack MCP Server v3.0.0</h1>
109
- <p class="sub">Session-based Slack MCP for Claude and MCP clients. Local-first stdio/web, secure-default hosted HTTP in v3.</p>
108
+ <h1>Slack MCP Server v3.2.0</h1>
109
+ <p class="sub">Give Claude full access to your Slack. Read channels, search messages, send replies, get AI summaries. Self-host free or Cloud from $19/mo.</p>
110
110
 
111
111
  <a class="preview" href="https://github.com/jtalk22/slack-mcp-server" rel="noopener">
112
112
  <img src="https://jtalk22.github.io/slack-mcp-server/docs/images/social-preview-v3.png" alt="Slack MCP Server social preview card">
@@ -119,6 +119,7 @@
119
119
  <a href="https://jtalk22.github.io/slack-mcp-server/" rel="noopener">Autoplay Demo Landing</a>
120
120
  <a href="https://jtalk22.github.io/slack-mcp-server/docs/videos/demo-claude-mobile-20s.mp4" rel="noopener">20s Mobile Clip</a>
121
121
  <a href="https://www.npmjs.com/package/@jtalk22/slack-mcp" rel="noopener">npm Package</a>
122
+ <a href="https://jtalk22.github.io/slack-mcp-server/cloud.html" rel="noopener" style="background:rgba(240,194,70,0.18);border-color:rgba(240,194,70,0.45);color:#f0c246">Cloud ($19/mo)</a>
122
123
  </div>
123
124
 
124
125
  <p class="note"><strong>Verify in 30 seconds:</strong> <code>--version</code>, <code>--doctor</code>, <code>--status</code>. Maintainer/operator: <code>jtalk22</code> (<code>james@revasser.nyc</code>).</p>
@@ -23,7 +23,7 @@ import {
23
23
  } from "../lib/token-store.js";
24
24
 
25
25
  const IS_MACOS = platform() === 'darwin';
26
- const VERSION = "3.0.0";
26
+ const VERSION = "3.2.0";
27
27
  const MIN_NODE_MAJOR = 20;
28
28
  const AUTH_TEST_URL = process.env.SLACK_MCP_AUTH_TEST_URL || "https://slack.com/api/auth.test";
29
29
 
package/server.json CHANGED
@@ -17,12 +17,18 @@
17
17
  "url": "https://github.com/jtalk22/slack-mcp-server",
18
18
  "source": "github"
19
19
  },
20
- "version": "3.0.0",
20
+ "version": "3.2.0",
21
+ "remotes": [
22
+ {
23
+ "type": "streamable-http",
24
+ "url": "https://mcp.revasserlabs.com/oauth/mcp"
25
+ }
26
+ ],
21
27
  "packages": [
22
28
  {
23
29
  "registryType": "npm",
24
30
  "identifier": "@jtalk22/slack-mcp",
25
- "version": "3.0.0",
31
+ "version": "3.2.0",
26
32
  "transport": {
27
33
  "type": "stdio"
28
34
  },
@@ -27,10 +27,15 @@ import {
27
27
  handleSendMessage,
28
28
  handleGetThread,
29
29
  handleListUsers,
30
+ handleAddReaction,
31
+ handleRemoveReaction,
32
+ handleConversationsMark,
33
+ handleConversationsUnreads,
34
+ handleUsersSearch,
30
35
  } from "../lib/handlers.js";
31
36
 
32
37
  const SERVER_NAME = "slack-mcp-server";
33
- const SERVER_VERSION = "3.0.0";
38
+ const SERVER_VERSION = "3.2.0";
34
39
  const PORT = process.env.PORT || 3000;
35
40
  const HTTP_INSECURE = process.env.SLACK_MCP_HTTP_INSECURE === "1";
36
41
  const HTTP_AUTH_TOKEN = process.env.SLACK_MCP_HTTP_AUTH_TOKEN || process.env.SLACK_API_KEY || null;
@@ -115,6 +120,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
115
120
  return await handleGetThread(args);
116
121
  case "slack_list_users":
117
122
  return await handleListUsers(args);
123
+ case "slack_add_reaction":
124
+ return await handleAddReaction(args);
125
+ case "slack_remove_reaction":
126
+ return await handleRemoveReaction(args);
127
+ case "slack_conversations_mark":
128
+ return await handleConversationsMark(args);
129
+ case "slack_conversations_unreads":
130
+ return await handleConversationsUnreads(args);
131
+ case "slack_users_search":
132
+ return await handleUsersSearch(args);
118
133
  default:
119
134
  return {
120
135
  content: [{
package/src/server.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * - Network error retry with exponential backoff
12
12
  * - Background token health monitoring
13
13
  *
14
- * @version 3.0.0
14
+ * @version 3.2.0
15
15
  */
16
16
 
17
17
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -41,6 +41,11 @@ import {
41
41
  handleSendMessage,
42
42
  handleGetThread,
43
43
  handleListUsers,
44
+ handleAddReaction,
45
+ handleRemoveReaction,
46
+ handleConversationsMark,
47
+ handleConversationsUnreads,
48
+ handleUsersSearch,
44
49
  } from "../lib/handlers.js";
45
50
 
46
51
  // Background refresh interval (4 hours)
@@ -48,7 +53,7 @@ const BACKGROUND_REFRESH_INTERVAL = 4 * 60 * 60 * 1000;
48
53
 
49
54
  // Package info
50
55
  const SERVER_NAME = "slack-mcp-server";
51
- const SERVER_VERSION = "3.0.0";
56
+ const SERVER_VERSION = "3.2.0";
52
57
 
53
58
  // MCP Prompts - predefined prompt templates for common Slack operations
54
59
  const PROMPTS = [
@@ -255,6 +260,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
255
260
  case "slack_list_users":
256
261
  return await handleListUsers(args);
257
262
 
263
+ case "slack_add_reaction":
264
+ return await handleAddReaction(args);
265
+
266
+ case "slack_remove_reaction":
267
+ return await handleRemoveReaction(args);
268
+
269
+ case "slack_conversations_mark":
270
+ return await handleConversationsMark(args);
271
+
272
+ case "slack_conversations_unreads":
273
+ return await handleConversationsUnreads(args);
274
+
275
+ case "slack_users_search":
276
+ return await handleUsersSearch(args);
277
+
258
278
  default:
259
279
  return {
260
280
  content: [{
@@ -306,11 +326,15 @@ async function main() {
306
326
  // Use unref() so this timer doesn't prevent the process from exiting
307
327
  // when the MCP transport closes (prevents zombie processes)
308
328
  const backgroundTimer = setInterval(async () => {
309
- const health = await checkTokenHealth(console);
310
- if (health.refreshed) {
311
- console.error("Background: tokens refreshed successfully");
312
- } else if (health.critical) {
313
- console.error("Background: tokens critical - open Slack in Chrome");
329
+ try {
330
+ const health = await checkTokenHealth(console);
331
+ if (health.refreshed) {
332
+ console.error("Background: tokens refreshed successfully");
333
+ } else if (health.critical) {
334
+ console.error("Background: tokens critical - open Slack in Chrome");
335
+ }
336
+ } catch (err) {
337
+ console.error(`Background health check failed: ${err.message}`);
314
338
  }
315
339
  }, BACKGROUND_REFRESH_INTERVAL);
316
340
  backgroundTimer.unref();
package/src/web-server.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * Exposes Slack MCP tools as REST endpoints for browser access.
6
6
  * Run alongside or instead of the MCP server for web-based access.
7
7
  *
8
- * @version 3.0.0
8
+ * @version 3.2.0
9
9
  */
10
10
 
11
11
  import express from "express";
@@ -30,6 +30,11 @@ import {
30
30
  handleSendMessage,
31
31
  handleGetThread,
32
32
  handleListUsers,
33
+ handleAddReaction,
34
+ handleRemoveReaction,
35
+ handleConversationsMark,
36
+ handleConversationsUnreads,
37
+ handleUsersSearch,
33
38
  } from "../lib/handlers.js";
34
39
 
35
40
  const app = express();
@@ -86,7 +91,7 @@ app.use((req, res, next) => {
86
91
  res.header("Access-Control-Allow-Origin", origin);
87
92
  }
88
93
  res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
89
- res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
94
+ res.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
90
95
  if (req.method === "OPTIONS") {
91
96
  return res.sendStatus(200);
92
97
  }
@@ -136,7 +141,7 @@ function extractContent(result) {
136
141
  app.get("/", (req, res) => {
137
142
  res.json({
138
143
  name: "Slack Web API Server",
139
- version: "3.0.0",
144
+ version: "3.2.0",
140
145
  status: "ok",
141
146
  code: "ok",
142
147
  message: "Web API server is running.",
@@ -151,6 +156,11 @@ app.get("/", (req, res) => {
151
156
  "POST /messages",
152
157
  "GET /users",
153
158
  "GET /users/:id",
159
+ "POST /reactions",
160
+ "DELETE /reactions",
161
+ "POST /conversations/:id/mark",
162
+ "GET /conversations/unreads",
163
+ "GET /users/search",
154
164
  ],
155
165
  docs: "Add Authorization: Bearer <api-key> header to all requests"
156
166
  });
@@ -207,6 +217,19 @@ app.get("/conversations", authenticate, async (req, res) => {
207
217
  }
208
218
  });
209
219
 
220
+ // Get unread conversations
221
+ app.get("/conversations/unreads", authenticate, async (req, res) => {
222
+ try {
223
+ const result = await handleConversationsUnreads({
224
+ types: req.query.types || "im,mpim,public_channel,private_channel",
225
+ limit: parseInt(req.query.limit) || 50
226
+ });
227
+ res.json(extractContent(result));
228
+ } catch (e) {
229
+ sendStructuredError(res, 500, "unreads_failed", String(e?.message || e));
230
+ }
231
+ });
232
+
210
233
  // Get conversation history
211
234
  app.get("/conversations/:id/history", authenticate, async (req, res) => {
212
235
  try {
@@ -253,6 +276,28 @@ app.get("/conversations/:id/thread/:ts", authenticate, async (req, res) => {
253
276
  }
254
277
  });
255
278
 
279
+ // Mark conversation as read
280
+ app.post("/conversations/:id/mark", authenticate, async (req, res) => {
281
+ try {
282
+ if (!req.body.timestamp) {
283
+ return sendStructuredError(
284
+ res,
285
+ 400,
286
+ "invalid_request",
287
+ "timestamp is required",
288
+ "Include timestamp in request JSON."
289
+ );
290
+ }
291
+ const result = await handleConversationsMark({
292
+ channel_id: req.params.id,
293
+ timestamp: req.body.timestamp
294
+ });
295
+ res.json(extractContent(result));
296
+ } catch (e) {
297
+ sendStructuredError(res, 500, "mark_failed", String(e?.message || e));
298
+ }
299
+ });
300
+
256
301
  // Search messages
257
302
  app.get("/search", authenticate, async (req, res) => {
258
303
  try {
@@ -310,6 +355,28 @@ app.get("/users", authenticate, async (req, res) => {
310
355
  }
311
356
  });
312
357
 
358
+ // Search users (must be registered before /users/:id to avoid Express matching "search" as an ID)
359
+ app.get("/users/search", authenticate, async (req, res) => {
360
+ try {
361
+ if (!req.query.q) {
362
+ return sendStructuredError(
363
+ res,
364
+ 400,
365
+ "invalid_request",
366
+ "Query parameter 'q' is required",
367
+ "Set the q query string and retry."
368
+ );
369
+ }
370
+ const result = await handleUsersSearch({
371
+ query: req.query.q,
372
+ limit: parseInt(req.query.limit) || 20
373
+ });
374
+ res.json(extractContent(result));
375
+ } catch (e) {
376
+ sendStructuredError(res, 500, "user_search_failed", String(e?.message || e));
377
+ }
378
+ });
379
+
313
380
  // Get user info
314
381
  app.get("/users/:id", authenticate, async (req, res) => {
315
382
  try {
@@ -322,6 +389,52 @@ app.get("/users/:id", authenticate, async (req, res) => {
322
389
  }
323
390
  });
324
391
 
392
+ // Add reaction
393
+ app.post("/reactions", authenticate, async (req, res) => {
394
+ try {
395
+ if (!req.body.channel_id || !req.body.timestamp || !req.body.reaction) {
396
+ return sendStructuredError(
397
+ res,
398
+ 400,
399
+ "invalid_request",
400
+ "channel_id, timestamp, and reaction are required",
401
+ "Include channel_id, timestamp, and reaction in request JSON."
402
+ );
403
+ }
404
+ const result = await handleAddReaction({
405
+ channel_id: req.body.channel_id,
406
+ timestamp: req.body.timestamp,
407
+ reaction: req.body.reaction
408
+ });
409
+ res.json(extractContent(result));
410
+ } catch (e) {
411
+ sendStructuredError(res, 500, "add_reaction_failed", String(e?.message || e));
412
+ }
413
+ });
414
+
415
+ // Remove reaction
416
+ app.delete("/reactions", authenticate, async (req, res) => {
417
+ try {
418
+ if (!req.body.channel_id || !req.body.timestamp || !req.body.reaction) {
419
+ return sendStructuredError(
420
+ res,
421
+ 400,
422
+ "invalid_request",
423
+ "channel_id, timestamp, and reaction are required",
424
+ "Include channel_id, timestamp, and reaction in request JSON."
425
+ );
426
+ }
427
+ const result = await handleRemoveReaction({
428
+ channel_id: req.body.channel_id,
429
+ timestamp: req.body.timestamp,
430
+ reaction: req.body.reaction
431
+ });
432
+ res.json(extractContent(result));
433
+ } catch (e) {
434
+ sendStructuredError(res, 500, "remove_reaction_failed", String(e?.message || e));
435
+ }
436
+ });
437
+
325
438
  // Start server
326
439
  async function main() {
327
440
  // Check for credentials
@@ -337,7 +450,7 @@ async function main() {
337
450
  app.listen(PORT, '127.0.0.1', () => {
338
451
  // Print to stderr to keep logs clean (stdout reserved for JSON in some setups)
339
452
  console.error(`\n${"═".repeat(60)}`);
340
- console.error(` Slack Web API Server v3.0.0`);
453
+ console.error(` Slack Web API Server v3.2.0`);
341
454
  console.error(`${"═".repeat(60)}`);
342
455
  console.error(`\n Dashboard: http://localhost:${PORT}/?key=${API_KEY}`);
343
456
  console.error(`\n API Key: ${API_KEY}`);
@@ -1,67 +0,0 @@
1
- # Cloudflare Browser Toolkit
2
-
3
- Use Cloudflare Browser Rendering from this repo to run resilient page checks and captures when local browser automation is blocked.
4
-
5
- ## Prerequisites
6
-
7
- - `CLOUDFLARE_ACCOUNT_ID` set
8
- - `CLOUDFLARE_API_TOKEN` **or** `CF_TERRAFORM_TOKEN` set
9
- - Token needs Browser Rendering access
10
- - Account API tokens are supported (recommended for this workflow)
11
-
12
- ## Verify Access
13
-
14
- ```bash
15
- npm run cf:browser -- verify
16
- ```
17
-
18
- The verifier checks account-token auth (`/accounts/{account_id}/tokens/verify`) first and falls back to user-token verification automatically.
19
-
20
- ## Quick Commands
21
-
22
- Rendered HTML:
23
-
24
- ```bash
25
- npm run cf:browser -- content "https://example.com"
26
- ```
27
-
28
- Markdown extract:
29
-
30
- ```bash
31
- npm run cf:browser -- markdown "https://example.com"
32
- ```
33
-
34
- Link extraction:
35
-
36
- ```bash
37
- npm run cf:browser -- links "https://example.com"
38
- ```
39
-
40
- Selector scrape:
41
-
42
- ```bash
43
- npm run cf:browser -- scrape "https://example.com" --selectors "h1,.card a"
44
- ```
45
-
46
- Screenshot:
47
-
48
- ```bash
49
- npm run cf:browser -- screenshot "https://example.com" --out ./tmp/example.png --type png
50
- ```
51
-
52
- PDF:
53
-
54
- ```bash
55
- npm run cf:browser -- pdf "https://example.com" --out ./tmp/example.pdf
56
- ```
57
-
58
- Structured JSON:
59
-
60
- ```bash
61
- npm run cf:browser -- json "https://example.com" --schema '{"title":"string","links":["string"]}'
62
- ```
63
-
64
- ## Notes
65
-
66
- - This toolkit is for page rendering, extraction, and evidence capture.
67
- - It does not manage third-party account logins for posting workflows.
@@ -1,66 +0,0 @@
1
- # Communication Style Guide
2
-
3
- Use this guide for release notes, issue replies, and changelog entries.
4
-
5
- ## Rules
6
-
7
- 1. Keep text technical, concise, and factual.
8
- 2. Do not include model/tool credit lines.
9
- 3. Do not include co-author trailers from tooling.
10
- 4. State exact versions and commands when relevant.
11
- 5. Avoid speculative claims.
12
- 6. Release titles use `vX.Y.Z — <concrete operational outcome>`.
13
-
14
- ## Issue Reply Template
15
-
16
- ```md
17
- Thanks for reporting this.
18
-
19
- Status: fixed in `<version>`.
20
-
21
- Included:
22
- - `<fix 1>`
23
- - `<fix 2>`
24
-
25
- Verify:
26
- - `npx -y @jtalk22/slack-mcp --version`
27
- - `npx -y @jtalk22/slack-mcp --status`
28
-
29
- Install/update:
30
- - `npx -y @jtalk22/slack-mcp`
31
- - `npm i -g @jtalk22/slack-mcp@<version>`
32
-
33
- If it still reproduces, reply with OS, Node version, runtime mode (`stdio|web|http|worker`), and exact error output.
34
- ```
35
-
36
- ## Release Notes Template
37
-
38
- ````md
39
- ## <version> — <short title>
40
-
41
- ### Improved
42
- - <item>
43
- - <item>
44
-
45
- ### Compatibility
46
- - No API/tool schema changes.
47
-
48
- ### Verify
49
- ```bash
50
- npx -y @jtalk22/slack-mcp --version
51
- npx -y @jtalk22/slack-mcp --setup
52
- npx -y @jtalk22/slack-mcp --status
53
- ```
54
- ````
55
-
56
- ## Changelog Entry Template
57
-
58
- ```md
59
- ## [<version>] - YYYY-MM-DD
60
-
61
- ### Fixed
62
- - <item>
63
-
64
- ### Changed
65
- - <item>
66
- ```
@@ -1,19 +0,0 @@
1
- # Compatibility Matrix
2
-
3
- Use this matrix to choose a known working client/runtime path before rollout.
4
-
5
- | Client | Mode | Token path | Status | Quick verify command |
6
- |---|---|---|---|---|
7
- | Claude Desktop (macOS) | `stdio` | `~/.slack-mcp-tokens.json` via `--setup` auto-extract | Supported | `npx -y @jtalk22/slack-mcp --setup && npx -y @jtalk22/slack-mcp --status` |
8
- | Claude Desktop (Windows) | `stdio` | `env` (`SLACK_TOKEN`, `SLACK_COOKIE`) in config | Supported | `npx -y @jtalk22/slack-mcp --status` |
9
- | Claude Desktop (Linux) | `stdio` | `env` or token file via guided setup | Supported | `npx -y @jtalk22/slack-mcp --status` |
10
- | Claude Code CLI | `stdio` | `~/.slack-mcp-tokens.json` or `env` | Supported | `npx -y @jtalk22/slack-mcp --version && npx -y @jtalk22/slack-mcp --status` |
11
- | Local Browser UI | `web` | token file or `env` | Supported | `npx -y @jtalk22/slack-mcp web` |
12
- | Hosted Node Runtime | `http` | `env` in host runtime | Supported with operator controls | `node src/server-http.js` then `curl -s http://localhost:8080/health` |
13
- | Cloudflare Worker / Smithery transport | `worker` | runtime env/query handoff per deployment config | Supported with deployment validation | `wrangler deploy --config workers/wrangler.toml` and verify `/health` |
14
-
15
- ## Notes
16
-
17
- 1. Runtime baseline is Node 20+.
18
- 2. `--doctor` is the fastest first check when setup status is unknown.
19
- 3. For hosted/team deployment, use [DEPLOYMENT-MODES.md](DEPLOYMENT-MODES.md) before production rollout.