@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026
3
+ Copyright (c) 2026 jtalk22
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,18 +1,38 @@
1
- # Slack MCP Server
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
- *Full workspace access via local session mirroring. DMs, threads, and history—no admin approval required.*
5
+ <h1 align="center">Slack MCP Server</h1>
4
6
 
5
- [![Live Demo](https://img.shields.io/badge/LIVE%20DEMO-Try%20It%20Now-00C853?style=for-the-badge&logo=slack&logoColor=white)](https://jtalk22.github.io/slack-mcp-server/public/demo.html)
6
- [![Claude Demo](https://img.shields.io/badge/CLAUDE%20DEMO-See%20MCP%20Tools-da7756?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTAgMThjLTQuNDEgMC04LTMuNTktOC04czMuNTktOCA4LTggOCAzLjU5IDggOC0zLjU5IDgtOCA4eiIvPjwvc3ZnPg==&logoColor=white)](https://jtalk22.github.io/slack-mcp-server/public/demo-claude.html)
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
- [![npm](https://img.shields.io/npm/v/@jtalk22/slack-mcp?color=blue&label=npm)](https://www.npmjs.com/package/@jtalk22/slack-mcp)
9
- [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue)](https://github.com/jtalk22/slack-mcp-server/pkgs/container/slack-mcp-server)
10
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
- [![Node.js](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg)](https://nodejs.org/)
12
- [![GitHub Sponsors](https://img.shields.io/github/sponsors/jtalk22?label=Sponsor&logo=github)](https://github.com/sponsors/jtalk22)
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
- <img src="https://assets-worker.james-20a.workers.dev/projects/slack-mcp-server/demo-claude.gif" alt="Claude Desktop using Slack MCP tools" width="800">
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
  ![Slack MCP Server Web UI](docs/images/demo-main.png)
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
- ```mermaid
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
+ ![Session Mirroring Flow](docs/images/diagram-session-flow.svg)
61
57
 
62
58
  ### Why Not OAuth?
63
59
 
64
- ```mermaid
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
+ ![OAuth vs Session Mirroring](docs/images/diagram-oauth-comparison.svg)
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
- Since claude.ai doesn't support MCP, use the REST server:
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.7
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Prerequisites
4
4
 
5
- - Node.js 18+
5
+ - Node.js 20+
6
6
  - Google Chrome (for token extraction)
7
7
  - macOS (for Keychain storage - other platforms use file storage only)
8
8
 
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>
@@ -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
- "Authorization": `Bearer ${creds.token}`,
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.0",
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.25.2",
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>
@@ -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);
@@ -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.0";
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
- try {
73
- // Temporarily set env vars for validation
74
- const oldToken = process.env.SLACK_TOKEN;
75
- const oldCookie = process.env.SLACK_COOKIE;
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
- // Restore env vars
82
- if (oldToken) process.env.SLACK_TOKEN = oldToken;
83
- else delete process.env.SLACK_TOKEN;
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(" Copy the token (starts with ${colors.cyan}xoxc-${colors.reset})");
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(" Find the '${colors.cyan}d${colors.reset}' cookie and copy its value");
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
+ });
@@ -30,7 +30,7 @@ import {
30
30
  } from "../lib/handlers.js";
31
31
 
32
32
  const SERVER_NAME = "slack-mcp-server";
33
- const SERVER_VERSION = "1.2.0";
33
+ const SERVER_VERSION = "1.2.1";
34
34
  const PORT = process.env.PORT || 3000;
35
35
 
36
36
  // Create MCP server
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.0
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.0";
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.0
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.0",
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.0`);
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