@jtalk22/slack-mcp 1.2.0 → 1.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/LICENSE +1 -1
- package/README.md +37 -57
- package/docs/SETUP.md +1 -1
- package/docs/assets/icon-512.png +0 -0
- package/docs/assets/icon.svg +27 -0
- package/docs/images/demo-claude-v1.2.gif +0 -0
- package/docs/images/demo-poster.png +0 -0
- package/docs/images/demo-readme.gif +0 -0
- package/docs/images/diagram-oauth-comparison.svg +80 -0
- package/docs/images/diagram-session-flow.svg +105 -0
- package/lib/slack-client.js +35 -6
- package/lib/tools.js +69 -0
- package/package.json +3 -2
- package/public/demo-video.html +151 -0
- package/scripts/record-demo.js +4 -0
- package/scripts/setup-wizard.js +19 -16
- package/src/cli.js +57 -0
- package/src/server-http.js +1 -1
- package/src/server.js +170 -3
- package/src/web-server.js +3 -3
- package/docs/images/demo-session.gif +0 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,18 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://jtalk22.github.io/slack-mcp-server/public/demo.html"><img src="docs/assets/icon-512.png" alt="Slack MCP Server" width="80"></a>
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">Slack MCP Server</h1>
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<em>Give Claude the same Slack access you have.<br>
|
|
9
|
+
DMs, threads, history—no admin approval.</em>
|
|
10
|
+
</p>
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://jtalk22.github.io/slack-mcp-server/public/demo-video.html">
|
|
14
|
+
<img src="docs/images/demo-readme.gif" alt="Slack MCP tools in action" width="640">
|
|
15
|
+
</a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://www.npmjs.com/package/@jtalk22/slack-mcp"><img src="https://img.shields.io/badge/npm-Install-blue?style=for-the-badge&logo=npm&logoColor=white" alt="npm"></a>
|
|
20
|
+
<a href="https://github.com/jtalk22/slack-mcp-server/pkgs/container/slack-mcp-server"><img src="https://img.shields.io/badge/Docker-Pull-2496ED?style=for-the-badge&logo=docker&logoColor=white" alt="Docker"></a>
|
|
21
|
+
</p>
|
|
13
22
|
|
|
14
23
|
<p align="center">
|
|
15
|
-
<
|
|
24
|
+
<a href="https://jtalk22.github.io/slack-mcp-server/public/demo.html"><img src="https://img.shields.io/badge/LIVE%20DEMO-Try%20It%20Now-00C853?style=for-the-badge&logo=slack&logoColor=white" alt="Live Demo"></a>
|
|
25
|
+
<a href="https://jtalk22.github.io/slack-mcp-server/public/demo-claude.html"><img src="https://img.shields.io/badge/CLAUDE%20DEMO-See%20MCP%20Tools-da7756?style=for-the-badge" alt="Claude Demo"></a>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<br>
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<a href="https://www.npmjs.com/package/@jtalk22/slack-mcp"><img src="https://img.shields.io/npm/dm/@jtalk22/slack-mcp?label=downloads&color=CB3837" alt="npm downloads"></a>
|
|
32
|
+
<a href="https://github.com/jtalk22/slack-mcp-server/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/jtalk22/slack-mcp-server/ci.yml?label=build" alt="Build Status"></a>
|
|
33
|
+
<a href="https://smithery.ai/server/jtalk22/slack-mcp-server"><img src="https://img.shields.io/badge/Smithery-Registry-4A154B" alt="Smithery"></a>
|
|
34
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
35
|
+
<a href="https://github.com/sponsors/jtalk22"><img src="https://img.shields.io/badge/Sponsor-♡-ea4aaa?logo=github" alt="Sponsor"></a>
|
|
16
36
|
</p>
|
|
17
37
|
|
|
18
38
|
---
|
|
@@ -27,59 +47,17 @@ This server bridges the gap. It creates a secure, local bridge between Claude an
|
|
|
27
47
|
|
|
28
48
|

|
|
29
49
|
|
|
30
|
-
> **[Try the Interactive Demo](https://jtalk22.github.io/slack-mcp-server/public/demo.html)** - See the Web UI in action
|
|
31
|
-
>
|
|
32
|
-
> **[See Claude Desktop Demo](https://jtalk22.github.io/slack-mcp-server/public/demo-claude.html)** - Watch MCP tools in action
|
|
33
|
-
|
|
34
50
|
---
|
|
35
51
|
|
|
36
52
|
## Architecture: Local Session Mirroring
|
|
37
53
|
|
|
38
54
|
Instead of authenticating as a bot, this server leverages your existing Chrome session credentials (macOS) or manual token injection (Linux/Windows). It mirrors your user access exactly—if you can see it in Slack, Claude can see it too.
|
|
39
55
|
|
|
40
|
-
|
|
41
|
-
sequenceDiagram
|
|
42
|
-
participant Chrome as Chrome Browser
|
|
43
|
-
participant Script as AppleScript
|
|
44
|
-
participant Store as Token Store
|
|
45
|
-
participant MCP as MCP Server
|
|
46
|
-
participant Slack as Slack API
|
|
47
|
-
|
|
48
|
-
Note over Chrome: You're logged into Slack
|
|
49
|
-
Script->>Chrome: Execute JavaScript in Slack tab
|
|
50
|
-
Chrome-->>Script: xoxc- token + xoxd- cookie
|
|
51
|
-
Script->>Store: Save to ~/.slack-mcp-tokens.json
|
|
52
|
-
Store->>Store: Encrypt in macOS Keychain
|
|
53
|
-
|
|
54
|
-
Note over MCP: Claude asks for DMs
|
|
55
|
-
MCP->>Store: Load credentials
|
|
56
|
-
Store-->>MCP: Token + Cookie
|
|
57
|
-
MCP->>Slack: GET conversations.history
|
|
58
|
-
Slack-->>MCP: Full message history
|
|
59
|
-
MCP-->>MCP: Return to Claude
|
|
60
|
-
```
|
|
56
|
+

|
|
61
57
|
|
|
62
58
|
### Why Not OAuth?
|
|
63
59
|
|
|
64
|
-
|
|
65
|
-
flowchart LR
|
|
66
|
-
subgraph Traditional["Official Slack API (OAuth)"]
|
|
67
|
-
A[Create App] --> B[Request Scopes]
|
|
68
|
-
B --> C[Admin Approval]
|
|
69
|
-
C --> D[User Authorization]
|
|
70
|
-
D --> E[Limited Access]
|
|
71
|
-
E --> F["No DMs without<br/>per-conversation consent"]
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
subgraph ThisServer["Session Mirroring"]
|
|
75
|
-
G[Open Slack in Chrome] --> H[Mirror Session]
|
|
76
|
-
H --> I[Full Access]
|
|
77
|
-
I --> J["Your DMs, Channels,<br/>Search, History"]
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
style Traditional fill:#ffcccc
|
|
81
|
-
style ThisServer fill:#ccffcc
|
|
82
|
-
```
|
|
60
|
+

|
|
83
61
|
|
|
84
62
|
**Trade-off:** Session tokens expire every 1-2 weeks. Auto-refresh (macOS) or manual update keeps things running.
|
|
85
63
|
|
|
@@ -120,6 +98,8 @@ flowchart LR
|
|
|
120
98
|
|
|
121
99
|
## Quick Start
|
|
122
100
|
|
|
101
|
+
**Runtime:** Node.js 20+
|
|
102
|
+
|
|
123
103
|
### Option A: npm (Recommended)
|
|
124
104
|
|
|
125
105
|
```bash
|
|
@@ -297,9 +277,9 @@ finally { refreshInProgress = false; }
|
|
|
297
277
|
|
|
298
278
|
---
|
|
299
279
|
|
|
300
|
-
## Web UI (for claude.ai)
|
|
280
|
+
## Web UI (for claude.ai — no MCP support)
|
|
301
281
|
|
|
302
|
-
|
|
282
|
+
If you're using claude.ai in a browser (which doesn't support MCP), you can use the REST server instead:
|
|
303
283
|
|
|
304
284
|
```bash
|
|
305
285
|
npm run web
|
|
@@ -310,7 +290,7 @@ npm run web
|
|
|
310
290
|
|
|
311
291
|
```
|
|
312
292
|
════════════════════════════════════════════════════════════
|
|
313
|
-
Slack Web API Server v1.1
|
|
293
|
+
Slack Web API Server v1.2.1
|
|
314
294
|
════════════════════════════════════════════════════════════
|
|
315
295
|
|
|
316
296
|
Dashboard: http://localhost:3000/?key=smcp_xxxxxxxxxxxx
|
package/docs/SETUP.md
CHANGED
|
Binary file
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#4A154B"/>
|
|
5
|
+
<stop offset="100%" style="stop-color:#3D1140"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
|
|
9
|
+
<!-- Background rounded square -->
|
|
10
|
+
<rect x="0" y="0" width="512" height="512" rx="96" ry="96" fill="url(#bg)"/>
|
|
11
|
+
|
|
12
|
+
<!-- Stylized hashtag/channel symbol -->
|
|
13
|
+
<g fill="none" stroke="#FFFFFF" stroke-width="36" stroke-linecap="round">
|
|
14
|
+
<!-- Vertical lines -->
|
|
15
|
+
<line x1="180" y1="140" x2="160" y2="372"/>
|
|
16
|
+
<line x1="352" y1="140" x2="332" y2="372"/>
|
|
17
|
+
<!-- Horizontal lines -->
|
|
18
|
+
<line x1="120" y1="200" x2="392" y2="200"/>
|
|
19
|
+
<line x1="120" y1="312" x2="392" y2="312"/>
|
|
20
|
+
</g>
|
|
21
|
+
|
|
22
|
+
<!-- Slack-colored connection dots (representing MCP bridge) -->
|
|
23
|
+
<circle cx="420" cy="92" r="28" fill="#36C5F0"/>
|
|
24
|
+
<circle cx="92" cy="420" r="28" fill="#2EB67D"/>
|
|
25
|
+
<circle cx="420" cy="420" r="28" fill="#ECB22E"/>
|
|
26
|
+
<circle cx="92" cy="92" r="28" fill="#E01E5A"/>
|
|
27
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 420">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bgGrad2" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#1a1a2e"/>
|
|
5
|
+
<stop offset="100%" style="stop-color:#16213e"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<marker id="arrowRed2" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
8
|
+
<path d="M0,0 L0,6 L9,3 z" fill="#e94560"/>
|
|
9
|
+
</marker>
|
|
10
|
+
<marker id="arrowTeal2" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
11
|
+
<path d="M0,0 L0,6 L9,3 z" fill="#4ecdc4"/>
|
|
12
|
+
</marker>
|
|
13
|
+
</defs>
|
|
14
|
+
|
|
15
|
+
<!-- Background -->
|
|
16
|
+
<rect width="900" height="420" fill="url(#bgGrad2)" rx="12"/>
|
|
17
|
+
|
|
18
|
+
<!-- Left Side: OAuth (Red-tinted) -->
|
|
19
|
+
<rect x="30" y="50" width="400" height="340" rx="12" fill="#e94560" opacity="0.1" stroke="#e94560" stroke-width="2"/>
|
|
20
|
+
<text x="230" y="85" text-anchor="middle" fill="#fca5a5" font-family="system-ui, -apple-system, sans-serif" font-size="16" font-weight="600">Official Slack API (OAuth)</text>
|
|
21
|
+
|
|
22
|
+
<!-- OAuth Flow - Row 1 -->
|
|
23
|
+
<rect x="45" y="110" width="120" height="45" rx="8" fill="#2d2d2d" stroke="#e94560" stroke-width="1"/>
|
|
24
|
+
<text x="105" y="138" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12">Create App</text>
|
|
25
|
+
|
|
26
|
+
<path d="M 170 132 L 190 132" stroke="#e94560" stroke-width="2" marker-end="url(#arrowRed2)"/>
|
|
27
|
+
|
|
28
|
+
<rect x="195" y="110" width="120" height="45" rx="8" fill="#2d2d2d" stroke="#e94560" stroke-width="1"/>
|
|
29
|
+
<text x="255" y="138" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12">Request Scopes</text>
|
|
30
|
+
|
|
31
|
+
<path d="M 320 132 L 340 132" stroke="#e94560" stroke-width="2" marker-end="url(#arrowRed2)"/>
|
|
32
|
+
|
|
33
|
+
<rect x="345" y="110" width="75" height="45" rx="8" fill="#e94560" opacity="0.4" stroke="#e94560" stroke-width="1"/>
|
|
34
|
+
<text x="382" y="128" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="600">Admin</text>
|
|
35
|
+
<text x="382" y="144" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="600">Approval</text>
|
|
36
|
+
|
|
37
|
+
<!-- Arrow down from Admin Approval -->
|
|
38
|
+
<path d="M 382 160 L 382 195" stroke="#e94560" stroke-width="2" marker-end="url(#arrowRed2)"/>
|
|
39
|
+
|
|
40
|
+
<!-- OAuth Flow - Row 2 -->
|
|
41
|
+
<rect x="95" y="205" width="140" height="45" rx="8" fill="#2d2d2d" stroke="#e94560" stroke-width="1"/>
|
|
42
|
+
<text x="165" y="233" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12">User Authorization</text>
|
|
43
|
+
|
|
44
|
+
<path d="M 240 227 L 260 227" stroke="#e94560" stroke-width="2" marker-end="url(#arrowRed2)"/>
|
|
45
|
+
|
|
46
|
+
<rect x="265" y="205" width="140" height="45" rx="8" fill="#2d2d2d" stroke="#e94560" stroke-width="1"/>
|
|
47
|
+
<text x="335" y="233" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12">Limited Access</text>
|
|
48
|
+
|
|
49
|
+
<!-- OAuth Result (blocked) -->
|
|
50
|
+
<rect x="70" y="280" width="290" height="60" rx="8" fill="#e94560" opacity="0.15" stroke="#e94560" stroke-width="2" stroke-dasharray="4"/>
|
|
51
|
+
<text x="215" y="305" text-anchor="middle" fill="#fca5a5" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">No DMs without</text>
|
|
52
|
+
<text x="215" y="325" text-anchor="middle" fill="#fca5a5" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">per-conversation consent</text>
|
|
53
|
+
|
|
54
|
+
<!-- Right Side: Session Mirroring (Green-tinted) -->
|
|
55
|
+
<rect x="470" y="50" width="400" height="340" rx="12" fill="#4ecdc4" opacity="0.1" stroke="#4ecdc4" stroke-width="2"/>
|
|
56
|
+
<text x="670" y="85" text-anchor="middle" fill="#5eead4" font-family="system-ui, -apple-system, sans-serif" font-size="16" font-weight="600">Session Mirroring</text>
|
|
57
|
+
|
|
58
|
+
<!-- Session Mirroring Flow -->
|
|
59
|
+
<rect x="495" y="120" width="160" height="50" rx="8" fill="#3b82f6" opacity="0.9"/>
|
|
60
|
+
<text x="575" y="150" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12" font-weight="500">Open Slack in Chrome</text>
|
|
61
|
+
|
|
62
|
+
<path d="M 660 145 L 700 145" stroke="#4ecdc4" stroke-width="3" marker-end="url(#arrowTeal2)"/>
|
|
63
|
+
|
|
64
|
+
<rect x="705" y="120" width="140" height="50" rx="8" fill="#da7756" opacity="0.9"/>
|
|
65
|
+
<text x="775" y="150" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="12" font-weight="500">Mirror Session</text>
|
|
66
|
+
|
|
67
|
+
<path d="M 775 175 L 775 210" stroke="#4ecdc4" stroke-width="3" marker-end="url(#arrowTeal2)"/>
|
|
68
|
+
|
|
69
|
+
<rect x="605" y="220" width="160" height="50" rx="8" fill="#4ecdc4" opacity="0.9"/>
|
|
70
|
+
<text x="685" y="250" text-anchor="middle" fill="#1a1a2e" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600">Full Access</text>
|
|
71
|
+
|
|
72
|
+
<!-- Session Mirroring Result (success) -->
|
|
73
|
+
<rect x="510" y="295" width="340" height="60" rx="8" fill="#4ecdc4" opacity="0.2" stroke="#4ecdc4" stroke-width="2"/>
|
|
74
|
+
<text x="680" y="320" text-anchor="middle" fill="#5eead4" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">Your DMs, Channels, Search, History</text>
|
|
75
|
+
<text x="680" y="340" text-anchor="middle" fill="#5eead4" font-family="system-ui, -apple-system, sans-serif" font-size="11">✓ Same access as your browser</text>
|
|
76
|
+
|
|
77
|
+
<!-- VS divider -->
|
|
78
|
+
<circle cx="450" cy="210" r="24" fill="#1a1a2e" stroke="#ffffff" stroke-width="2" opacity="0.9"/>
|
|
79
|
+
<text x="450" y="216" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="700">vs</text>
|
|
80
|
+
</svg>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 400">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#1a1a2e"/>
|
|
5
|
+
<stop offset="100%" style="stop-color:#16213e"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<filter id="glow">
|
|
8
|
+
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
|
|
9
|
+
<feMerge>
|
|
10
|
+
<feMergeNode in="coloredBlur"/>
|
|
11
|
+
<feMergeNode in="SourceGraphic"/>
|
|
12
|
+
</feMerge>
|
|
13
|
+
</filter>
|
|
14
|
+
</defs>
|
|
15
|
+
|
|
16
|
+
<!-- Background -->
|
|
17
|
+
<rect width="900" height="400" fill="url(#bgGrad)" rx="12"/>
|
|
18
|
+
|
|
19
|
+
<!-- Title -->
|
|
20
|
+
<text x="450" y="35" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="18" font-weight="600">Session Mirroring Flow</text>
|
|
21
|
+
|
|
22
|
+
<!-- Participants -->
|
|
23
|
+
<!-- Chrome -->
|
|
24
|
+
<rect x="50" y="60" width="120" height="40" rx="8" fill="#3b82f6" opacity="0.9"/>
|
|
25
|
+
<text x="110" y="85" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">Chrome</text>
|
|
26
|
+
<line x1="110" y1="100" x2="110" y2="360" stroke="#3b82f6" stroke-width="2" stroke-dasharray="4"/>
|
|
27
|
+
|
|
28
|
+
<!-- AppleScript -->
|
|
29
|
+
<rect x="220" y="60" width="120" height="40" rx="8" fill="#8b5cf6" opacity="0.9"/>
|
|
30
|
+
<text x="280" y="85" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">AppleScript</text>
|
|
31
|
+
<line x1="280" y1="100" x2="280" y2="360" stroke="#8b5cf6" stroke-width="2" stroke-dasharray="4"/>
|
|
32
|
+
|
|
33
|
+
<!-- Token Store -->
|
|
34
|
+
<rect x="390" y="60" width="120" height="40" rx="8" fill="#4ecdc4" opacity="0.9"/>
|
|
35
|
+
<text x="450" y="85" text-anchor="middle" fill="#1a1a2e" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">Token Store</text>
|
|
36
|
+
<line x1="450" y1="100" x2="450" y2="360" stroke="#4ecdc4" stroke-width="2" stroke-dasharray="4"/>
|
|
37
|
+
|
|
38
|
+
<!-- MCP Server -->
|
|
39
|
+
<rect x="560" y="60" width="120" height="40" rx="8" fill="#da7756" opacity="0.9"/>
|
|
40
|
+
<text x="620" y="85" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">MCP Server</text>
|
|
41
|
+
<line x1="620" y1="100" x2="620" y2="360" stroke="#da7756" stroke-width="2" stroke-dasharray="4"/>
|
|
42
|
+
|
|
43
|
+
<!-- Slack API -->
|
|
44
|
+
<rect x="730" y="60" width="120" height="40" rx="8" fill="#e94560" opacity="0.9"/>
|
|
45
|
+
<text x="790" y="85" text-anchor="middle" fill="#ffffff" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">Slack API</text>
|
|
46
|
+
<line x1="790" y1="100" x2="790" y2="360" stroke="#e94560" stroke-width="2" stroke-dasharray="4"/>
|
|
47
|
+
|
|
48
|
+
<!-- Note: Logged into Slack -->
|
|
49
|
+
<rect x="60" y="115" width="100" height="30" rx="4" fill="#3b82f6" opacity="0.2" stroke="#3b82f6" stroke-width="1"/>
|
|
50
|
+
<text x="110" y="135" text-anchor="middle" fill="#93c5fd" font-family="system-ui, -apple-system, sans-serif" font-size="10">Logged into Slack</text>
|
|
51
|
+
|
|
52
|
+
<!-- Arrow 1: Script → Chrome -->
|
|
53
|
+
<line x1="280" y1="160" x2="130" y2="160" stroke="#8b5cf6" stroke-width="2" marker-end="url(#arrowPurple)"/>
|
|
54
|
+
<text x="205" y="153" text-anchor="middle" fill="#c4b5fd" font-family="system-ui, -apple-system, sans-serif" font-size="10">Execute JS in tab</text>
|
|
55
|
+
|
|
56
|
+
<!-- Arrow 2: Chrome → Script (dashed return) -->
|
|
57
|
+
<line x1="130" y1="185" x2="260" y2="185" stroke="#3b82f6" stroke-width="2" stroke-dasharray="5" marker-end="url(#arrowBlue)"/>
|
|
58
|
+
<text x="195" y="200" text-anchor="middle" fill="#93c5fd" font-family="system-ui, -apple-system, sans-serif" font-size="10">xoxc- + xoxd- tokens</text>
|
|
59
|
+
|
|
60
|
+
<!-- Arrow 3: Script → Store -->
|
|
61
|
+
<line x1="300" y1="225" x2="430" y2="225" stroke="#8b5cf6" stroke-width="2" marker-end="url(#arrowPurple)"/>
|
|
62
|
+
<text x="365" y="218" text-anchor="middle" fill="#c4b5fd" font-family="system-ui, -apple-system, sans-serif" font-size="10">Save tokens</text>
|
|
63
|
+
|
|
64
|
+
<!-- Store self-arrow (Keychain) -->
|
|
65
|
+
<path d="M 470 245 Q 500 245 500 260 Q 500 275 470 275" fill="none" stroke="#4ecdc4" stroke-width="2"/>
|
|
66
|
+
<text x="515" y="265" fill="#5eead4" font-family="system-ui, -apple-system, sans-serif" font-size="10">Keychain encrypt</text>
|
|
67
|
+
|
|
68
|
+
<!-- Divider -->
|
|
69
|
+
<line x1="50" y1="300" x2="850" y2="300" stroke="#ffffff" stroke-width="1" opacity="0.2"/>
|
|
70
|
+
<text x="60" y="295" fill="#ffffff" opacity="0.5" font-family="system-ui, -apple-system, sans-serif" font-size="10">Claude requests data</text>
|
|
71
|
+
|
|
72
|
+
<!-- Arrow 4: MCP → Store -->
|
|
73
|
+
<line x1="600" y1="320" x2="470" y2="320" stroke="#da7756" stroke-width="2" marker-end="url(#arrowOrange)"/>
|
|
74
|
+
<text x="535" y="313" text-anchor="middle" fill="#fbbf8c" font-family="system-ui, -apple-system, sans-serif" font-size="10">Load creds</text>
|
|
75
|
+
|
|
76
|
+
<!-- Arrow 5: Store → MCP (return) -->
|
|
77
|
+
<line x1="470" y1="340" x2="600" y2="340" stroke="#4ecdc4" stroke-width="2" stroke-dasharray="5" marker-end="url(#arrowTeal)"/>
|
|
78
|
+
|
|
79
|
+
<!-- Arrow 6: MCP → Slack -->
|
|
80
|
+
<line x1="640" y1="355" x2="770" y2="355" stroke="#da7756" stroke-width="2" marker-end="url(#arrowOrange)"/>
|
|
81
|
+
<text x="705" y="348" text-anchor="middle" fill="#fbbf8c" font-family="system-ui, -apple-system, sans-serif" font-size="10">GET messages</text>
|
|
82
|
+
|
|
83
|
+
<!-- Arrow 7: Slack → MCP (return) -->
|
|
84
|
+
<line x1="770" y1="375" x2="640" y2="375" stroke="#e94560" stroke-width="2" stroke-dasharray="5" marker-end="url(#arrowRed)"/>
|
|
85
|
+
<text x="705" y="390" text-anchor="middle" fill="#fca5a5" font-family="system-ui, -apple-system, sans-serif" font-size="10">Full history</text>
|
|
86
|
+
|
|
87
|
+
<!-- Arrow markers -->
|
|
88
|
+
<defs>
|
|
89
|
+
<marker id="arrowPurple" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
90
|
+
<path d="M0,0 L0,6 L9,3 z" fill="#8b5cf6"/>
|
|
91
|
+
</marker>
|
|
92
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
93
|
+
<path d="M0,0 L0,6 L9,3 z" fill="#3b82f6"/>
|
|
94
|
+
</marker>
|
|
95
|
+
<marker id="arrowTeal" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
96
|
+
<path d="M0,0 L0,6 L9,3 z" fill="#4ecdc4"/>
|
|
97
|
+
</marker>
|
|
98
|
+
<marker id="arrowOrange" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
99
|
+
<path d="M0,0 L0,6 L9,3 z" fill="#da7756"/>
|
|
100
|
+
</marker>
|
|
101
|
+
<marker id="arrowRed" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
102
|
+
<path d="M0,0 L0,6 L9,3 z" fill="#e94560"/>
|
|
103
|
+
</marker>
|
|
104
|
+
</defs>
|
|
105
|
+
</svg>
|
package/lib/slack-client.js
CHANGED
|
@@ -24,6 +24,15 @@ const RETRYABLE_NETWORK_ERRORS = [
|
|
|
24
24
|
'ECONNREFUSED', 'EPIPE', 'UND_ERR_CONNECT_TIMEOUT'
|
|
25
25
|
];
|
|
26
26
|
|
|
27
|
+
// Slack API methods that require form-encoded params instead of JSON
|
|
28
|
+
const FORM_ENCODED_METHODS = new Set([
|
|
29
|
+
"conversations.replies",
|
|
30
|
+
"search.messages",
|
|
31
|
+
"search.all",
|
|
32
|
+
"search.files",
|
|
33
|
+
"users.info",
|
|
34
|
+
]);
|
|
35
|
+
|
|
27
36
|
let lastRefreshAttempt = 0;
|
|
28
37
|
|
|
29
38
|
// ============ LRU Cache with TTL ============
|
|
@@ -149,16 +158,36 @@ export async function slackAPI(method, params = {}, options = {}) {
|
|
|
149
158
|
checkTokenHealth({ error: () => {} }).catch(() => {});
|
|
150
159
|
}
|
|
151
160
|
|
|
161
|
+
const useForm = FORM_ENCODED_METHODS.has(method);
|
|
162
|
+
|
|
152
163
|
let response;
|
|
153
164
|
try {
|
|
165
|
+
const headers = {
|
|
166
|
+
"Authorization": `Bearer ${creds.token}`,
|
|
167
|
+
"Cookie": `d=${creds.cookie}`,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
let body;
|
|
171
|
+
if (useForm) {
|
|
172
|
+
// URLSearchParams coerces non-primitives to "[object Object]",
|
|
173
|
+
// so stringify any arrays/objects before encoding.
|
|
174
|
+
const safeParams = {};
|
|
175
|
+
for (const [key, value] of Object.entries(params)) {
|
|
176
|
+
safeParams[key] = (typeof value === "object" && value !== null)
|
|
177
|
+
? JSON.stringify(value)
|
|
178
|
+
: String(value);
|
|
179
|
+
}
|
|
180
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8";
|
|
181
|
+
body = new URLSearchParams(safeParams).toString();
|
|
182
|
+
} else {
|
|
183
|
+
headers["Content-Type"] = "application/json; charset=utf-8";
|
|
184
|
+
body = JSON.stringify(params);
|
|
185
|
+
}
|
|
186
|
+
|
|
154
187
|
response = await fetch(`https://slack.com/api/${method}`, {
|
|
155
188
|
method: "POST",
|
|
156
|
-
headers
|
|
157
|
-
|
|
158
|
-
"Cookie": `d=${creds.cookie}`,
|
|
159
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
160
|
-
},
|
|
161
|
-
body: JSON.stringify(params),
|
|
189
|
+
headers,
|
|
190
|
+
body,
|
|
162
191
|
});
|
|
163
192
|
} catch (networkError) {
|
|
164
193
|
// Retry on network errors with exponential backoff + jitter
|
package/lib/tools.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* MCP Tool Definitions
|
|
3
3
|
*
|
|
4
4
|
* All Slack MCP tools in one place for easy maintenance.
|
|
5
|
+
* Includes MCP annotations for better tool discovery and safety hints.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
export const TOOLS = [
|
|
@@ -11,6 +12,12 @@ export const TOOLS = [
|
|
|
11
12
|
inputSchema: {
|
|
12
13
|
type: "object",
|
|
13
14
|
properties: {}
|
|
15
|
+
},
|
|
16
|
+
annotations: {
|
|
17
|
+
title: "Token Status",
|
|
18
|
+
readOnlyHint: true,
|
|
19
|
+
idempotentHint: true,
|
|
20
|
+
openWorldHint: false
|
|
14
21
|
}
|
|
15
22
|
},
|
|
16
23
|
{
|
|
@@ -19,6 +26,12 @@ export const TOOLS = [
|
|
|
19
26
|
inputSchema: {
|
|
20
27
|
type: "object",
|
|
21
28
|
properties: {}
|
|
29
|
+
},
|
|
30
|
+
annotations: {
|
|
31
|
+
title: "Health Check",
|
|
32
|
+
readOnlyHint: true,
|
|
33
|
+
idempotentHint: true,
|
|
34
|
+
openWorldHint: true
|
|
22
35
|
}
|
|
23
36
|
},
|
|
24
37
|
{
|
|
@@ -27,6 +40,13 @@ export const TOOLS = [
|
|
|
27
40
|
inputSchema: {
|
|
28
41
|
type: "object",
|
|
29
42
|
properties: {}
|
|
43
|
+
},
|
|
44
|
+
annotations: {
|
|
45
|
+
title: "Refresh Tokens",
|
|
46
|
+
readOnlyHint: false,
|
|
47
|
+
destructiveHint: false,
|
|
48
|
+
idempotentHint: true,
|
|
49
|
+
openWorldHint: false
|
|
30
50
|
}
|
|
31
51
|
},
|
|
32
52
|
{
|
|
@@ -49,6 +69,12 @@ export const TOOLS = [
|
|
|
49
69
|
description: "If true, actively discover all DMs (slower, may hit rate limits on large workspaces). Default false uses cached DMs."
|
|
50
70
|
}
|
|
51
71
|
}
|
|
72
|
+
},
|
|
73
|
+
annotations: {
|
|
74
|
+
title: "List Conversations",
|
|
75
|
+
readOnlyHint: true,
|
|
76
|
+
idempotentHint: true,
|
|
77
|
+
openWorldHint: true
|
|
52
78
|
}
|
|
53
79
|
},
|
|
54
80
|
{
|
|
@@ -79,6 +105,12 @@ export const TOOLS = [
|
|
|
79
105
|
}
|
|
80
106
|
},
|
|
81
107
|
required: ["channel_id"]
|
|
108
|
+
},
|
|
109
|
+
annotations: {
|
|
110
|
+
title: "Conversation History",
|
|
111
|
+
readOnlyHint: true,
|
|
112
|
+
idempotentHint: true,
|
|
113
|
+
openWorldHint: true
|
|
82
114
|
}
|
|
83
115
|
},
|
|
84
116
|
{
|
|
@@ -113,6 +145,12 @@ export const TOOLS = [
|
|
|
113
145
|
}
|
|
114
146
|
},
|
|
115
147
|
required: ["channel_id"]
|
|
148
|
+
},
|
|
149
|
+
annotations: {
|
|
150
|
+
title: "Full Conversation Export",
|
|
151
|
+
readOnlyHint: true,
|
|
152
|
+
idempotentHint: true,
|
|
153
|
+
openWorldHint: true
|
|
116
154
|
}
|
|
117
155
|
},
|
|
118
156
|
{
|
|
@@ -131,6 +169,12 @@ export const TOOLS = [
|
|
|
131
169
|
}
|
|
132
170
|
},
|
|
133
171
|
required: ["query"]
|
|
172
|
+
},
|
|
173
|
+
annotations: {
|
|
174
|
+
title: "Search Messages",
|
|
175
|
+
readOnlyHint: true,
|
|
176
|
+
idempotentHint: true,
|
|
177
|
+
openWorldHint: true
|
|
134
178
|
}
|
|
135
179
|
},
|
|
136
180
|
{
|
|
@@ -145,6 +189,12 @@ export const TOOLS = [
|
|
|
145
189
|
}
|
|
146
190
|
},
|
|
147
191
|
required: ["user_id"]
|
|
192
|
+
},
|
|
193
|
+
annotations: {
|
|
194
|
+
title: "User Info",
|
|
195
|
+
readOnlyHint: true,
|
|
196
|
+
idempotentHint: true,
|
|
197
|
+
openWorldHint: true
|
|
148
198
|
}
|
|
149
199
|
},
|
|
150
200
|
{
|
|
@@ -167,6 +217,13 @@ export const TOOLS = [
|
|
|
167
217
|
}
|
|
168
218
|
},
|
|
169
219
|
required: ["channel_id", "text"]
|
|
220
|
+
},
|
|
221
|
+
annotations: {
|
|
222
|
+
title: "Send Message",
|
|
223
|
+
readOnlyHint: false,
|
|
224
|
+
destructiveHint: false,
|
|
225
|
+
idempotentHint: false,
|
|
226
|
+
openWorldHint: true
|
|
170
227
|
}
|
|
171
228
|
},
|
|
172
229
|
{
|
|
@@ -185,6 +242,12 @@ export const TOOLS = [
|
|
|
185
242
|
}
|
|
186
243
|
},
|
|
187
244
|
required: ["channel_id", "thread_ts"]
|
|
245
|
+
},
|
|
246
|
+
annotations: {
|
|
247
|
+
title: "Get Thread",
|
|
248
|
+
readOnlyHint: true,
|
|
249
|
+
idempotentHint: true,
|
|
250
|
+
openWorldHint: true
|
|
188
251
|
}
|
|
189
252
|
},
|
|
190
253
|
{
|
|
@@ -198,6 +261,12 @@ export const TOOLS = [
|
|
|
198
261
|
description: "Maximum users to return (default 500, supports pagination)"
|
|
199
262
|
}
|
|
200
263
|
}
|
|
264
|
+
},
|
|
265
|
+
annotations: {
|
|
266
|
+
title: "List Users",
|
|
267
|
+
readOnlyHint: true,
|
|
268
|
+
idempotentHint: true,
|
|
269
|
+
openWorldHint: true
|
|
201
270
|
}
|
|
202
271
|
}
|
|
203
272
|
];
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jtalk22/slack-mcp",
|
|
3
3
|
"mcpName": "io.github.jtalk22/slack-mcp-server",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"description": "Full Slack access for Claude - DMs, channels, search. No OAuth. No admin approval. Just works.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "src/server.js",
|
|
8
8
|
"bin": {
|
|
9
|
+
"slack-mcp": "./src/cli.js",
|
|
9
10
|
"slack-mcp-server": "./src/server.js",
|
|
10
11
|
"slack-mcp-http": "./src/server-http.js",
|
|
11
12
|
"slack-mcp-web": "./src/web-server.js",
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
"node": ">=20.0.0"
|
|
89
90
|
},
|
|
90
91
|
"dependencies": {
|
|
91
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
92
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
92
93
|
"express": "^4.18.2"
|
|
93
94
|
},
|
|
94
95
|
"files": [
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Slack MCP Server Demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
body {
|
|
14
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
15
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
16
|
+
min-height: 100vh;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
padding: 2rem;
|
|
22
|
+
}
|
|
23
|
+
.container {
|
|
24
|
+
max-width: 900px;
|
|
25
|
+
width: 100%;
|
|
26
|
+
}
|
|
27
|
+
h1 {
|
|
28
|
+
color: #ffffff;
|
|
29
|
+
font-size: 1.75rem;
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
text-align: center;
|
|
32
|
+
margin-bottom: 0.5rem;
|
|
33
|
+
}
|
|
34
|
+
.subtitle {
|
|
35
|
+
color: #94a3b8;
|
|
36
|
+
text-align: center;
|
|
37
|
+
margin-bottom: 1.5rem;
|
|
38
|
+
font-size: 1rem;
|
|
39
|
+
}
|
|
40
|
+
.video-wrapper {
|
|
41
|
+
position: relative;
|
|
42
|
+
border-radius: 12px;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
45
|
+
background: #0f0f1a;
|
|
46
|
+
}
|
|
47
|
+
video {
|
|
48
|
+
width: 100%;
|
|
49
|
+
display: block;
|
|
50
|
+
border-radius: 12px;
|
|
51
|
+
}
|
|
52
|
+
.controls {
|
|
53
|
+
display: flex;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
gap: 1rem;
|
|
56
|
+
margin-top: 1.5rem;
|
|
57
|
+
}
|
|
58
|
+
.btn {
|
|
59
|
+
padding: 0.75rem 1.5rem;
|
|
60
|
+
border-radius: 8px;
|
|
61
|
+
border: none;
|
|
62
|
+
font-size: 0.875rem;
|
|
63
|
+
font-weight: 500;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
transition: all 0.2s;
|
|
66
|
+
}
|
|
67
|
+
.btn-primary {
|
|
68
|
+
background: #4ecdc4;
|
|
69
|
+
color: #1a1a2e;
|
|
70
|
+
}
|
|
71
|
+
.btn-primary:hover {
|
|
72
|
+
background: #5eead4;
|
|
73
|
+
transform: translateY(-1px);
|
|
74
|
+
}
|
|
75
|
+
.btn-secondary {
|
|
76
|
+
background: rgba(255, 255, 255, 0.1);
|
|
77
|
+
color: #ffffff;
|
|
78
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
79
|
+
}
|
|
80
|
+
.btn-secondary:hover {
|
|
81
|
+
background: rgba(255, 255, 255, 0.15);
|
|
82
|
+
}
|
|
83
|
+
.back-link {
|
|
84
|
+
margin-top: 2rem;
|
|
85
|
+
text-align: center;
|
|
86
|
+
}
|
|
87
|
+
.back-link a {
|
|
88
|
+
color: #94a3b8;
|
|
89
|
+
text-decoration: none;
|
|
90
|
+
font-size: 0.875rem;
|
|
91
|
+
}
|
|
92
|
+
.back-link a:hover {
|
|
93
|
+
color: #ffffff;
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
96
|
+
</head>
|
|
97
|
+
<body>
|
|
98
|
+
<div class="container">
|
|
99
|
+
<h1>Slack MCP Server</h1>
|
|
100
|
+
<p class="subtitle">Full workspace access via local session mirroring</p>
|
|
101
|
+
|
|
102
|
+
<div class="video-wrapper">
|
|
103
|
+
<video id="demo" poster="../docs/images/demo-poster.png" playsinline>
|
|
104
|
+
<source src="../docs/videos/demo-claude-v1.2.webm" type="video/webm">
|
|
105
|
+
<source src="../docs/images/demo-claude-v1.2.gif" type="image/gif">
|
|
106
|
+
Your browser does not support the video tag.
|
|
107
|
+
</video>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div class="controls">
|
|
111
|
+
<button class="btn btn-primary" onclick="togglePlay()">Play / Pause</button>
|
|
112
|
+
<button class="btn btn-secondary" onclick="restart()">Restart</button>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div class="back-link">
|
|
116
|
+
<a href="https://github.com/jtalk22/slack-mcp-server">← Back to Repository</a>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<script>
|
|
121
|
+
const video = document.getElementById('demo');
|
|
122
|
+
|
|
123
|
+
// Autoplay with 1 second delay
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
video.play().catch(() => {
|
|
126
|
+
// Autoplay blocked, user will need to click
|
|
127
|
+
console.log('Autoplay blocked, click to play');
|
|
128
|
+
});
|
|
129
|
+
}, 1000);
|
|
130
|
+
|
|
131
|
+
function togglePlay() {
|
|
132
|
+
if (video.paused) {
|
|
133
|
+
video.play();
|
|
134
|
+
} else {
|
|
135
|
+
video.pause();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function restart() {
|
|
140
|
+
video.currentTime = 0;
|
|
141
|
+
video.play();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Loop the video
|
|
145
|
+
video.addEventListener('ended', () => {
|
|
146
|
+
video.currentTime = 0;
|
|
147
|
+
video.play();
|
|
148
|
+
});
|
|
149
|
+
</script>
|
|
150
|
+
</body>
|
|
151
|
+
</html>
|
package/scripts/record-demo.js
CHANGED
|
@@ -73,6 +73,10 @@ async function recordDemo() {
|
|
|
73
73
|
await page.goto(`file://${demoPath}`);
|
|
74
74
|
await page.waitForTimeout(1000);
|
|
75
75
|
|
|
76
|
+
// Hold on initial frame for a few seconds (visible first frame in GIF)
|
|
77
|
+
console.log('⏸️ Holding initial frame (3s)...');
|
|
78
|
+
await page.waitForTimeout(3000);
|
|
79
|
+
|
|
76
80
|
// Set slow speed for video recording
|
|
77
81
|
console.log(`⏱️ Setting speed to ${CONFIG.speed}x...`);
|
|
78
82
|
await page.selectOption('#speedSelect', CONFIG.speed);
|
package/scripts/setup-wizard.js
CHANGED
|
@@ -16,7 +16,7 @@ import { loadTokens, saveTokens, extractFromChrome, isAutoRefreshAvailable, TOKE
|
|
|
16
16
|
import { slackAPI } from "../lib/slack-client.js";
|
|
17
17
|
|
|
18
18
|
const IS_MACOS = platform() === 'darwin';
|
|
19
|
-
const VERSION = "1.2.
|
|
19
|
+
const VERSION = "1.2.1";
|
|
20
20
|
|
|
21
21
|
// ANSI colors
|
|
22
22
|
const colors = {
|
|
@@ -69,24 +69,27 @@ async function pressEnterToContinue(rl) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
async function validateTokens(token, cookie) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
process.env.SLACK_TOKEN = token;
|
|
77
|
-
process.env.SLACK_COOKIE = cookie;
|
|
78
|
-
|
|
79
|
-
const result = await slackAPI("auth.test", {});
|
|
72
|
+
const hadOldToken = Object.prototype.hasOwnProperty.call(process.env, "SLACK_TOKEN");
|
|
73
|
+
const hadOldCookie = Object.prototype.hasOwnProperty.call(process.env, "SLACK_COOKIE");
|
|
74
|
+
const oldToken = process.env.SLACK_TOKEN;
|
|
75
|
+
const oldCookie = process.env.SLACK_COOKIE;
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (oldCookie) process.env.SLACK_COOKIE = oldCookie;
|
|
85
|
-
else delete process.env.SLACK_COOKIE;
|
|
77
|
+
// Temporarily set env vars for validation
|
|
78
|
+
process.env.SLACK_TOKEN = token;
|
|
79
|
+
process.env.SLACK_COOKIE = cookie;
|
|
86
80
|
|
|
81
|
+
try {
|
|
82
|
+
const result = await slackAPI("auth.test", {});
|
|
87
83
|
return { valid: true, user: result.user, team: result.team };
|
|
88
84
|
} catch (e) {
|
|
89
85
|
return { valid: false, error: e.message };
|
|
86
|
+
} finally {
|
|
87
|
+
// Always restore prior process env state
|
|
88
|
+
if (hadOldToken) process.env.SLACK_TOKEN = oldToken;
|
|
89
|
+
else delete process.env.SLACK_TOKEN;
|
|
90
|
+
|
|
91
|
+
if (hadOldCookie) process.env.SLACK_COOKIE = oldCookie;
|
|
92
|
+
else delete process.env.SLACK_COOKIE;
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -175,7 +178,7 @@ async function runManualSetup(rl) {
|
|
|
175
178
|
" .teams)[0]].token",
|
|
176
179
|
], 55);
|
|
177
180
|
print();
|
|
178
|
-
print(
|
|
181
|
+
print(` Copy the token (starts with ${colors.cyan}xoxc-${colors.reset})`);
|
|
179
182
|
print();
|
|
180
183
|
|
|
181
184
|
const token = await question(rl, `${colors.bold}Paste your token:${colors.reset} `);
|
|
@@ -187,7 +190,7 @@ async function runManualSetup(rl) {
|
|
|
187
190
|
|
|
188
191
|
print();
|
|
189
192
|
print(`${colors.bold}Step 4:${colors.reset} Go to ${colors.cyan}Application${colors.reset} tab → ${colors.cyan}Cookies${colors.reset} → slack.com`);
|
|
190
|
-
print(
|
|
193
|
+
print(` Find the '${colors.cyan}d${colors.reset}' cookie and copy its value`);
|
|
191
194
|
print();
|
|
192
195
|
|
|
193
196
|
const cookie = await question(rl, `${colors.bold}Paste your cookie:${colors.reset} `);
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* slack-mcp package entrypoint dispatcher.
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* - default stdio server startup
|
|
7
|
+
* - web/http server modes
|
|
8
|
+
* - setup wizard and its status/help/version flags
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const firstArg = args[0];
|
|
19
|
+
|
|
20
|
+
const WIZARD_ARGS = new Set([
|
|
21
|
+
"--setup", "setup",
|
|
22
|
+
"--status", "status",
|
|
23
|
+
"--version", "-v",
|
|
24
|
+
"--help", "-h", "help",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
let scriptPath = join(__dirname, "server.js");
|
|
28
|
+
let scriptArgs = args;
|
|
29
|
+
|
|
30
|
+
if (firstArg === "web") {
|
|
31
|
+
scriptPath = join(__dirname, "web-server.js");
|
|
32
|
+
scriptArgs = args.slice(1);
|
|
33
|
+
} else if (firstArg === "http") {
|
|
34
|
+
scriptPath = join(__dirname, "server-http.js");
|
|
35
|
+
scriptArgs = args.slice(1);
|
|
36
|
+
} else if (WIZARD_ARGS.has(firstArg)) {
|
|
37
|
+
scriptPath = join(__dirname, "../scripts/setup-wizard.js");
|
|
38
|
+
scriptArgs = args;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const child = spawn(process.execPath, [scriptPath, ...scriptArgs], {
|
|
42
|
+
stdio: "inherit",
|
|
43
|
+
env: process.env,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
child.on("error", (error) => {
|
|
47
|
+
console.error(`Failed to start ${scriptPath}: ${error.message}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
child.on("exit", (code, signal) => {
|
|
52
|
+
if (signal) {
|
|
53
|
+
process.kill(process.pid, signal);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
process.exit(code ?? 0);
|
|
57
|
+
});
|
package/src/server-http.js
CHANGED
package/src/server.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - Network error retry with exponential backoff
|
|
12
12
|
* - Background token health monitoring
|
|
13
13
|
*
|
|
14
|
-
* @version 1.2.
|
|
14
|
+
* @version 1.2.1
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -19,6 +19,10 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
19
19
|
import {
|
|
20
20
|
CallToolRequestSchema,
|
|
21
21
|
ListToolsRequestSchema,
|
|
22
|
+
ListPromptsRequestSchema,
|
|
23
|
+
GetPromptRequestSchema,
|
|
24
|
+
ListResourcesRequestSchema,
|
|
25
|
+
ReadResourceRequestSchema,
|
|
22
26
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
23
27
|
|
|
24
28
|
import { loadTokens } from "../lib/token-store.js";
|
|
@@ -43,12 +47,70 @@ const BACKGROUND_REFRESH_INTERVAL = 4 * 60 * 60 * 1000;
|
|
|
43
47
|
|
|
44
48
|
// Package info
|
|
45
49
|
const SERVER_NAME = "slack-mcp-server";
|
|
46
|
-
const SERVER_VERSION = "1.2.
|
|
50
|
+
const SERVER_VERSION = "1.2.1";
|
|
51
|
+
|
|
52
|
+
// MCP Prompts - predefined prompt templates for common Slack operations
|
|
53
|
+
const PROMPTS = [
|
|
54
|
+
{
|
|
55
|
+
name: "search-recent",
|
|
56
|
+
description: "Search workspace for messages from the past week",
|
|
57
|
+
arguments: [
|
|
58
|
+
{
|
|
59
|
+
name: "query",
|
|
60
|
+
description: "Search terms to look for",
|
|
61
|
+
required: true
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "summarize-channel",
|
|
67
|
+
description: "Get recent activity from a channel for summarization",
|
|
68
|
+
arguments: [
|
|
69
|
+
{
|
|
70
|
+
name: "channel_id",
|
|
71
|
+
description: "Channel ID to summarize",
|
|
72
|
+
required: true
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "days",
|
|
76
|
+
description: "Number of days to look back (default 7)",
|
|
77
|
+
required: false
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "find-messages-from",
|
|
83
|
+
description: "Find all messages from a specific user",
|
|
84
|
+
arguments: [
|
|
85
|
+
{
|
|
86
|
+
name: "username",
|
|
87
|
+
description: "Username or display name to search for",
|
|
88
|
+
required: true
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// MCP Resources - data sources the server provides
|
|
95
|
+
const RESOURCES = [
|
|
96
|
+
{
|
|
97
|
+
uri: "slack://workspace/info",
|
|
98
|
+
name: "Workspace Info",
|
|
99
|
+
description: "Current workspace name, team, and authenticated user",
|
|
100
|
+
mimeType: "application/json"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
uri: "slack://conversations/list",
|
|
104
|
+
name: "Conversations",
|
|
105
|
+
description: "List of available channels and DMs",
|
|
106
|
+
mimeType: "application/json"
|
|
107
|
+
}
|
|
108
|
+
];
|
|
47
109
|
|
|
48
110
|
// Initialize server
|
|
49
111
|
const server = new Server(
|
|
50
112
|
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
51
|
-
{ capabilities: { tools: {} } }
|
|
113
|
+
{ capabilities: { tools: {}, prompts: {}, resources: {} } }
|
|
52
114
|
);
|
|
53
115
|
|
|
54
116
|
// Register tool list handler
|
|
@@ -56,6 +118,103 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
56
118
|
tools: TOOLS
|
|
57
119
|
}));
|
|
58
120
|
|
|
121
|
+
// Register prompts handlers
|
|
122
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
123
|
+
prompts: PROMPTS
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
127
|
+
const { name, arguments: args } = request.params;
|
|
128
|
+
|
|
129
|
+
switch (name) {
|
|
130
|
+
case "search-recent": {
|
|
131
|
+
const query = args?.query || "";
|
|
132
|
+
const oneWeekAgo = Math.floor(Date.now() / 1000) - (7 * 24 * 60 * 60);
|
|
133
|
+
return {
|
|
134
|
+
messages: [
|
|
135
|
+
{
|
|
136
|
+
role: "user",
|
|
137
|
+
content: {
|
|
138
|
+
type: "text",
|
|
139
|
+
text: `Search Slack for "${query}" from the past week. Use the slack_search_messages tool with query: "${query} after:${new Date(oneWeekAgo * 1000).toISOString().split('T')[0]}"`
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
case "summarize-channel": {
|
|
146
|
+
const channelId = args?.channel_id || "";
|
|
147
|
+
const days = parseInt(args?.days) || 7;
|
|
148
|
+
const since = Math.floor(Date.now() / 1000) - (days * 24 * 60 * 60);
|
|
149
|
+
return {
|
|
150
|
+
messages: [
|
|
151
|
+
{
|
|
152
|
+
role: "user",
|
|
153
|
+
content: {
|
|
154
|
+
type: "text",
|
|
155
|
+
text: `Get the last ${days} days of messages from channel ${channelId} and provide a summary. Use slack_conversations_history with channel_id: "${channelId}" and oldest: "${since}"`
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
case "find-messages-from": {
|
|
162
|
+
const username = args?.username || "";
|
|
163
|
+
return {
|
|
164
|
+
messages: [
|
|
165
|
+
{
|
|
166
|
+
role: "user",
|
|
167
|
+
content: {
|
|
168
|
+
type: "text",
|
|
169
|
+
text: `Find messages from ${username}. Use slack_search_messages with query: "from:@${username}"`
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
default:
|
|
176
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Register resources handlers
|
|
181
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
182
|
+
resources: RESOURCES
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
186
|
+
const { uri } = request.params;
|
|
187
|
+
|
|
188
|
+
switch (uri) {
|
|
189
|
+
case "slack://workspace/info": {
|
|
190
|
+
const result = await handleHealthCheck();
|
|
191
|
+
return {
|
|
192
|
+
contents: [
|
|
193
|
+
{
|
|
194
|
+
uri,
|
|
195
|
+
mimeType: "application/json",
|
|
196
|
+
text: result.content[0].text
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
case "slack://conversations/list": {
|
|
202
|
+
const result = await handleListConversations({ types: "im,mpim,public_channel,private_channel", limit: 50 });
|
|
203
|
+
return {
|
|
204
|
+
contents: [
|
|
205
|
+
{
|
|
206
|
+
uri,
|
|
207
|
+
mimeType: "application/json",
|
|
208
|
+
text: result.content[0].text
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
default:
|
|
214
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
59
218
|
// Register tool call handler
|
|
60
219
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
61
220
|
const { name, arguments: args } = request.params;
|
|
@@ -149,3 +308,11 @@ main().catch(error => {
|
|
|
149
308
|
console.error("Fatal error:", error);
|
|
150
309
|
process.exit(1);
|
|
151
310
|
});
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Smithery sandbox server for capability scanning
|
|
314
|
+
* Returns a server instance with mock config for tool discovery
|
|
315
|
+
*/
|
|
316
|
+
export function createSandboxServer() {
|
|
317
|
+
return server;
|
|
318
|
+
}
|
package/src/web-server.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Exposes Slack MCP tools as REST endpoints for browser access.
|
|
6
6
|
* Run alongside or instead of the MCP server for web-based access.
|
|
7
7
|
*
|
|
8
|
-
* @version 1.2.
|
|
8
|
+
* @version 1.2.1
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import express from "express";
|
|
@@ -116,7 +116,7 @@ function extractContent(result) {
|
|
|
116
116
|
app.get("/", (req, res) => {
|
|
117
117
|
res.json({
|
|
118
118
|
name: "Slack Web API Server",
|
|
119
|
-
version: "1.2.
|
|
119
|
+
version: "1.2.1",
|
|
120
120
|
status: "running",
|
|
121
121
|
endpoints: [
|
|
122
122
|
"GET /health",
|
|
@@ -295,7 +295,7 @@ async function main() {
|
|
|
295
295
|
app.listen(PORT, '127.0.0.1', () => {
|
|
296
296
|
// Print to stderr to keep logs clean (stdout reserved for JSON in some setups)
|
|
297
297
|
console.error(`\n${"═".repeat(60)}`);
|
|
298
|
-
console.error(` Slack Web API Server v1.2.
|
|
298
|
+
console.error(` Slack Web API Server v1.2.1`);
|
|
299
299
|
console.error(`${"═".repeat(60)}`);
|
|
300
300
|
console.error(`\n Dashboard: http://localhost:${PORT}/?key=${API_KEY}`);
|
|
301
301
|
console.error(`\n API Key: ${API_KEY}`);
|
|
Binary file
|