@jtalk22/slack-mcp 3.0.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 (59) hide show
  1. package/README.md +63 -28
  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 +35 -36
  9. package/public/index.html +10 -6
  10. package/public/share.html +128 -0
  11. package/scripts/setup-wizard.js +1 -1
  12. package/server.json +10 -4
  13. package/src/server-http.js +16 -1
  14. package/src/server.js +31 -7
  15. package/src/web-server.js +119 -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 -40
  22. package/docs/INSTALL-PROOF.md +0 -18
  23. package/docs/LAUNCH-COPY-v3.0.0.md +0 -73
  24. package/docs/LAUNCH-MATRIX.md +0 -22
  25. package/docs/LAUNCH-OPS.md +0 -71
  26. package/docs/RELEASE-HEALTH.md +0 -90
  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-main-mobile-360x800.png +0 -0
  35. package/docs/images/demo-main-mobile-390x844.png +0 -0
  36. package/docs/images/demo-main.png +0 -0
  37. package/docs/images/demo-messages.png +0 -0
  38. package/docs/images/demo-poster.png +0 -0
  39. package/docs/images/demo-sidebar.png +0 -0
  40. package/docs/images/diagram-oauth-comparison.svg +0 -80
  41. package/docs/images/diagram-session-flow.svg +0 -105
  42. package/docs/images/web-api-mobile-360x800.png +0 -0
  43. package/docs/images/web-api-mobile-390x844.png +0 -0
  44. package/public/demo-claude.html +0 -1958
  45. package/public/demo-video.html +0 -235
  46. package/public/demo.html +0 -1196
  47. package/scripts/build-release-health-delta.js +0 -201
  48. package/scripts/capture-screenshots.js +0 -146
  49. package/scripts/check-owner-attribution.sh +0 -80
  50. package/scripts/check-public-language.sh +0 -25
  51. package/scripts/check-version-parity.js +0 -176
  52. package/scripts/cloudflare-browser-tool.js +0 -237
  53. package/scripts/collect-release-health.js +0 -150
  54. package/scripts/record-demo.js +0 -162
  55. package/scripts/release-preflight.js +0 -243
  56. package/scripts/setup-git-hooks.sh +0 -15
  57. package/scripts/verify-core.js +0 -159
  58. package/scripts/verify-install-flow.js +0 -193
  59. package/scripts/verify-web.js +0 -269
package/README.md CHANGED
@@ -1,43 +1,71 @@
1
1
  # Slack MCP Server
2
2
 
3
- Use your existing Slack session with Claude and other MCP clients.
4
- Local-first by default (`stdio`/`web`), hosted HTTP when you need a remote endpoint.
3
+ [![npm version](https://img.shields.io/npm/v/@jtalk22/slack-mcp)](https://www.npmjs.com/package/@jtalk22/slack-mcp)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@jtalk22/slack-mcp)](https://www.npmjs.com/package/@jtalk22/slack-mcp)
5
+ [![MCP Registry](https://img.shields.io/badge/MCP_Registry-v3.2.0-blue)](https://registry.modelcontextprotocol.io)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
5
7
 
6
- [Live demo](https://jtalk22.github.io/slack-mcp-server/public/demo-video.html) · [npm package](https://www.npmjs.com/package/@jtalk22/slack-mcp) · [Compatibility matrix](https://github.com/jtalk22/slack-mcp-server/blob/main/docs/COMPATIBILITY.md)
8
+ Session-based Slack MCP for Claude and MCP clients. Local-first `stdio`/`web` with secure-default hosted HTTP in v3.
7
9
 
8
- ![Slack MCP tools in action](https://jtalk22.github.io/slack-mcp-server/docs/images/demo-readme.gif)
9
-
10
- ## 30-Second Verify
10
+ ## Install + Verify
11
11
 
12
12
  ```bash
13
+ npx -y @jtalk22/slack-mcp --setup
13
14
  npx -y @jtalk22/slack-mcp@latest --version
14
15
  npx -y @jtalk22/slack-mcp@latest --doctor
15
16
  npx -y @jtalk22/slack-mcp@latest --status
16
17
  ```
17
18
 
18
- Expected:
19
- - `--version` prints `slack-mcp-server v3.0.0`
20
- - `--doctor` exits with deterministic `0|1|2|3`
21
- - `--status` is read-only and non-mutating
19
+ [Setup guide](https://github.com/jtalk22/slack-mcp-server/blob/main/docs/SETUP.md) · [30-second verify reference](https://github.com/jtalk22/slack-mcp-server/blob/main/README.md#install--verify) · [Autoplay demo landing](https://jtalk22.github.io/slack-mcp-server/) · [Latest release](https://github.com/jtalk22/slack-mcp-server/releases/latest) · [npm package](https://www.npmjs.com/package/@jtalk22/slack-mcp)
22
20
 
23
- ## Install
21
+ [![Live demo poster](https://jtalk22.github.io/slack-mcp-server/docs/images/demo-poster.png)](https://jtalk22.github.io/slack-mcp-server/public/demo-video.html)
24
22
 
25
- ```bash
26
- npm install -g @jtalk22/slack-mcp
27
- npx -y @jtalk22/slack-mcp --setup
28
- ```
23
+ Motion proof: [20-second mobile clip](https://jtalk22.github.io/slack-mcp-server/docs/videos/demo-claude-mobile-20s.mp4) · [Live demo walkthrough](https://jtalk22.github.io/slack-mcp-server/public/demo-video.html) · [Share card](https://jtalk22.github.io/slack-mcp-server/public/share.html)
29
24
 
30
- If this project saves you setup time, star the repo: https://github.com/jtalk22/slack-mcp-server
25
+ Hosted migration note: `v3.1.0` keeps local `stdio`/`web` flows unchanged; hosted `/mcp` requires `SLACK_MCP_HTTP_AUTH_TOKEN` and `SLACK_MCP_HTTP_ALLOWED_ORIGINS`.
31
26
 
32
27
  Maintainer/operator: `jtalk22` (`james@revasser.nyc`)
33
- Release notes: [v3.0.0 notes](https://github.com/jtalk22/slack-mcp-server/blob/main/.github/v3.0.0-release-notes.md)
28
+ Release: [`v3.2.0`](https://github.com/jtalk22/slack-mcp-server/releases/tag/v3.2.0) · Notes: [v3.2.0 notes](https://github.com/jtalk22/slack-mcp-server/blob/main/.github/v3.2.0-release-notes.md) · Support: [deployment intake](https://github.com/jtalk22/slack-mcp-server/issues/new?template=deployment-intake.md)
29
+
30
+ If this saved you setup time, consider starring the repo. Maintenance support: [GitHub Sponsors](https://github.com/sponsors/jtalk22) · [Ko-fi](https://ko-fi.com/jtalk22) · [Buy Me a Coffee](https://buymeacoffee.com/jtalk22)
31
+
32
+ ## v3.2.0 at a Glance
33
+
34
+ - **16 tools** — added reactions, mark-as-read, unread inbox, and user search
35
+ - All three transports (stdio, web, hosted HTTP) have full tool parity
36
+ - No MCP tool renames or removals — fully backwards compatible
37
+
38
+ ## Slack MCP Cloud
39
+
40
+ **No token management. No Docker. No Chrome extensions. One URL and you're connected.**
41
+
42
+ Skip all local setup — paste one URL into Claude and get 16 Slack tools running in under 60 seconds. Encrypted token storage on Cloudflare's global edge (300+ PoPs). The only cloud-hosted session-based Slack MCP on the market.
34
43
 
35
- ## v3.0.0 at a Glance
44
+ | Plan | Price | Includes |
45
+ |------|-------|----------|
46
+ | Solo | $19/mo | 16 standard tools, AES-256-GCM encrypted storage, 5K requests/mo |
47
+ | Team | $49/mo | 16 standard + 3 AI compound tools, 3 workspaces, 25K requests/mo |
36
48
 
37
- - Hosted HTTP `/mcp` now requires bearer auth by default (`SLACK_MCP_HTTP_AUTH_TOKEN`).
38
- - Hosted HTTP CORS now uses explicit allowlisting (`SLACK_MCP_HTTP_ALLOWED_ORIGINS`).
39
- - Local-first paths (`stdio`, `web`) stay compatible.
40
- - MCP tool names stay stable (no renames/removals).
49
+ [Get Your API Key](https://jtalk22.github.io/slack-mcp-server/cloud.html) live in 60 seconds. [Privacy Policy](https://jtalk22.github.io/slack-mcp-server/privacy.html).
50
+
51
+ ### Cloud Usage Examples
52
+
53
+ Once configured, ask Claude naturally:
54
+
55
+ 1. **Catch up on a channel** — *"Summarize what happened in #engineering this week"*
56
+ Uses `slack_list_conversations` → `slack_conversations_history` → Claude synthesis.
57
+
58
+ 2. **Find a decision** — *"What did the team decide about the API migration?"*
59
+ Uses `slack_search_messages` with query `"API migration"`, then `slack_get_thread` to pull full context.
60
+
61
+ 3. **Export a conversation** — *"Export my full DM history with Sarah including threads"*
62
+ Uses `slack_list_conversations` to resolve Sarah's DM ID → `slack_get_full_conversation` with `include_threads: true`.
63
+
64
+ 4. **Send a standup update** — *"Post my standup in #daily-standup: shipped auth refactor, reviewing PR #42 today"*
65
+ Uses `slack_send_message` to the target channel.
66
+
67
+ 5. **AI-powered action items** *(Team plan)* — *"What action items came out of #product-sync today?"*
68
+ Uses `slack_extract_action_items` to identify owners, deadlines, and commitments.
41
69
 
42
70
  ## 60-Second Hosted Migration
43
71
 
@@ -96,7 +124,9 @@ Instead of authenticating as a bot, this server leverages your existing Chrome s
96
124
  - **Full Export** - Conversations with threads and resolved usernames
97
125
  - **Search** - Query across your entire workspace
98
126
  - **Send Messages** - DMs or channels, with thread support
99
- - **User Directory** - List and search 500+ users with pagination
127
+ - **Reactions** - Add or remove emoji reactions on any message
128
+ - **Unreads** - Priority-sorted unread inbox across all conversations
129
+ - **User Directory** - List, search, and look up 500+ users with pagination
100
130
 
101
131
  ### Stability
102
132
  - **Auto Token Refresh** - Extracts fresh tokens from Chrome automatically *(macOS only)*
@@ -119,6 +149,11 @@ Instead of authenticating as a bot, this server leverages your existing Chrome s
119
149
  | `slack_get_thread` | Get thread replies |
120
150
  | `slack_users_info` | Get user details |
121
151
  | `slack_list_users` | List workspace users (paginated, 500+ supported) |
152
+ | `slack_add_reaction` | Add an emoji reaction to a message |
153
+ | `slack_remove_reaction` | Remove an emoji reaction from a message |
154
+ | `slack_conversations_mark` | Mark a conversation as read up to a timestamp |
155
+ | `slack_conversations_unreads` | Get channels/DMs with unread messages, priority-sorted |
156
+ | `slack_users_search` | Search workspace users by name, display name, or email |
122
157
 
123
158
  ---
124
159
 
@@ -129,19 +164,19 @@ Instead of authenticating as a bot, this server leverages your existing Chrome s
129
164
  ### 30-Second Compatibility Check
130
165
 
131
166
  ```bash
132
- npx -y @jtalk22/slack-mcp --version
133
- npx -y @jtalk22/slack-mcp --doctor
134
167
  npx -y @jtalk22/slack-mcp --setup
168
+ npx -y @jtalk22/slack-mcp --doctor
169
+ npx -y @jtalk22/slack-mcp --status
135
170
  ```
136
171
 
137
172
  Expected:
138
- - `--version` prints `slack-mcp-server v3.0.x`
173
+ - `--setup` launches the interactive wizard
139
174
  - `--doctor` returns one clear next action with exit code:
140
175
  - `0` ready
141
176
  - `1` missing credentials
142
177
  - `2` invalid/expired credentials
143
178
  - `3` connectivity/runtime issue
144
- - `--setup` launches the interactive wizard
179
+ - `--status` is read-only and non-mutating
145
180
 
146
181
  Command reference: [HN launch kit](https://github.com/jtalk22/slack-mcp-server/blob/main/docs/HN-LAUNCH.md)
147
182
 
@@ -366,7 +401,7 @@ npm run web
366
401
 
367
402
  ```
368
403
  ════════════════════════════════════════════════════════════
369
- Slack Web API Server v3.0.0
404
+ Slack Web API Server v3.2.0
370
405
  ════════════════════════════════════════════════════════════
371
406
 
372
407
  Dashboard: http://localhost:3000/?key=smcp_xxxxxxxxxxxx
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;