@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.
- package/README.md +63 -28
- 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 +35 -36
- package/public/index.html +10 -6
- package/public/share.html +128 -0
- package/scripts/setup-wizard.js +1 -1
- package/server.json +10 -4
- package/src/server-http.js +16 -1
- package/src/server.js +31 -7
- package/src/web-server.js +119 -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 -40
- package/docs/INSTALL-PROOF.md +0 -18
- package/docs/LAUNCH-COPY-v3.0.0.md +0 -73
- package/docs/LAUNCH-MATRIX.md +0 -22
- package/docs/LAUNCH-OPS.md +0 -71
- package/docs/RELEASE-HEALTH.md +0 -90
- 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-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/web-api-mobile-360x800.png +0 -0
- package/docs/images/web-api-mobile-390x844.png +0 -0
- package/public/demo-claude.html +0 -1958
- package/public/demo-video.html +0 -235
- package/public/demo.html +0 -1196
- package/scripts/build-release-health-delta.js +0 -201
- package/scripts/capture-screenshots.js +0 -146
- package/scripts/check-owner-attribution.sh +0 -80
- package/scripts/check-public-language.sh +0 -25
- package/scripts/check-version-parity.js +0 -176
- package/scripts/cloudflare-browser-tool.js +0 -237
- package/scripts/collect-release-health.js +0 -150
- package/scripts/record-demo.js +0 -162
- package/scripts/release-preflight.js +0 -243
- package/scripts/setup-git-hooks.sh +0 -15
- package/scripts/verify-core.js +0 -159
- package/scripts/verify-install-flow.js +0 -193
- package/scripts/verify-web.js +0 -269
package/README.md
CHANGED
|
@@ -1,43 +1,71 @@
|
|
|
1
1
|
# Slack MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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)
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
Session-based Slack MCP for Claude and MCP clients. Local-first `stdio`/`web` with secure-default hosted HTTP in v3.
|
|
7
9
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
+
[](https://jtalk22.github.io/slack-mcp-server/public/demo-video.html)
|
|
24
22
|
|
|
25
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
- **
|
|
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
|
-
- `--
|
|
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
|
-
- `--
|
|
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.
|
|
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
|
-
##
|
|
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;
|