@jtalk22/slack-mcp 1.2.1 → 1.2.3

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.
@@ -12,19 +12,19 @@
12
12
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
13
  <meta name="author" content="@jtalk22">
14
14
  <title>Slack MCP Server - Claude Desktop Demo</title>
15
- <meta name="description" content="See how Claude uses Slack MCP tools to access your workspace - DMs, channels, search, and more.">
15
+ <meta name="description" content="Session-based Slack access for Claude with your existing workspace permissions. Demo workflows for DMs, channels, search, and threads.">
16
16
 
17
17
  <!-- Open Graph -->
18
- <meta property="og:title" content="Slack MCP Server - Claude Desktop Demo">
19
- <meta property="og:description" content="Interactive demo showing Claude Desktop using MCP tools to access Slack - search messages, read threads, send updates.">
18
+ <meta property="og:title" content="Slack MCP Server - Session-Based Slack Access Demo">
19
+ <meta property="og:description" content="Session-based Slack access for Claude with your existing workspace permissions. Search, thread, and messaging workflows.">
20
20
  <meta property="og:type" content="website">
21
21
  <meta property="og:url" content="https://jtalk22.github.io/slack-mcp-server/public/demo-claude.html">
22
22
  <meta property="og:image" content="https://assets-worker.james-20a.workers.dev/projects/slack-mcp-server/demo-claude.gif">
23
23
 
24
24
  <!-- Twitter Card -->
25
25
  <meta name="twitter:card" content="summary_large_image">
26
- <meta name="twitter:title" content="Slack MCP Server - Claude Desktop Demo">
27
- <meta name="twitter:description" content="Interactive demo showing Claude using MCP tools to access your Slack workspace.">
26
+ <meta name="twitter:title" content="Slack MCP Server - Session-Based Slack Access Demo">
27
+ <meta name="twitter:description" content="Session-based Slack access for Claude with your existing workspace permissions. Search, thread, and messaging workflows.">
28
28
  <meta name="twitter:image" content="https://assets-worker.james-20a.workers.dev/projects/slack-mcp-server/demo-claude.gif">
29
29
 
30
30
  <!-- Theme -->
@@ -124,6 +124,43 @@
124
124
  color: var(--text-secondary);
125
125
  font-size: 16px;
126
126
  }
127
+ .cta-strip {
128
+ width: 100%;
129
+ max-width: 960px;
130
+ margin-bottom: 16px;
131
+ background: rgba(15, 52, 96, 0.72);
132
+ border: 1px solid rgba(255, 255, 255, 0.15);
133
+ border-radius: 12px;
134
+ padding: 10px 14px;
135
+ display: flex;
136
+ justify-content: space-between;
137
+ align-items: center;
138
+ gap: 10px;
139
+ flex-wrap: wrap;
140
+ font-size: 13px;
141
+ }
142
+ .cta-strip .links {
143
+ display: flex;
144
+ gap: 8px;
145
+ flex-wrap: wrap;
146
+ }
147
+ .cta-strip .links a {
148
+ color: #d8efff;
149
+ text-decoration: none;
150
+ border: 1px solid rgba(255, 255, 255, 0.25);
151
+ border-radius: 999px;
152
+ padding: 4px 8px;
153
+ }
154
+ .cta-strip .links a:hover {
155
+ background: rgba(255, 255, 255, 0.08);
156
+ }
157
+ .cta-strip .note {
158
+ color: rgba(255, 255, 255, 0.78);
159
+ }
160
+ .cta-strip .note a {
161
+ color: #9ee7ff;
162
+ text-decoration: underline;
163
+ }
127
164
 
128
165
  .badge {
129
166
  display: inline-flex;
@@ -1111,10 +1148,20 @@
1111
1148
  </style>
1112
1149
  </head>
1113
1150
  <body>
1151
+ <div class="cta-strip">
1152
+ <div class="links">
1153
+ <a href="https://www.npmjs.com/package/@jtalk22/slack-mcp" target="_blank" rel="noopener noreferrer">Install</a>
1154
+ <a href="https://github.com/jtalk22/slack-mcp-server/blob/main/docs/SETUP.md" target="_blank" rel="noopener noreferrer">Setup Guide</a>
1155
+ <a href="https://github.com/jtalk22/slack-mcp-server#30-second-proof" target="_blank" rel="noopener noreferrer">30-Second Proof</a>
1156
+ </div>
1157
+ <div class="note">
1158
+ Free local-first path with optional team rollout guidance in <a href="https://github.com/jtalk22/slack-mcp-server/blob/main/docs/DEPLOYMENT-MODES.md" target="_blank" rel="noopener noreferrer">Deployment Modes</a>.
1159
+ </div>
1160
+ </div>
1114
1161
  <header class="page-header">
1115
1162
  <h1>
1116
1163
  <span>Slack MCP Server</span>
1117
- <span class="badge">🔧 MCP Demo v1.2</span>
1164
+ <span class="badge">🔧 MCP Demo v1.2.3</span>
1118
1165
  </h1>
1119
1166
  <p>See how Claude uses MCP tools to access your Slack workspace</p>
1120
1167
  </header>
@@ -1186,7 +1233,7 @@
1186
1233
  <div class="title-logo">💬</div>
1187
1234
  <h1>Slack MCP Server</h1>
1188
1235
  <p class="title-tagline">Full Slack access for Claude Desktop</p>
1189
- <p class="title-version">v1.2 • @jtalk22</p>
1236
+ <p class="title-version">v1.2.3 • @jtalk22</p>
1190
1237
  </div>
1191
1238
 
1192
1239
  <!-- Scenario Caption Overlay -->
@@ -1196,9 +1243,9 @@
1196
1243
  <div class="closing-card" id="closingCard">
1197
1244
  <div class="closing-check">✅</div>
1198
1245
  <h2>Demo Complete</h2>
1199
- <p class="closing-cta">Full Slack access. No OAuth. No admin approval.</p>
1246
+ <p class="closing-cta">Session-based Slack access aligned to your existing workspace permissions.</p>
1200
1247
  <div class="closing-links">
1201
- <code>npm install -g @jtalk22/slack-mcp</code>
1248
+ <code>npx -y @jtalk22/slack-mcp --setup</code>
1202
1249
  </div>
1203
1250
  <p class="closing-github">github.com/jtalk22/slack-mcp-server</p>
1204
1251
  <span class="easter-egg">ê</span>
@@ -4,6 +4,16 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Slack MCP Server Demo</title>
7
+ <meta name="description" content="Session-based Slack access for Claude with your existing workspace permissions. Video demo for DMs, channels, search, and threads.">
8
+ <meta property="og:type" content="website">
9
+ <meta property="og:title" content="Slack MCP Server - Session-Based Slack Access Demo">
10
+ <meta property="og:description" content="Session-based Slack access for Claude with your existing workspace permissions. Video walkthrough for Slack workflows.">
11
+ <meta property="og:url" content="https://jtalk22.github.io/slack-mcp-server/public/demo-video.html">
12
+ <meta property="og:image" content="https://raw.githubusercontent.com/jtalk22/slack-mcp-server/main/docs/images/demo-poster.png">
13
+ <meta name="twitter:card" content="summary_large_image">
14
+ <meta name="twitter:title" content="Slack MCP Server - Session-Based Slack Access Demo">
15
+ <meta name="twitter:description" content="Session-based Slack access for Claude with your existing workspace permissions. Video walkthrough for Slack workflows.">
16
+ <meta name="twitter:image" content="https://raw.githubusercontent.com/jtalk22/slack-mcp-server/main/docs/images/demo-poster.png">
7
17
  <style>
8
18
  * {
9
19
  margin: 0;
@@ -37,6 +47,41 @@
37
47
  margin-bottom: 1.5rem;
38
48
  font-size: 1rem;
39
49
  }
50
+ .cta-strip {
51
+ margin: 0 auto 1rem;
52
+ background: rgba(15, 52, 96, 0.72);
53
+ border: 1px solid rgba(255, 255, 255, 0.16);
54
+ border-radius: 12px;
55
+ padding: 10px 14px;
56
+ display: flex;
57
+ justify-content: space-between;
58
+ align-items: center;
59
+ gap: 10px;
60
+ flex-wrap: wrap;
61
+ font-size: 0.8125rem;
62
+ }
63
+ .cta-strip .links {
64
+ display: flex;
65
+ gap: 8px;
66
+ flex-wrap: wrap;
67
+ }
68
+ .cta-strip .links a {
69
+ color: #d8efff;
70
+ text-decoration: none;
71
+ border: 1px solid rgba(255, 255, 255, 0.24);
72
+ border-radius: 999px;
73
+ padding: 4px 8px;
74
+ }
75
+ .cta-strip .links a:hover {
76
+ background: rgba(255, 255, 255, 0.08);
77
+ }
78
+ .cta-strip .note {
79
+ color: rgba(255, 255, 255, 0.82);
80
+ }
81
+ .cta-strip .note a {
82
+ color: #9ee7ff;
83
+ text-decoration: underline;
84
+ }
40
85
  .video-wrapper {
41
86
  position: relative;
42
87
  border-radius: 12px;
@@ -97,7 +142,17 @@
97
142
  <body>
98
143
  <div class="container">
99
144
  <h1>Slack MCP Server</h1>
100
- <p class="subtitle">Full workspace access via local session mirroring</p>
145
+ <p class="subtitle">Free local-first Slack access using your existing session permissions</p>
146
+ <div class="cta-strip">
147
+ <div class="links">
148
+ <a href="https://www.npmjs.com/package/@jtalk22/slack-mcp" target="_blank" rel="noopener noreferrer">Install</a>
149
+ <a href="https://github.com/jtalk22/slack-mcp-server/blob/main/docs/SETUP.md" target="_blank" rel="noopener noreferrer">Setup Guide</a>
150
+ <a href="https://github.com/jtalk22/slack-mcp-server#30-second-proof" target="_blank" rel="noopener noreferrer">30-Second Proof</a>
151
+ </div>
152
+ <div class="note">
153
+ Free local-first path with optional team rollout guidance in <a href="https://github.com/jtalk22/slack-mcp-server/blob/main/docs/DEPLOYMENT-MODES.md" target="_blank" rel="noopener noreferrer">Deployment Modes</a>.
154
+ </div>
155
+ </div>
101
156
 
102
157
  <div class="video-wrapper">
103
158
  <video id="demo" poster="../docs/images/demo-poster.png" playsinline>
package/public/demo.html CHANGED
@@ -8,18 +8,18 @@
8
8
  <!-- Open Graph / Social Sharing -->
9
9
  <meta property="og:type" content="website">
10
10
  <meta property="og:url" content="https://jtalk22.github.io/slack-mcp-server/public/demo.html">
11
- <meta property="og:title" content="Slack MCP Server - Direct Access for Claude">
12
- <meta property="og:description" content="Give Claude full access to DMs and Channels without OAuth. Try the interactive simulator.">
11
+ <meta property="og:title" content="Slack MCP Server - Session-Based Slack Access Demo">
12
+ <meta property="og:description" content="Session-based Slack access for Claude with your existing workspace permissions. DMs, channels, search, and threads.">
13
13
  <meta property="og:image" content="https://raw.githubusercontent.com/jtalk22/slack-mcp-server/main/docs/images/demo-main.png">
14
14
 
15
15
  <!-- Twitter Card -->
16
16
  <meta name="twitter:card" content="summary_large_image">
17
- <meta name="twitter:title" content="Slack MCP Server - Direct Access for Claude">
18
- <meta name="twitter:description" content="Give Claude full access to DMs and Channels without OAuth. Try the interactive simulator.">
17
+ <meta name="twitter:title" content="Slack MCP Server - Session-Based Slack Access Demo">
18
+ <meta name="twitter:description" content="Session-based Slack access for Claude with your existing workspace permissions. DMs, channels, search, and threads.">
19
19
  <meta name="twitter:image" content="https://raw.githubusercontent.com/jtalk22/slack-mcp-server/main/docs/images/demo-main.png">
20
20
 
21
21
  <!-- SEO -->
22
- <meta name="description" content="Full Slack access for Claude - DMs, channels, search. No OAuth. No admin approval. Interactive demo simulator.">
22
+ <meta name="description" content="Session-based Slack access for Claude with your existing workspace permissions. Interactive demo for DMs, channels, search, and thread workflows.">
23
23
  <link rel="preconnect" href="https://fonts.googleapis.com">
24
24
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
25
25
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
@@ -74,12 +74,45 @@
74
74
  color: white;
75
75
  text-decoration: underline;
76
76
  }
77
+ .cta-strip {
78
+ background: rgba(15, 52, 96, 0.9);
79
+ border-bottom: 1px solid rgba(255, 255, 255, 0.12);
80
+ padding: 10px 16px;
81
+ display: flex;
82
+ justify-content: space-between;
83
+ align-items: center;
84
+ gap: 12px;
85
+ flex-wrap: wrap;
86
+ font-size: 13px;
87
+ }
88
+ .cta-links {
89
+ display: flex;
90
+ gap: 10px;
91
+ flex-wrap: wrap;
92
+ }
93
+ .cta-links a {
94
+ color: #d8efff;
95
+ text-decoration: none;
96
+ padding: 4px 8px;
97
+ border: 1px solid rgba(255, 255, 255, 0.25);
98
+ border-radius: 999px;
99
+ }
100
+ .cta-links a:hover {
101
+ background: rgba(255, 255, 255, 0.1);
102
+ }
103
+ .cta-note {
104
+ color: rgba(255, 255, 255, 0.75);
105
+ }
106
+ .cta-note a {
107
+ color: #9ee7ff;
108
+ text-decoration: underline;
109
+ }
77
110
 
78
111
  /* Main Layout */
79
112
  .split-container {
80
113
  display: grid;
81
114
  grid-template-columns: 420px 1fr;
82
- height: calc(100vh - 44px);
115
+ height: calc(100vh - 88px);
83
116
  overflow: hidden;
84
117
  }
85
118
 
@@ -600,6 +633,16 @@
600
633
  STATIC PREVIEW - No real data. Run <code>npm run web</code> for live dashboard.
601
634
  <a href="https://github.com/jtalk22/slack-mcp-server">View on GitHub</a>
602
635
  </div>
636
+ <div class="cta-strip">
637
+ <div class="cta-links">
638
+ <a href="https://www.npmjs.com/package/@jtalk22/slack-mcp" target="_blank" rel="noopener noreferrer">Install</a>
639
+ <a href="https://github.com/jtalk22/slack-mcp-server/blob/main/docs/SETUP.md" target="_blank" rel="noopener noreferrer">Setup Guide</a>
640
+ <a href="https://github.com/jtalk22/slack-mcp-server#30-second-proof" target="_blank" rel="noopener noreferrer">30-Second Proof</a>
641
+ </div>
642
+ <div class="cta-note">
643
+ Free local-first path with optional team rollout guidance in <a href="https://github.com/jtalk22/slack-mcp-server/blob/main/docs/DEPLOYMENT-MODES.md" target="_blank" rel="noopener noreferrer">Deployment Modes</a>.
644
+ </div>
645
+ </div>
603
646
 
604
647
  <div class="split-container">
605
648
  <!-- LEFT: Claude Chat Panel -->
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXPECTED_NAME="${EXPECTED_GIT_NAME:-jtalk22}"
5
+ EXPECTED_EMAIL="${EXPECTED_GIT_EMAIL:-james@revasser.nyc}"
6
+ BANNED_REGEX='(?i)(co-authored-by|generated with|\bclaude\b|\bgpt\b|\bcopilot\b|\bgemini\b|\bai\b)'
7
+
8
+ die() {
9
+ echo "ERROR: $*" >&2
10
+ exit 1
11
+ }
12
+
13
+ contains_banned_markers() {
14
+ local text="$1"
15
+ if command -v rg >/dev/null 2>&1; then
16
+ rg -Niq "$BANNED_REGEX" <<<"$text"
17
+ else
18
+ grep -Eiq '(Co-authored-by|Generated with|Claude|GPT|Copilot|Gemini)' <<<"$text" \
19
+ || grep -Eiq '(^|[^[:alnum:]_])[Aa][Ii]([^[:alnum:]_]|$)' <<<"$text"
20
+ fi
21
+ }
22
+
23
+ if [[ "${SKIP_LOCAL_CONFIG_CHECK:-0}" != "1" ]]; then
24
+ local_name="$(git config --get user.name || true)"
25
+ local_email="$(git config --get user.email || true)"
26
+
27
+ [[ -n "$local_name" ]] || die "Missing repo-local git user.name"
28
+ [[ -n "$local_email" ]] || die "Missing repo-local git user.email"
29
+
30
+ [[ "$local_name" == "$EXPECTED_NAME" ]] \
31
+ || die "Repo-local user.name is '$local_name' (expected '$EXPECTED_NAME')"
32
+ [[ "$local_email" == "$EXPECTED_EMAIL" ]] \
33
+ || die "Repo-local user.email is '$local_email' (expected '$EXPECTED_EMAIL')"
34
+ fi
35
+
36
+ default_range="HEAD"
37
+ if git rev-parse --verify origin/main >/dev/null 2>&1; then
38
+ default_range="origin/main..HEAD"
39
+ fi
40
+
41
+ range="${1:-${GIT_CHECK_RANGE:-$default_range}}"
42
+
43
+ git rev-list --count "$range" >/dev/null 2>&1 || die "Invalid commit range: $range"
44
+ commit_count="$(git rev-list --count "$range")"
45
+
46
+ if [[ "$commit_count" -eq 0 ]]; then
47
+ echo "No commits to validate in range '$range'."
48
+ exit 0
49
+ fi
50
+
51
+ errors=0
52
+
53
+ while IFS= read -r sha; do
54
+ author_name="$(git show -s --format=%an "$sha")"
55
+ author_email="$(git show -s --format=%ae "$sha")"
56
+ committer_name="$(git show -s --format=%cn "$sha")"
57
+ committer_email="$(git show -s --format=%ce "$sha")"
58
+ body="$(git show -s --format=%B "$sha")"
59
+
60
+ if [[ "$author_name" != "$EXPECTED_NAME" || "$author_email" != "$EXPECTED_EMAIL" ]]; then
61
+ echo "Commit $sha has author '$author_name <$author_email>' (expected '$EXPECTED_NAME <$EXPECTED_EMAIL>')." >&2
62
+ errors=1
63
+ fi
64
+
65
+ if [[ "$committer_name" != "$EXPECTED_NAME" || "$committer_email" != "$EXPECTED_EMAIL" ]]; then
66
+ echo "Commit $sha has committer '$committer_name <$committer_email>' (expected '$EXPECTED_NAME <$EXPECTED_EMAIL>')." >&2
67
+ errors=1
68
+ fi
69
+
70
+ if contains_banned_markers "$body"; then
71
+ echo "Commit $sha contains disallowed attribution markers in commit message." >&2
72
+ errors=1
73
+ fi
74
+ done < <(git rev-list "$range")
75
+
76
+ if [[ "$errors" -ne 0 ]]; then
77
+ exit 1
78
+ fi
79
+
80
+ echo "Owner-only attribution check passed for $commit_count commit(s) in '$range'."
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ repo_root="$(git rev-parse --show-toplevel)"
5
+ cd "$repo_root"
6
+
7
+ [[ -d .githooks ]] || {
8
+ echo "Missing .githooks directory." >&2
9
+ exit 1
10
+ }
11
+
12
+ git config core.hooksPath .githooks
13
+ find .githooks -maxdepth 1 -type f -exec chmod +x {} +
14
+
15
+ echo "Configured git hooks path: .githooks"
@@ -12,11 +12,19 @@
12
12
 
13
13
  import { platform } from "os";
14
14
  import * as readline from "readline";
15
- import { loadTokens, saveTokens, extractFromChrome, isAutoRefreshAvailable, TOKEN_FILE } from "../lib/token-store.js";
16
- import { slackAPI } from "../lib/slack-client.js";
15
+ import {
16
+ loadTokens,
17
+ saveTokens,
18
+ extractFromChrome,
19
+ isAutoRefreshAvailable,
20
+ TOKEN_FILE,
21
+ getFromFile,
22
+ getFromKeychain,
23
+ } from "../lib/token-store.js";
17
24
 
18
25
  const IS_MACOS = platform() === 'darwin';
19
- const VERSION = "1.2.1";
26
+ const VERSION = "1.2.3";
27
+ const MIN_NODE_MAJOR = 20;
20
28
 
21
29
  // ANSI colors
22
30
  const colors = {
@@ -69,27 +77,25 @@ async function pressEnterToContinue(rl) {
69
77
  }
70
78
 
71
79
  async function validateTokens(token, cookie) {
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;
76
-
77
- // Temporarily set env vars for validation
78
- process.env.SLACK_TOKEN = token;
79
- process.env.SLACK_COOKIE = cookie;
80
-
81
80
  try {
82
- const result = await slackAPI("auth.test", {});
83
- return { valid: true, user: result.user, team: result.team };
81
+ const response = await fetch("https://slack.com/api/auth.test", {
82
+ method: "POST",
83
+ headers: {
84
+ "Authorization": `Bearer ${token}`,
85
+ "Cookie": `d=${cookie}`,
86
+ "Content-Type": "application/json; charset=utf-8",
87
+ },
88
+ body: JSON.stringify({}),
89
+ });
90
+
91
+ const result = await response.json();
92
+ if (!result.ok) {
93
+ return { valid: false, error: result.error || "auth.test_failed" };
94
+ }
95
+
96
+ return { valid: true, user: result.user, team: result.team, userId: result.user_id };
84
97
  } catch (e) {
85
98
  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;
93
99
  }
94
100
  }
95
101
 
@@ -232,7 +238,7 @@ async function showStatus() {
232
238
  if (!creds) {
233
239
  error("No tokens found");
234
240
  print();
235
- print("Run setup wizard: npx @jtalk22/slack-mcp --setup");
241
+ print("Run setup wizard: npx -y @jtalk22/slack-mcp --setup");
236
242
  process.exit(1);
237
243
  }
238
244
 
@@ -243,23 +249,118 @@ async function showStatus() {
243
249
  }
244
250
  print();
245
251
 
246
- try {
247
- // Need to set env vars for slackAPI to work
248
- process.env.SLACK_TOKEN = creds.token;
249
- process.env.SLACK_COOKIE = creds.cookie;
250
-
251
- const result = await slackAPI("auth.test", {});
252
- success("Status: VALID");
253
- print(`User: ${result.user}`);
254
- print(`Team: ${result.team}`);
255
- print(`User ID: ${result.user_id}`);
256
- } catch (e) {
252
+ const result = await validateTokens(creds.token, creds.cookie);
253
+ if (!result.valid) {
257
254
  error("Status: INVALID");
258
- print(`Error: ${e.message}`);
255
+ print(`Error: ${result.error}`);
256
+ print();
257
+ print("Run setup wizard to refresh: npx -y @jtalk22/slack-mcp --setup");
258
+ process.exit(1);
259
+ }
260
+
261
+ success("Status: VALID");
262
+ print(`User: ${result.user}`);
263
+ print(`Team: ${result.team}`);
264
+ print(`User ID: ${result.userId}`);
265
+ }
266
+
267
+ function getDoctorCredentials() {
268
+ if (process.env.SLACK_TOKEN && process.env.SLACK_COOKIE) {
269
+ return { token: process.env.SLACK_TOKEN, cookie: process.env.SLACK_COOKIE, source: "environment" };
270
+ }
271
+
272
+ const fileTokens = getFromFile();
273
+ if (fileTokens?.token && fileTokens?.cookie) {
274
+ return {
275
+ token: fileTokens.token,
276
+ cookie: fileTokens.cookie,
277
+ source: "file",
278
+ path: TOKEN_FILE,
279
+ updatedAt: fileTokens.updatedAt,
280
+ };
281
+ }
282
+
283
+ if (IS_MACOS) {
284
+ const keychainToken = getFromKeychain("token");
285
+ const keychainCookie = getFromKeychain("cookie");
286
+ if (keychainToken && keychainCookie) {
287
+ return { token: keychainToken, cookie: keychainCookie, source: "keychain" };
288
+ }
289
+ }
290
+
291
+ return null;
292
+ }
293
+
294
+ function classifyAuthError(rawError) {
295
+ const msg = String(rawError || "").toLowerCase();
296
+ if (
297
+ msg.includes("invalid_auth") ||
298
+ msg.includes("token_expired") ||
299
+ msg.includes("not_authed") ||
300
+ msg.includes("account_inactive")
301
+ ) {
302
+ return 2;
303
+ }
304
+ return 3;
305
+ }
306
+
307
+ function parseNodeMajor() {
308
+ return Number.parseInt(process.versions.node.split(".")[0], 10);
309
+ }
310
+
311
+ async function runDoctor() {
312
+ print(`${colors.bold}slack-mcp-server doctor${colors.reset}`);
313
+ print();
314
+
315
+ const nodeMajor = parseNodeMajor();
316
+ if (Number.isNaN(nodeMajor) || nodeMajor < MIN_NODE_MAJOR) {
317
+ error(`Node.js ${process.versions.node} detected (requires Node ${MIN_NODE_MAJOR}+)`);
259
318
  print();
260
- print("Run setup wizard to refresh: npx @jtalk22/slack-mcp --setup");
319
+ print("Next action:");
320
+ print(` npx -y @jtalk22/slack-mcp --doctor # rerun after upgrading Node ${MIN_NODE_MAJOR}+`);
321
+ process.exit(3);
322
+ }
323
+ success(`Node.js ${process.versions.node} (supported)`);
324
+
325
+ const creds = getDoctorCredentials();
326
+ if (!creds) {
327
+ error("Credentials: not found");
328
+ print();
329
+ print("Next action:");
330
+ print(" npx -y @jtalk22/slack-mcp --setup");
261
331
  process.exit(1);
262
332
  }
333
+
334
+ success(`Credentials loaded from: ${creds.source}`);
335
+ if (creds.path) {
336
+ print(`Path: ${creds.path}`);
337
+ }
338
+ if (creds.updatedAt) {
339
+ print(`Last updated: ${creds.updatedAt}`);
340
+ }
341
+
342
+ print();
343
+ print("Validating Slack auth...");
344
+ const validation = await validateTokens(creds.token, creds.cookie);
345
+ if (!validation.valid) {
346
+ const exitCode = classifyAuthError(validation.error);
347
+ error(`Slack auth failed: ${validation.error}`);
348
+ print();
349
+ print("Next action:");
350
+ if (exitCode === 2) {
351
+ print(" npx -y @jtalk22/slack-mcp --setup");
352
+ } else {
353
+ print(" Check network connectivity and retry:");
354
+ print(" npx -y @jtalk22/slack-mcp --doctor");
355
+ }
356
+ process.exit(exitCode);
357
+ }
358
+
359
+ success(`Slack auth valid for ${validation.user} @ ${validation.team}`);
360
+ print();
361
+ print("Ready. Next command:");
362
+ print(" npx -y @jtalk22/slack-mcp");
363
+ process.exit(0);
263
364
  }
264
365
 
265
366
  async function showHelp() {
@@ -268,11 +369,12 @@ async function showHelp() {
268
369
  print("Full Slack access for Claude via MCP. Session mirroring bypasses OAuth.");
269
370
  print();
270
371
  print(`${colors.bold}Usage:${colors.reset}`);
271
- print(" npx @jtalk22/slack-mcp Start MCP server (stdio)");
272
- print(" npx @jtalk22/slack-mcp --setup Interactive token setup wizard");
273
- print(" npx @jtalk22/slack-mcp --status Check token health");
274
- print(" npx @jtalk22/slack-mcp --version Print version");
275
- print(" npx @jtalk22/slack-mcp --help Show this help");
372
+ print(" npx -y @jtalk22/slack-mcp Start MCP server (stdio)");
373
+ print(" npx -y @jtalk22/slack-mcp --setup Interactive token setup wizard");
374
+ print(" npx -y @jtalk22/slack-mcp --status Check token health");
375
+ print(" npx -y @jtalk22/slack-mcp --doctor Run runtime and auth diagnostics");
376
+ print(" npx -y @jtalk22/slack-mcp --version Print version");
377
+ print(" npx -y @jtalk22/slack-mcp --help Show this help");
276
378
  print();
277
379
  print(`${colors.bold}npm scripts:${colors.reset}`);
278
380
  print(" npm start Start MCP server");
@@ -296,6 +398,10 @@ async function main() {
296
398
  case 'status':
297
399
  await showStatus();
298
400
  return;
401
+ case '--doctor':
402
+ case 'doctor':
403
+ await runDoctor();
404
+ return;
299
405
  case '--version':
300
406
  case '-v':
301
407
  print(`slack-mcp-server v${VERSION}`);
@@ -345,8 +451,8 @@ async function main() {
345
451
  print(`${colors.green}${colors.bold}Setup complete!${colors.reset}`);
346
452
  print();
347
453
  print("Next steps:");
348
- print(" • Verify: npx @jtalk22/slack-mcp --status");
349
- print(" • Start server: npx @jtalk22/slack-mcp");
454
+ print(" • Verify: npx -y @jtalk22/slack-mcp --status");
455
+ print(" • Start server: npx -y @jtalk22/slack-mcp");
350
456
  print(" • Or add to Claude Desktop config");
351
457
  } else {
352
458
  print(`${colors.red}Setup failed.${colors.reset} See errors above.`);