@jtalk22/slack-mcp 3.1.0 → 3.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.
Files changed (66) hide show
  1. package/README.md +82 -426
  2. package/docs/API.md +134 -0
  3. package/docs/SETUP.md +64 -29
  4. package/docs/TROUBLESHOOTING.md +28 -0
  5. package/lib/handlers.js +156 -0
  6. package/lib/slack-client.js +11 -3
  7. package/lib/token-store.js +6 -5
  8. package/lib/tools.js +132 -1
  9. package/package.json +15 -8
  10. package/public/index.html +10 -6
  11. package/public/share.html +6 -5
  12. package/scripts/setup-wizard.js +2 -2
  13. package/server.json +8 -2
  14. package/src/server-http.js +16 -1
  15. package/src/server.js +31 -7
  16. package/src/web-server.js +117 -4
  17. package/docs/CLOUDFLARE-BROWSER-TOOLKIT.md +0 -67
  18. package/docs/COMMUNICATION-STYLE.md +0 -66
  19. package/docs/COMPATIBILITY.md +0 -19
  20. package/docs/DEPLOYMENT-MODES.md +0 -55
  21. package/docs/HN-LAUNCH.md +0 -72
  22. package/docs/INDEX.md +0 -41
  23. package/docs/INSTALL-PROOF.md +0 -18
  24. package/docs/LAUNCH-COPY-v3.0.0.md +0 -101
  25. package/docs/LAUNCH-MATRIX.md +0 -22
  26. package/docs/LAUNCH-OPS.md +0 -71
  27. package/docs/RELEASE-HEALTH.md +0 -77
  28. package/docs/SUPPORT-BOUNDARIES.md +0 -49
  29. package/docs/USE_CASE_RECIPES.md +0 -69
  30. package/docs/WEB-API.md +0 -303
  31. package/docs/images/demo-channel-messages.png +0 -0
  32. package/docs/images/demo-channels.png +0 -0
  33. package/docs/images/demo-claude-mobile-360x800.png +0 -0
  34. package/docs/images/demo-claude-mobile-390x844.png +0 -0
  35. package/docs/images/demo-claude-mobile-poster.png +0 -0
  36. package/docs/images/demo-main-mobile-360x800.png +0 -0
  37. package/docs/images/demo-main-mobile-390x844.png +0 -0
  38. package/docs/images/demo-main.png +0 -0
  39. package/docs/images/demo-messages.png +0 -0
  40. package/docs/images/demo-poster.png +0 -0
  41. package/docs/images/demo-sidebar.png +0 -0
  42. package/docs/images/diagram-oauth-comparison.svg +0 -80
  43. package/docs/images/diagram-session-flow.svg +0 -105
  44. package/docs/images/social-preview-v3.png +0 -0
  45. package/docs/images/web-api-mobile-360x800.png +0 -0
  46. package/docs/images/web-api-mobile-390x844.png +0 -0
  47. package/public/demo-claude.html +0 -1974
  48. package/public/demo-video.html +0 -244
  49. package/public/demo.html +0 -1196
  50. package/scripts/build-mobile-demo.js +0 -168
  51. package/scripts/build-release-health-delta.js +0 -201
  52. package/scripts/build-social-preview.js +0 -189
  53. package/scripts/capture-screenshots.js +0 -152
  54. package/scripts/check-owner-attribution.sh +0 -131
  55. package/scripts/check-public-language.sh +0 -26
  56. package/scripts/check-version-parity.js +0 -218
  57. package/scripts/cloudflare-browser-tool.js +0 -237
  58. package/scripts/collect-release-health.js +0 -162
  59. package/scripts/impact-push-v3.js +0 -781
  60. package/scripts/record-demo.js +0 -163
  61. package/scripts/release-preflight.js +0 -247
  62. package/scripts/setup-git-hooks.sh +0 -15
  63. package/scripts/update-github-social-preview.js +0 -208
  64. package/scripts/verify-core.js +0 -159
  65. package/scripts/verify-install-flow.js +0 -193
  66. package/scripts/verify-web.js +0 -273
package/docs/API.md CHANGED
@@ -321,3 +321,137 @@ List all workspace users.
321
321
  ]
322
322
  }
323
323
  ```
324
+
325
+ ---
326
+
327
+ ### slack_add_reaction
328
+
329
+ Add an emoji reaction to a message.
330
+
331
+ **Parameters:**
332
+ | Name | Type | Default | Description |
333
+ |------|------|---------|-------------|
334
+ | channel_id | string | *required* | Channel or DM ID |
335
+ | timestamp | string | *required* | Message timestamp to react to |
336
+ | reaction | string | *required* | Emoji name without colons (e.g. `thumbsup`, `heart`, `eyes`) |
337
+
338
+ **Returns:**
339
+ ```json
340
+ {
341
+ "status": "added",
342
+ "channel": "D063M4403MW",
343
+ "timestamp": "1767368030.607599",
344
+ "reaction": "thumbsup"
345
+ }
346
+ ```
347
+
348
+ ---
349
+
350
+ ### slack_remove_reaction
351
+
352
+ Remove an emoji reaction from a message.
353
+
354
+ **Parameters:**
355
+ | Name | Type | Default | Description |
356
+ |------|------|---------|-------------|
357
+ | channel_id | string | *required* | Channel or DM ID |
358
+ | timestamp | string | *required* | Message timestamp |
359
+ | reaction | string | *required* | Emoji name without colons |
360
+
361
+ **Returns:**
362
+ ```json
363
+ {
364
+ "status": "removed",
365
+ "channel": "D063M4403MW",
366
+ "timestamp": "1767368030.607599",
367
+ "reaction": "thumbsup"
368
+ }
369
+ ```
370
+
371
+ ---
372
+
373
+ ### slack_conversations_mark
374
+
375
+ Mark a conversation as read up to a specific message timestamp.
376
+
377
+ **Parameters:**
378
+ | Name | Type | Default | Description |
379
+ |------|------|---------|-------------|
380
+ | channel_id | string | *required* | Channel or DM ID to mark as read |
381
+ | timestamp | string | *required* | Message timestamp to mark as read up to |
382
+
383
+ **Returns:**
384
+ ```json
385
+ {
386
+ "status": "marked",
387
+ "channel": "D063M4403MW",
388
+ "read_up_to": "1767368030.607599"
389
+ }
390
+ ```
391
+
392
+ ---
393
+
394
+ ### slack_conversations_unreads
395
+
396
+ Get channels and DMs with unread messages, sorted by unread count (highest first).
397
+
398
+ **Parameters:**
399
+ | Name | Type | Default | Description |
400
+ |------|------|---------|-------------|
401
+ | types | string | "im,mpim,public_channel,private_channel" | Comma-separated conversation types |
402
+ | limit | number | 50 | Maximum conversations to return |
403
+
404
+ **Returns:**
405
+ ```json
406
+ {
407
+ "total_unread_conversations": 3,
408
+ "conversations": [
409
+ {
410
+ "id": "C05GPEVH7J9",
411
+ "name": "engineering",
412
+ "type": "public_channel",
413
+ "unread_count": 12,
414
+ "latest_ts": "1767368030.607599"
415
+ },
416
+ {
417
+ "id": "D063M4403MW",
418
+ "name": "Gwen Santos",
419
+ "type": "dm",
420
+ "unread_count": 5,
421
+ "latest_ts": "1767368025.123456"
422
+ }
423
+ ]
424
+ }
425
+ ```
426
+
427
+ ---
428
+
429
+ ### slack_users_search
430
+
431
+ Search workspace users by name, display name, or email. Case-insensitive partial match.
432
+
433
+ **Parameters:**
434
+ | Name | Type | Default | Description |
435
+ |------|------|---------|-------------|
436
+ | query | string | *required* | Search term to match against name, display name, real name, or email |
437
+ | limit | number | 20 | Maximum results to return |
438
+
439
+ **Returns:**
440
+ ```json
441
+ {
442
+ "query": "gwen",
443
+ "count": 1,
444
+ "total_matches": 1,
445
+ "users": [
446
+ {
447
+ "id": "U05GPEVH7J9",
448
+ "name": "gwen",
449
+ "real_name": "Gwen Santos",
450
+ "display_name": "Gwen",
451
+ "email": "gwen@example.com",
452
+ "title": "Assistant",
453
+ "is_admin": false
454
+ }
455
+ ]
456
+ }
457
+ ```
package/docs/SETUP.md CHANGED
@@ -1,29 +1,68 @@
1
1
  # Setup Guide
2
2
 
3
- ## Prerequisites
3
+ ## Cloud (Fastest — No Local Setup)
4
+
5
+ If you want to skip local setup entirely, use **Slack MCP Cloud**:
6
+
7
+ 1. Go to [cloud.html](https://jtalk22.github.io/slack-mcp-server/cloud.html) 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. No Node.js, no Docker, no token management — one URL, 16 tools
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 (free), continue below.
38
+
39
+ ---
40
+
41
+ ## Self-Hosted Setup
42
+
43
+ ### Prerequisites
4
44
 
5
45
  - Node.js 20+
6
- - Google Chrome (for token extraction)
46
+ - Google Chrome (for token extraction on macOS)
7
47
  - macOS (for Keychain storage - other platforms use file storage only)
8
48
 
9
- ## Installation
10
-
11
- ### 1. Clone or Copy the Project
49
+ ### 1. Install via npm (Recommended)
12
50
 
13
51
  ```bash
14
- cd ~
15
- git clone https://github.com/jtalk22/slack-mcp-server.git
16
- # or if already exists:
17
- cd ~/slack-mcp-server
52
+ npm install -g @jtalk22/slack-mcp
53
+ # or use npx (no install):
54
+ npx -y @jtalk22/slack-mcp --version
18
55
  ```
19
56
 
20
- ### 2. Install Dependencies
57
+ ### 1b. Or Clone the Repository
21
58
 
22
59
  ```bash
60
+ git clone https://github.com/jtalk22/slack-mcp-server.git
61
+ cd slack-mcp-server
23
62
  npm install
24
63
  ```
25
64
 
26
- ### 2.5 Verify Install Path in a Clean Directory
65
+ ### 2. Verify Installation
27
66
 
28
67
  ```bash
29
68
  tmpdir="$(mktemp -d)"
@@ -76,45 +115,41 @@ npm run tokens:auto
76
115
  ```
77
116
  And paste both values when prompted.
78
117
 
79
- ### 4. Configure Claude Desktop (GUI App)
118
+ ### 4. Configure Claude Desktop
80
119
 
81
- Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
120
+ **macOS:** Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
121
+ **Windows:** Edit `%APPDATA%\Claude\claude_desktop_config.json`
82
122
 
83
123
  ```json
84
124
  {
85
125
  "mcpServers": {
86
126
  "slack": {
87
- "command": "/opt/homebrew/bin/node",
88
- "args": ["/Users/YOUR_USERNAME/slack-mcp-server/src/server.js"],
89
- "env": {
90
- "SLACK_TOKEN": "xoxc-your-token-here",
91
- "SLACK_COOKIE": "xoxd-your-cookie-here",
92
- "PATH": "/opt/homebrew/bin:/usr/bin:/bin"
93
- }
127
+ "command": "npx",
128
+ "args": ["-y", "@jtalk22/slack-mcp"]
94
129
  }
95
130
  }
96
131
  }
97
132
  ```
98
133
 
99
- **Important:**
100
- - Replace `YOUR_USERNAME` with your actual username
101
- - Copy tokens from `~/.slack-mcp-tokens.json` into the env section
102
- - Fully restart Claude Desktop (Cmd+Q, then reopen)
134
+ Fully restart Claude Desktop (Cmd+Q on macOS, then reopen).
103
135
 
104
- **Verify it's working:** Check `~/Library/Logs/Claude/mcp-server-slack.log`
136
+ **Verify it's working:** Check `~/Library/Logs/Claude/mcp-server-slack.log` (macOS)
105
137
 
106
138
  ### 5. Configure Claude Code (CLI)
107
139
 
108
- Edit `~/.claude.json` and add under `mcpServers`:
140
+ ```bash
141
+ claude mcp add slack npx -y @jtalk22/slack-mcp
142
+ ```
143
+
144
+ Or manually edit `~/.claude.json`:
109
145
 
110
146
  ```json
111
147
  {
112
148
  "mcpServers": {
113
149
  "slack": {
114
150
  "type": "stdio",
115
- "command": "node",
116
- "args": ["/Users/YOUR_USERNAME/slack-mcp-server/src/server.js"],
117
- "env": {}
151
+ "command": "npx",
152
+ "args": ["-y", "@jtalk22/slack-mcp"]
118
153
  }
119
154
  }
120
155
  }
@@ -29,6 +29,34 @@ If `--version` fails here, the issue is install/runtime path, not Slack credenti
29
29
 
30
30
  ---
31
31
 
32
+ ## Cloud Issues
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 james@revasser.nyc 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 standard 16 tools available on all 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
+ ```
57
+
58
+ ---
59
+
32
60
  ## DMs Not Showing Up
33
61
 
34
62
  **Symptom:** `slack_list_conversations` returns channels but no DMs.
package/lib/handlers.js CHANGED
@@ -617,3 +617,159 @@ export async function handleListUsers(args) {
617
617
  }]
618
618
  };
619
619
  }
620
+
621
+ /**
622
+ * Add reaction handler
623
+ */
624
+ export async function handleAddReaction(args) {
625
+ await slackAPI("reactions.add", {
626
+ channel: args.channel_id,
627
+ timestamp: args.timestamp,
628
+ name: args.reaction
629
+ });
630
+
631
+ return {
632
+ content: [{
633
+ type: "text",
634
+ text: JSON.stringify({
635
+ status: "added",
636
+ channel: args.channel_id,
637
+ timestamp: args.timestamp,
638
+ reaction: args.reaction
639
+ }, null, 2)
640
+ }]
641
+ };
642
+ }
643
+
644
+ /**
645
+ * Remove reaction handler
646
+ */
647
+ export async function handleRemoveReaction(args) {
648
+ await slackAPI("reactions.remove", {
649
+ channel: args.channel_id,
650
+ timestamp: args.timestamp,
651
+ name: args.reaction
652
+ });
653
+
654
+ return {
655
+ content: [{
656
+ type: "text",
657
+ text: JSON.stringify({
658
+ status: "removed",
659
+ channel: args.channel_id,
660
+ timestamp: args.timestamp,
661
+ reaction: args.reaction
662
+ }, null, 2)
663
+ }]
664
+ };
665
+ }
666
+
667
+ /**
668
+ * Mark conversation as read handler
669
+ */
670
+ export async function handleConversationsMark(args) {
671
+ await slackAPI("conversations.mark", {
672
+ channel: args.channel_id,
673
+ ts: args.timestamp
674
+ });
675
+
676
+ return asMcpJson({
677
+ status: "marked",
678
+ channel: args.channel_id,
679
+ read_up_to: args.timestamp
680
+ });
681
+ }
682
+
683
+ /**
684
+ * Unread conversations handler - returns channels/DMs with unread messages
685
+ */
686
+ export async function handleConversationsUnreads(args) {
687
+ const types = args.types || "im,mpim,public_channel,private_channel";
688
+ const limit = args.limit || 50;
689
+
690
+ const result = await slackAPI("conversations.list", {
691
+ types,
692
+ limit: 200,
693
+ exclude_archived: true
694
+ });
695
+
696
+ // Filter to conversations with unreads and resolve names
697
+ const unreads = [];
698
+ for (const c of (result.channels || [])) {
699
+ const unreadCount = c.unread_count_display || c.unread_count || 0;
700
+ if (unreadCount === 0) continue;
701
+
702
+ let displayName = c.name;
703
+ if (c.is_im && c.user) {
704
+ displayName = await resolveUser(c.user);
705
+ }
706
+
707
+ unreads.push({
708
+ id: c.id,
709
+ name: displayName,
710
+ type: c.is_im ? "dm" : c.is_mpim ? "group_dm" : c.is_private ? "private_channel" : "public_channel",
711
+ unread_count: unreadCount,
712
+ latest_ts: c.latest?.ts || null
713
+ });
714
+ }
715
+
716
+ // Sort by unread count descending
717
+ unreads.sort((a, b) => b.unread_count - a.unread_count);
718
+
719
+ return asMcpJson({
720
+ total_unread_conversations: unreads.length,
721
+ conversations: unreads.slice(0, limit)
722
+ });
723
+ }
724
+
725
+ /**
726
+ * Search users handler - client-side filter on users.list
727
+ */
728
+ export async function handleUsersSearch(args) {
729
+ const query = (args.query || "").toLowerCase();
730
+ const limit = args.limit || 20;
731
+
732
+ // Fetch all users (paginated)
733
+ const allUsers = [];
734
+ let cursor;
735
+
736
+ do {
737
+ const result = await slackAPI("users.list", {
738
+ limit: 200,
739
+ cursor
740
+ });
741
+
742
+ for (const u of (result.members || [])) {
743
+ if (u.deleted || u.is_bot || u.id === "USLACKBOT") continue;
744
+
745
+ const searchFields = [
746
+ u.name,
747
+ u.real_name,
748
+ u.profile?.display_name,
749
+ u.profile?.email
750
+ ].filter(Boolean).map(s => s.toLowerCase());
751
+
752
+ if (searchFields.some(f => f.includes(query))) {
753
+ allUsers.push({
754
+ id: u.id,
755
+ name: u.name,
756
+ real_name: u.real_name,
757
+ display_name: u.profile?.display_name,
758
+ email: u.profile?.email,
759
+ title: u.profile?.title,
760
+ is_admin: u.is_admin
761
+ });
762
+ }
763
+ }
764
+
765
+ cursor = result.response_metadata?.next_cursor;
766
+ if (cursor) await sleep(100);
767
+ } while (cursor && allUsers.length < 500);
768
+
769
+ return asMcpJson({
770
+ query: args.query,
771
+ count: Math.min(allUsers.length, limit),
772
+ total_matches: allUsers.length,
773
+ users: allUsers.slice(0, limit)
774
+ });
775
+ }
@@ -193,7 +193,8 @@ export async function slackAPI(method, params = {}, options = {}) {
193
193
  // so stringify any arrays/objects before encoding.
194
194
  const safeParams = {};
195
195
  for (const [key, value] of Object.entries(params)) {
196
- safeParams[key] = (typeof value === "object" && value !== null)
196
+ if (value === undefined || value === null) continue;
197
+ safeParams[key] = (typeof value === "object")
197
198
  ? JSON.stringify(value)
198
199
  : String(value);
199
200
  }
@@ -228,7 +229,12 @@ export async function slackAPI(method, params = {}, options = {}) {
228
229
  throw networkError;
229
230
  }
230
231
 
231
- const data = await response.json();
232
+ let data;
233
+ try {
234
+ data = await response.json();
235
+ } catch (parseError) {
236
+ throw new Error(`Slack API ${method} returned non-JSON (HTTP ${response.status}): ${parseError.message}`);
237
+ }
232
238
 
233
239
  if (!data.ok) {
234
240
  // Handle rate limiting with exponential backoff
@@ -297,7 +303,9 @@ export function getUserCacheStats() {
297
303
  * Format a Slack timestamp to ISO string
298
304
  */
299
305
  export function formatTimestamp(ts) {
300
- return new Date(parseFloat(ts) * 1000).toISOString();
306
+ const parsed = parseFloat(ts);
307
+ if (!Number.isFinite(parsed)) return null;
308
+ return new Date(parsed * 1000).toISOString();
301
309
  }
302
310
 
303
311
  /**
@@ -11,7 +11,7 @@
11
11
  import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync } from "fs";
12
12
  import { homedir, platform } from "os";
13
13
  import { join } from "path";
14
- import { execSync } from "child_process";
14
+ import { execSync, execFileSync } from "child_process";
15
15
 
16
16
  const TOKEN_FILE = join(homedir(), ".slack-mcp-tokens.json");
17
17
  const KEYCHAIN_SERVICE = "slack-mcp-server";
@@ -28,8 +28,9 @@ let lastExtractionError = null;
28
28
  export function getFromKeychain(key) {
29
29
  if (!IS_MACOS) return null; // Keychain is macOS-only
30
30
  try {
31
- const result = execSync(
32
- `security find-generic-password -s "${KEYCHAIN_SERVICE}" -a "${key}" -w 2>/dev/null`,
31
+ const result = execFileSync(
32
+ "security",
33
+ ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", key, "-w"],
33
34
  { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
34
35
  );
35
36
  return result.trim();
@@ -43,11 +44,11 @@ export function saveToKeychain(key, value) {
43
44
  try {
44
45
  // Delete existing entry
45
46
  try {
46
- execSync(`security delete-generic-password -s "${KEYCHAIN_SERVICE}" -a "${key}" 2>/dev/null`, { stdio: 'pipe' });
47
+ execFileSync("security", ["delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", key], { stdio: 'pipe' });
47
48
  } catch (e) { /* ignore */ }
48
49
 
49
50
  // Add new entry
50
- execSync(`security add-generic-password -s "${KEYCHAIN_SERVICE}" -a "${key}" -w "${value}"`, { stdio: 'pipe' });
51
+ execFileSync("security", ["add-generic-password", "-s", KEYCHAIN_SERVICE, "-a", key, "-w", value], { stdio: 'pipe' });
51
52
  return true;
52
53
  } catch (e) {
53
54
  return false;
package/lib/tools.js CHANGED
@@ -221,7 +221,7 @@ export const TOOLS = [
221
221
  annotations: {
222
222
  title: "Send Message",
223
223
  readOnlyHint: false,
224
- destructiveHint: false,
224
+ destructiveHint: true,
225
225
  idempotentHint: false,
226
226
  openWorldHint: true
227
227
  }
@@ -268,5 +268,136 @@ export const TOOLS = [
268
268
  idempotentHint: true,
269
269
  openWorldHint: true
270
270
  }
271
+ },
272
+ {
273
+ name: "slack_add_reaction",
274
+ description: "Add an emoji reaction to a message",
275
+ inputSchema: {
276
+ type: "object",
277
+ properties: {
278
+ channel_id: {
279
+ type: "string",
280
+ description: "Channel or DM ID containing the message"
281
+ },
282
+ timestamp: {
283
+ type: "string",
284
+ description: "Message timestamp to react to"
285
+ },
286
+ reaction: {
287
+ type: "string",
288
+ description: "Emoji name without colons (e.g., 'thumbsup', 'eyes', 'white_check_mark')"
289
+ }
290
+ },
291
+ required: ["channel_id", "timestamp", "reaction"]
292
+ },
293
+ annotations: {
294
+ title: "Add Reaction",
295
+ readOnlyHint: false,
296
+ destructiveHint: true,
297
+ idempotentHint: true,
298
+ openWorldHint: true
299
+ }
300
+ },
301
+ {
302
+ name: "slack_remove_reaction",
303
+ description: "Remove an emoji reaction from a message",
304
+ inputSchema: {
305
+ type: "object",
306
+ properties: {
307
+ channel_id: {
308
+ type: "string",
309
+ description: "Channel or DM ID containing the message"
310
+ },
311
+ timestamp: {
312
+ type: "string",
313
+ description: "Message timestamp to remove reaction from"
314
+ },
315
+ reaction: {
316
+ type: "string",
317
+ description: "Emoji name without colons (e.g., 'thumbsup', 'eyes')"
318
+ }
319
+ },
320
+ required: ["channel_id", "timestamp", "reaction"]
321
+ },
322
+ annotations: {
323
+ title: "Remove Reaction",
324
+ readOnlyHint: false,
325
+ destructiveHint: true,
326
+ idempotentHint: true,
327
+ openWorldHint: true
328
+ }
329
+ },
330
+ {
331
+ name: "slack_conversations_mark",
332
+ description: "Mark a conversation as read up to a specific message timestamp",
333
+ inputSchema: {
334
+ type: "object",
335
+ properties: {
336
+ channel_id: {
337
+ type: "string",
338
+ description: "Channel or DM ID to mark as read"
339
+ },
340
+ timestamp: {
341
+ type: "string",
342
+ description: "Message timestamp to mark as read up to (all messages at or before this are marked read)"
343
+ }
344
+ },
345
+ required: ["channel_id", "timestamp"]
346
+ },
347
+ annotations: {
348
+ title: "Mark as Read",
349
+ readOnlyHint: false,
350
+ destructiveHint: true,
351
+ idempotentHint: true,
352
+ openWorldHint: true
353
+ }
354
+ },
355
+ {
356
+ name: "slack_conversations_unreads",
357
+ description: "Get channels and DMs with unread messages, sorted by unread count (highest first)",
358
+ inputSchema: {
359
+ type: "object",
360
+ properties: {
361
+ types: {
362
+ type: "string",
363
+ description: "Comma-separated types: im, mpim, public_channel, private_channel (default all)",
364
+ default: "im,mpim,public_channel,private_channel"
365
+ },
366
+ limit: {
367
+ type: "number",
368
+ description: "Maximum conversations to return (default 50)"
369
+ }
370
+ }
371
+ },
372
+ annotations: {
373
+ title: "Unread Conversations",
374
+ readOnlyHint: true,
375
+ idempotentHint: true,
376
+ openWorldHint: true
377
+ }
378
+ },
379
+ {
380
+ name: "slack_users_search",
381
+ description: "Search workspace users by name, display name, or email. Case-insensitive partial match.",
382
+ inputSchema: {
383
+ type: "object",
384
+ properties: {
385
+ query: {
386
+ type: "string",
387
+ description: "Search term to match against name, display name, real name, or email"
388
+ },
389
+ limit: {
390
+ type: "number",
391
+ description: "Maximum results to return (default 20)"
392
+ }
393
+ },
394
+ required: ["query"]
395
+ },
396
+ annotations: {
397
+ title: "Search Users",
398
+ readOnlyHint: true,
399
+ idempotentHint: true,
400
+ openWorldHint: true
401
+ }
271
402
  }
272
403
  ];