@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.
- package/README.md +82 -426
- package/docs/API.md +134 -0
- package/docs/SETUP.md +64 -29
- package/docs/TROUBLESHOOTING.md +28 -0
- package/lib/handlers.js +156 -0
- package/lib/slack-client.js +11 -3
- package/lib/token-store.js +6 -5
- package/lib/tools.js +132 -1
- package/package.json +15 -8
- package/public/index.html +10 -6
- package/public/share.html +6 -5
- package/scripts/setup-wizard.js +2 -2
- package/server.json +8 -2
- package/src/server-http.js +16 -1
- package/src/server.js +31 -7
- package/src/web-server.js +117 -4
- package/docs/CLOUDFLARE-BROWSER-TOOLKIT.md +0 -67
- package/docs/COMMUNICATION-STYLE.md +0 -66
- package/docs/COMPATIBILITY.md +0 -19
- package/docs/DEPLOYMENT-MODES.md +0 -55
- package/docs/HN-LAUNCH.md +0 -72
- package/docs/INDEX.md +0 -41
- package/docs/INSTALL-PROOF.md +0 -18
- package/docs/LAUNCH-COPY-v3.0.0.md +0 -101
- package/docs/LAUNCH-MATRIX.md +0 -22
- package/docs/LAUNCH-OPS.md +0 -71
- package/docs/RELEASE-HEALTH.md +0 -77
- package/docs/SUPPORT-BOUNDARIES.md +0 -49
- package/docs/USE_CASE_RECIPES.md +0 -69
- package/docs/WEB-API.md +0 -303
- package/docs/images/demo-channel-messages.png +0 -0
- package/docs/images/demo-channels.png +0 -0
- package/docs/images/demo-claude-mobile-360x800.png +0 -0
- package/docs/images/demo-claude-mobile-390x844.png +0 -0
- package/docs/images/demo-claude-mobile-poster.png +0 -0
- package/docs/images/demo-main-mobile-360x800.png +0 -0
- package/docs/images/demo-main-mobile-390x844.png +0 -0
- package/docs/images/demo-main.png +0 -0
- package/docs/images/demo-messages.png +0 -0
- package/docs/images/demo-poster.png +0 -0
- package/docs/images/demo-sidebar.png +0 -0
- package/docs/images/diagram-oauth-comparison.svg +0 -80
- package/docs/images/diagram-session-flow.svg +0 -105
- package/docs/images/social-preview-v3.png +0 -0
- package/docs/images/web-api-mobile-360x800.png +0 -0
- package/docs/images/web-api-mobile-390x844.png +0 -0
- package/public/demo-claude.html +0 -1974
- package/public/demo-video.html +0 -244
- package/public/demo.html +0 -1196
- package/scripts/build-mobile-demo.js +0 -168
- package/scripts/build-release-health-delta.js +0 -201
- package/scripts/build-social-preview.js +0 -189
- package/scripts/capture-screenshots.js +0 -152
- package/scripts/check-owner-attribution.sh +0 -131
- package/scripts/check-public-language.sh +0 -26
- package/scripts/check-version-parity.js +0 -218
- package/scripts/cloudflare-browser-tool.js +0 -237
- package/scripts/collect-release-health.js +0 -162
- package/scripts/impact-push-v3.js +0 -781
- package/scripts/record-demo.js +0 -163
- package/scripts/release-preflight.js +0 -247
- package/scripts/setup-git-hooks.sh +0 -15
- package/scripts/update-github-social-preview.js +0 -208
- package/scripts/verify-core.js +0 -159
- package/scripts/verify-install-flow.js +0 -193
- 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
|
-
##
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
### 1. Clone or Copy the Project
|
|
49
|
+
### 1. Install via npm (Recommended)
|
|
12
50
|
|
|
13
51
|
```bash
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
###
|
|
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.
|
|
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
|
|
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": "
|
|
88
|
-
"args": ["/
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
116
|
-
"args": ["/
|
|
117
|
-
"env": {}
|
|
151
|
+
"command": "npx",
|
|
152
|
+
"args": ["-y", "@jtalk22/slack-mcp"]
|
|
118
153
|
}
|
|
119
154
|
}
|
|
120
155
|
}
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -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
|
+
}
|
package/lib/slack-client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
/**
|
package/lib/token-store.js
CHANGED
|
@@ -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 =
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
];
|