@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.
- package/README.md +45 -13
- 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 +131 -0
- package/package.json +15 -8
- package/public/index.html +10 -6
- package/public/share.html +6 -5
- package/scripts/setup-wizard.js +1 -1
- 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/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Slack MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@jtalk22/slack-mcp)
|
|
4
|
+
[](https://www.npmjs.com/package/@jtalk22/slack-mcp)
|
|
5
|
+
[](https://registry.modelcontextprotocol.io)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
3
8
|
Session-based Slack MCP for Claude and MCP clients. Local-first `stdio`/`web` with secure-default hosted HTTP in v3.
|
|
4
9
|
|
|
5
10
|
## Install + Verify
|
|
@@ -17,30 +22,50 @@ npx -y @jtalk22/slack-mcp@latest --status
|
|
|
17
22
|
|
|
18
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)
|
|
19
24
|
|
|
20
|
-
Hosted migration note: `v3.
|
|
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`.
|
|
21
26
|
|
|
22
27
|
Maintainer/operator: `jtalk22` (`james@revasser.nyc`)
|
|
23
|
-
Release: [`v3.
|
|
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)
|
|
24
29
|
|
|
25
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)
|
|
26
31
|
|
|
27
|
-
## v3.
|
|
32
|
+
## v3.2.0 at a Glance
|
|
28
33
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
- MCP tool names stay stable (no renames/removals).
|
|
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
|
|
33
37
|
|
|
34
38
|
## Slack MCP Cloud
|
|
35
39
|
|
|
36
|
-
|
|
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.
|
|
37
43
|
|
|
38
44
|
| Plan | Price | Includes |
|
|
39
45
|
|------|-------|----------|
|
|
40
|
-
| Solo | $19/mo |
|
|
41
|
-
| Team | $49/mo |
|
|
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 |
|
|
48
|
+
|
|
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.
|
|
42
66
|
|
|
43
|
-
|
|
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.
|
|
44
69
|
|
|
45
70
|
## 60-Second Hosted Migration
|
|
46
71
|
|
|
@@ -99,7 +124,9 @@ Instead of authenticating as a bot, this server leverages your existing Chrome s
|
|
|
99
124
|
- **Full Export** - Conversations with threads and resolved usernames
|
|
100
125
|
- **Search** - Query across your entire workspace
|
|
101
126
|
- **Send Messages** - DMs or channels, with thread support
|
|
102
|
-
- **
|
|
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
|
|
103
130
|
|
|
104
131
|
### Stability
|
|
105
132
|
- **Auto Token Refresh** - Extracts fresh tokens from Chrome automatically *(macOS only)*
|
|
@@ -122,6 +149,11 @@ Instead of authenticating as a bot, this server leverages your existing Chrome s
|
|
|
122
149
|
| `slack_get_thread` | Get thread replies |
|
|
123
150
|
| `slack_users_info` | Get user details |
|
|
124
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 |
|
|
125
157
|
|
|
126
158
|
---
|
|
127
159
|
|
|
@@ -369,7 +401,7 @@ npm run web
|
|
|
369
401
|
|
|
370
402
|
```
|
|
371
403
|
════════════════════════════════════════════════════════════
|
|
372
|
-
Slack Web API Server v3.
|
|
404
|
+
Slack Web API Server v3.2.0
|
|
373
405
|
════════════════════════════════════════════════════════════
|
|
374
406
|
|
|
375
407
|
Dashboard: http://localhost:3000/?key=smcp_xxxxxxxxxxxx
|
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
|
@@ -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: false,
|
|
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: false,
|
|
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: false,
|
|
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
|
];
|