@jtalk22/slack-mcp 1.2.2 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -7
- package/docs/API.md +11 -4
- package/docs/COMMUNICATION-STYLE.md +15 -7
- package/docs/COMPATIBILITY.md +19 -0
- package/docs/HN-LAUNCH.md +63 -0
- package/docs/INDEX.md +31 -0
- package/docs/RELEASE-HEALTH.md +66 -0
- package/docs/SETUP.md +22 -2
- package/docs/TROUBLESHOOTING.md +32 -3
- package/docs/WEB-API.md +12 -1
- package/docs/release-health/2026-02-25.md +33 -0
- package/docs/release-health/2026-02-26.md +33 -0
- package/docs/release-health/24h-delta.md +21 -0
- package/docs/release-health/24h-end.md +33 -0
- package/docs/release-health/24h-start.md +33 -0
- package/docs/release-health/latest.md +33 -0
- package/lib/handlers.js +113 -85
- package/lib/slack-client.js +20 -16
- package/lib/token-store.js +103 -30
- package/package.json +12 -33
- package/public/demo-claude.html +10 -10
- package/public/demo-video.html +9 -9
- package/public/demo.html +8 -8
- package/scripts/build-release-health-delta.js +201 -0
- package/scripts/check-public-language.sh +25 -0
- package/scripts/collect-release-health.js +150 -0
- package/scripts/setup-wizard.js +161 -39
- package/scripts/token-cli.js +6 -4
- package/scripts/verify-install-flow.js +157 -0
- package/src/cli.js +1 -0
- package/src/server-http.js +1 -1
- package/src/server.js +5 -5
- package/src/web-server.js +6 -6
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Release Health Snapshot
|
|
2
|
+
|
|
3
|
+
- Generated: 2026-02-26T09:32:04.228Z
|
|
4
|
+
- Repo: `jtalk22/slack-mcp-server`
|
|
5
|
+
- Package: `@jtalk22/slack-mcp`
|
|
6
|
+
|
|
7
|
+
## Install Signals
|
|
8
|
+
|
|
9
|
+
- npm downloads (last week): 532
|
|
10
|
+
- npm downloads (last month): 1011
|
|
11
|
+
- npm latest version: 1.2.3
|
|
12
|
+
|
|
13
|
+
## GitHub Reach
|
|
14
|
+
|
|
15
|
+
- stars: 13
|
|
16
|
+
- forks: 8
|
|
17
|
+
- open issues: 0
|
|
18
|
+
- 14d views: 498
|
|
19
|
+
- 14d unique visitors: 263
|
|
20
|
+
- 14d clones: 338
|
|
21
|
+
- 14d unique cloners: 113
|
|
22
|
+
- deployment-intake submissions (all-time): 1
|
|
23
|
+
|
|
24
|
+
## 14-Day Reliability Targets (v1.2.3 Cycle)
|
|
25
|
+
|
|
26
|
+
- weekly downloads: >= 180
|
|
27
|
+
- qualified deployment-intake submissions: >= 2
|
|
28
|
+
- maintainer support load: <= 2 hours/week
|
|
29
|
+
|
|
30
|
+
## Notes
|
|
31
|
+
|
|
32
|
+
- Update this snapshot daily during active release windows, then weekly.
|
|
33
|
+
- Track deployment-intake quality and support load manually in issue notes.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Release Health Snapshot
|
|
2
|
+
|
|
3
|
+
- Generated: 2026-02-26T09:32:53.328Z
|
|
4
|
+
- Repo: `jtalk22/slack-mcp-server`
|
|
5
|
+
- Package: `@jtalk22/slack-mcp`
|
|
6
|
+
|
|
7
|
+
## Install Signals
|
|
8
|
+
|
|
9
|
+
- npm downloads (last week): 532
|
|
10
|
+
- npm downloads (last month): 1011
|
|
11
|
+
- npm latest version: 1.2.3
|
|
12
|
+
|
|
13
|
+
## GitHub Reach
|
|
14
|
+
|
|
15
|
+
- stars: 13
|
|
16
|
+
- forks: 8
|
|
17
|
+
- open issues: 0
|
|
18
|
+
- 14d views: 498
|
|
19
|
+
- 14d unique visitors: 263
|
|
20
|
+
- 14d clones: 338
|
|
21
|
+
- 14d unique cloners: 113
|
|
22
|
+
- deployment-intake submissions (all-time): 1
|
|
23
|
+
|
|
24
|
+
## 14-Day Reliability Targets (v1.2.3 Cycle)
|
|
25
|
+
|
|
26
|
+
- weekly downloads: >= 180
|
|
27
|
+
- qualified deployment-intake submissions: >= 2
|
|
28
|
+
- maintainer support load: <= 2 hours/week
|
|
29
|
+
|
|
30
|
+
## Notes
|
|
31
|
+
|
|
32
|
+
- Update this snapshot daily during active release windows, then weekly.
|
|
33
|
+
- Track deployment-intake quality and support load manually in issue notes.
|
package/lib/handlers.js
CHANGED
|
@@ -8,7 +8,13 @@ import { writeFileSync, readFileSync, existsSync, renameSync, unlinkSync, mkdirS
|
|
|
8
8
|
import { execSync } from "child_process";
|
|
9
9
|
import { homedir, platform } from "os";
|
|
10
10
|
import { join } from "path";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
loadTokensReadOnly,
|
|
13
|
+
saveTokens,
|
|
14
|
+
extractFromChrome,
|
|
15
|
+
isAutoRefreshAvailable,
|
|
16
|
+
getLastExtractionError
|
|
17
|
+
} from "./token-store.js";
|
|
12
18
|
import { slackAPI, resolveUser, formatTimestamp, sleep, checkTokenHealth, getUserCacheStats } from "./slack-client.js";
|
|
13
19
|
|
|
14
20
|
// ============ Utilities ============
|
|
@@ -26,6 +32,16 @@ function parseBool(val) {
|
|
|
26
32
|
return false;
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
function asMcpJson(payload, isError = false) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: JSON.stringify(payload, null, 2)
|
|
40
|
+
}],
|
|
41
|
+
...(isError ? { isError: true } : {})
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
/**
|
|
30
46
|
* Atomic write to prevent file corruption from concurrent writes
|
|
31
47
|
*/
|
|
@@ -77,72 +93,75 @@ export async function handleTokenStatus() {
|
|
|
77
93
|
const health = await checkTokenHealth({ error: () => {} });
|
|
78
94
|
const cacheStats = getUserCacheStats();
|
|
79
95
|
const dmCache = loadDMCache();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
96
|
+
const tokenStatus = health.reason === 'no_tokens'
|
|
97
|
+
? "missing"
|
|
98
|
+
: health.critical
|
|
99
|
+
? "critical"
|
|
100
|
+
: health.warning
|
|
101
|
+
? "warning"
|
|
102
|
+
: "healthy";
|
|
103
|
+
|
|
104
|
+
return asMcpJson({
|
|
105
|
+
status: tokenStatus,
|
|
106
|
+
code: health.reason || (health.critical ? "token_critical" : health.warning ? "token_warning" : "ok"),
|
|
107
|
+
message: health.message,
|
|
108
|
+
next_action: health.reason === 'no_tokens' ? "Run npx -y @jtalk22/slack-mcp --setup" : null,
|
|
109
|
+
token: {
|
|
110
|
+
status: tokenStatus,
|
|
111
|
+
age_hours: health.age_hours,
|
|
112
|
+
source: health.source,
|
|
113
|
+
updated_at: health.updated_at
|
|
114
|
+
},
|
|
115
|
+
auto_refresh: {
|
|
116
|
+
enabled: isAutoRefreshAvailable(),
|
|
117
|
+
interval: "4 hours",
|
|
118
|
+
last_attempt: health.refreshed ? "just_now" : null,
|
|
119
|
+
requires: isAutoRefreshAvailable() ? "Slack tab open in Chrome" : "Not supported on this platform"
|
|
120
|
+
},
|
|
121
|
+
cache: {
|
|
122
|
+
users: cacheStats,
|
|
123
|
+
dms: {
|
|
124
|
+
count: Object.keys(dmCache.dms || {}).length,
|
|
125
|
+
age_hours: dmCache.updated ? Math.round((Date.now() - dmCache.updated) / (60 * 60 * 1000) * 10) / 10 : null
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
108
129
|
}
|
|
109
130
|
|
|
110
131
|
/**
|
|
111
132
|
* Health check handler
|
|
112
133
|
*/
|
|
113
134
|
export async function handleHealthCheck() {
|
|
114
|
-
const creds =
|
|
135
|
+
const creds = loadTokensReadOnly();
|
|
115
136
|
if (!creds) {
|
|
116
|
-
return {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
};
|
|
137
|
+
return asMcpJson({
|
|
138
|
+
status: "error",
|
|
139
|
+
code: "missing_credentials",
|
|
140
|
+
message: "No credentials found",
|
|
141
|
+
next_action: "Run npx -y @jtalk22/slack-mcp --setup"
|
|
142
|
+
}, true);
|
|
123
143
|
}
|
|
124
144
|
|
|
125
145
|
try {
|
|
126
|
-
const result = await slackAPI("auth.test", {});
|
|
127
|
-
return {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}, null, 2)
|
|
139
|
-
}]
|
|
140
|
-
};
|
|
146
|
+
const result = await slackAPI("auth.test", {}, { retryOnAuthFail: false });
|
|
147
|
+
return asMcpJson({
|
|
148
|
+
status: "ok",
|
|
149
|
+
code: "ok",
|
|
150
|
+
message: "Slack auth valid",
|
|
151
|
+
user: result.user,
|
|
152
|
+
user_id: result.user_id,
|
|
153
|
+
team: result.team,
|
|
154
|
+
team_id: result.team_id,
|
|
155
|
+
token_source: creds.source,
|
|
156
|
+
token_updated: creds.updatedAt || null
|
|
157
|
+
});
|
|
141
158
|
} catch (e) {
|
|
142
|
-
return {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
159
|
+
return asMcpJson({
|
|
160
|
+
status: "error",
|
|
161
|
+
code: "auth_failed",
|
|
162
|
+
message: e.message,
|
|
163
|
+
next_action: "Run npx -y @jtalk22/slack-mcp --setup"
|
|
164
|
+
}, true);
|
|
146
165
|
}
|
|
147
166
|
}
|
|
148
167
|
|
|
@@ -152,13 +171,12 @@ export async function handleHealthCheck() {
|
|
|
152
171
|
export async function handleRefreshTokens() {
|
|
153
172
|
// Check platform support
|
|
154
173
|
if (!isAutoRefreshAvailable()) {
|
|
155
|
-
return {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
174
|
+
return asMcpJson({
|
|
175
|
+
status: "error",
|
|
176
|
+
code: "unsupported_platform",
|
|
177
|
+
message: "Auto-refresh is only available on macOS.",
|
|
178
|
+
next_action: "Manually update ~/.slack-mcp-tokens.json with SLACK_TOKEN and SLACK_COOKIE."
|
|
179
|
+
}, true);
|
|
162
180
|
}
|
|
163
181
|
|
|
164
182
|
const chromeTokens = extractFromChrome();
|
|
@@ -166,31 +184,41 @@ export async function handleRefreshTokens() {
|
|
|
166
184
|
saveTokens(chromeTokens.token, chromeTokens.cookie);
|
|
167
185
|
try {
|
|
168
186
|
const result = await slackAPI("auth.test", {}, { retryOnAuthFail: false });
|
|
169
|
-
return {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
team: result.team
|
|
177
|
-
}, null, 2)
|
|
178
|
-
}]
|
|
179
|
-
};
|
|
187
|
+
return asMcpJson({
|
|
188
|
+
status: "ok",
|
|
189
|
+
code: "refreshed",
|
|
190
|
+
message: "Tokens refreshed from Chrome.",
|
|
191
|
+
user: result.user,
|
|
192
|
+
team: result.team
|
|
193
|
+
});
|
|
180
194
|
} catch (e) {
|
|
181
|
-
return {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
195
|
+
return asMcpJson({
|
|
196
|
+
status: "error",
|
|
197
|
+
code: "auth_failed_after_refresh",
|
|
198
|
+
message: e.message,
|
|
199
|
+
next_action: "Refresh Slack in Chrome and rerun slack_refresh_tokens."
|
|
200
|
+
}, true);
|
|
185
201
|
}
|
|
186
202
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
203
|
+
|
|
204
|
+
const extractionError = getLastExtractionError();
|
|
205
|
+
if (extractionError?.code === "apple_events_javascript_disabled") {
|
|
206
|
+
return asMcpJson({
|
|
207
|
+
status: "error",
|
|
208
|
+
code: extractionError.code,
|
|
209
|
+
message: extractionError.message,
|
|
210
|
+
detail: extractionError.detail,
|
|
211
|
+
next_action: "In Chrome: View > Developer > Allow JavaScript from Apple Events, then retry."
|
|
212
|
+
}, true);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return asMcpJson({
|
|
216
|
+
status: "error",
|
|
217
|
+
code: extractionError?.code || "chrome_extraction_failed",
|
|
218
|
+
message: extractionError?.message || "Could not extract tokens from Chrome.",
|
|
219
|
+
detail: extractionError?.detail || "Ensure Chrome is running with a logged-in Slack tab at app.slack.com.",
|
|
220
|
+
next_action: "Open Slack in Chrome and run slack_refresh_tokens again."
|
|
221
|
+
}, true);
|
|
194
222
|
}
|
|
195
223
|
|
|
196
224
|
/**
|
package/lib/slack-client.js
CHANGED
|
@@ -13,8 +13,8 @@ import { loadTokens, saveTokens, extractFromChrome } from "./token-store.js";
|
|
|
13
13
|
|
|
14
14
|
// ============ Configuration ============
|
|
15
15
|
|
|
16
|
-
const TOKEN_WARNING_AGE =
|
|
17
|
-
const TOKEN_CRITICAL_AGE =
|
|
16
|
+
const TOKEN_WARNING_AGE = 10 * 24 * 60 * 60 * 1000; // 10 days
|
|
17
|
+
const TOKEN_CRITICAL_AGE = 13 * 24 * 60 * 60 * 1000; // 13 days
|
|
18
18
|
const REFRESH_COOLDOWN = 60 * 60 * 1000; // 1 hour between refresh attempts
|
|
19
19
|
const USER_CACHE_MAX_SIZE = 500;
|
|
20
20
|
const USER_CACHE_TTL = 60 * 60 * 1000; // 1 hour
|
|
@@ -94,19 +94,21 @@ const userCache = new LRUCache(USER_CACHE_MAX_SIZE, USER_CACHE_TTL);
|
|
|
94
94
|
*/
|
|
95
95
|
export async function checkTokenHealth(logger = console) {
|
|
96
96
|
const silentLogger = { error: () => {}, warn: () => {}, log: () => {} };
|
|
97
|
-
const creds = loadTokens(false, silentLogger);
|
|
97
|
+
const creds = loadTokens(false, silentLogger, { autoExtract: false });
|
|
98
98
|
|
|
99
99
|
if (!creds) {
|
|
100
100
|
return { healthy: false, reason: 'no_tokens', message: 'No credentials found' };
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const ageHours =
|
|
103
|
+
const updatedAtMs = creds.updatedAt ? new Date(creds.updatedAt).getTime() : Number.NaN;
|
|
104
|
+
const hasKnownAge = Number.isFinite(updatedAtMs);
|
|
105
|
+
const tokenAge = hasKnownAge ? Date.now() - updatedAtMs : null;
|
|
106
|
+
const ageHours = hasKnownAge
|
|
107
|
+
? Math.round(tokenAge / (60 * 60 * 1000) * 10) / 10
|
|
108
|
+
: null;
|
|
107
109
|
|
|
108
110
|
// Attempt proactive refresh if token is getting old
|
|
109
|
-
if (tokenAge > TOKEN_WARNING_AGE && Date.now() - lastRefreshAttempt > REFRESH_COOLDOWN) {
|
|
111
|
+
if (hasKnownAge && tokenAge > TOKEN_WARNING_AGE && Date.now() - lastRefreshAttempt > REFRESH_COOLDOWN) {
|
|
110
112
|
lastRefreshAttempt = Date.now();
|
|
111
113
|
logger.error?.(`Token is ${ageHours}h old, attempting proactive refresh...`);
|
|
112
114
|
|
|
@@ -127,17 +129,19 @@ export async function checkTokenHealth(logger = console) {
|
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
return {
|
|
130
|
-
healthy: tokenAge < TOKEN_CRITICAL_AGE,
|
|
132
|
+
healthy: !hasKnownAge || tokenAge < TOKEN_CRITICAL_AGE,
|
|
131
133
|
age_hours: ageHours,
|
|
132
|
-
warning: tokenAge > TOKEN_WARNING_AGE,
|
|
133
|
-
critical: tokenAge > TOKEN_CRITICAL_AGE,
|
|
134
|
+
warning: hasKnownAge && tokenAge > TOKEN_WARNING_AGE,
|
|
135
|
+
critical: hasKnownAge && tokenAge > TOKEN_CRITICAL_AGE,
|
|
134
136
|
source: creds.source,
|
|
135
137
|
updated_at: creds.updatedAt,
|
|
136
|
-
message:
|
|
137
|
-
? 'Token
|
|
138
|
-
: tokenAge >
|
|
139
|
-
? 'Token
|
|
140
|
-
:
|
|
138
|
+
message: !hasKnownAge
|
|
139
|
+
? 'Token age unknown (missing timestamp) - auth can still be valid'
|
|
140
|
+
: tokenAge > TOKEN_CRITICAL_AGE
|
|
141
|
+
? 'Token may expire soon - open Slack in Chrome'
|
|
142
|
+
: tokenAge > TOKEN_WARNING_AGE
|
|
143
|
+
? 'Token is getting old - will auto-refresh if Slack tab is open'
|
|
144
|
+
: 'Token is healthy'
|
|
141
145
|
};
|
|
142
146
|
}
|
|
143
147
|
|
package/lib/token-store.js
CHANGED
|
@@ -21,6 +21,7 @@ const IS_MACOS = platform() === 'darwin';
|
|
|
21
21
|
|
|
22
22
|
// Refresh lock to prevent concurrent extraction attempts
|
|
23
23
|
let refreshInProgress = null;
|
|
24
|
+
let lastExtractionError = null;
|
|
24
25
|
|
|
25
26
|
// ============ Keychain Storage (macOS only) ============
|
|
26
27
|
|
|
@@ -62,7 +63,7 @@ export function getFromFile() {
|
|
|
62
63
|
return {
|
|
63
64
|
token: data.SLACK_TOKEN,
|
|
64
65
|
cookie: data.SLACK_COOKIE,
|
|
65
|
-
updatedAt: data.updated_at
|
|
66
|
+
updatedAt: data.updated_at || data.UPDATED_AT || null
|
|
66
67
|
};
|
|
67
68
|
} catch (e) {
|
|
68
69
|
return null;
|
|
@@ -110,13 +111,53 @@ const SLACK_TOKEN_PATHS = [
|
|
|
110
111
|
`window.boot_data?.api_token`,
|
|
111
112
|
];
|
|
112
113
|
|
|
114
|
+
function normalizeExtractionError(error) {
|
|
115
|
+
const raw = String(error?.message || error || "");
|
|
116
|
+
|
|
117
|
+
if (raw.includes("Executing JavaScript through AppleScript is turned off")) {
|
|
118
|
+
return {
|
|
119
|
+
code: "apple_events_javascript_disabled",
|
|
120
|
+
message: "Chrome blocked JavaScript execution from Apple Events.",
|
|
121
|
+
detail: "Enable it in Chrome: View > Developer > Allow JavaScript from Apple Events."
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (raw.includes("Application isn't running") || raw.includes("Google Chrome got an error")) {
|
|
126
|
+
return {
|
|
127
|
+
code: "chrome_not_ready",
|
|
128
|
+
message: "Chrome is not ready for token extraction.",
|
|
129
|
+
detail: "Open Google Chrome with an active Slack tab at app.slack.com."
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (raw.toLowerCase().includes("timed out")) {
|
|
134
|
+
return {
|
|
135
|
+
code: "chrome_extraction_timeout",
|
|
136
|
+
message: "Chrome token extraction timed out.",
|
|
137
|
+
detail: "Ensure Slack is open in Chrome and retry."
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
code: "chrome_extraction_failed",
|
|
143
|
+
message: "Chrome token extraction failed.",
|
|
144
|
+
detail: raw || "Unknown extraction error."
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
113
148
|
/**
|
|
114
149
|
* Extract tokens from Chrome (macOS only, uses AppleScript)
|
|
115
150
|
* Returns null on non-macOS platforms
|
|
116
151
|
*/
|
|
117
152
|
function extractFromChromeInternal() {
|
|
153
|
+
lastExtractionError = null;
|
|
118
154
|
if (!IS_MACOS) {
|
|
119
155
|
// AppleScript/osascript is macOS-only
|
|
156
|
+
lastExtractionError = {
|
|
157
|
+
code: "unsupported_platform",
|
|
158
|
+
message: "Chrome auto-extraction is only available on macOS.",
|
|
159
|
+
detail: "Use manual token setup on this platform."
|
|
160
|
+
};
|
|
120
161
|
return null;
|
|
121
162
|
}
|
|
122
163
|
|
|
@@ -138,7 +179,14 @@ function extractFromChromeInternal() {
|
|
|
138
179
|
encoding: 'utf-8', timeout: 5000
|
|
139
180
|
}).trim();
|
|
140
181
|
|
|
141
|
-
if (!cookie || !cookie.startsWith('xoxd-'))
|
|
182
|
+
if (!cookie || !cookie.startsWith('xoxd-')) {
|
|
183
|
+
lastExtractionError = {
|
|
184
|
+
code: "cookie_not_found",
|
|
185
|
+
message: "Could not extract Slack cookie from Chrome.",
|
|
186
|
+
detail: "Ensure a logged-in Slack tab is open at app.slack.com."
|
|
187
|
+
};
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
142
190
|
|
|
143
191
|
// Try multiple token extraction paths
|
|
144
192
|
const tokenPathsJS = SLACK_TOKEN_PATHS.map((path, i) =>
|
|
@@ -161,10 +209,18 @@ function extractFromChromeInternal() {
|
|
|
161
209
|
encoding: 'utf-8', timeout: 5000
|
|
162
210
|
}).trim();
|
|
163
211
|
|
|
164
|
-
if (!token || !token.startsWith('xoxc-'))
|
|
212
|
+
if (!token || !token.startsWith('xoxc-')) {
|
|
213
|
+
lastExtractionError = {
|
|
214
|
+
code: "token_not_found",
|
|
215
|
+
message: "Could not extract Slack token from Chrome.",
|
|
216
|
+
detail: "Refresh Slack in Chrome and retry extraction."
|
|
217
|
+
};
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
165
220
|
|
|
166
221
|
return { token, cookie };
|
|
167
222
|
} catch (e) {
|
|
223
|
+
lastExtractionError = normalizeExtractionError(e);
|
|
168
224
|
return null;
|
|
169
225
|
}
|
|
170
226
|
}
|
|
@@ -188,6 +244,10 @@ export function extractFromChrome() {
|
|
|
188
244
|
}
|
|
189
245
|
}
|
|
190
246
|
|
|
247
|
+
export function getLastExtractionError() {
|
|
248
|
+
return lastExtractionError;
|
|
249
|
+
}
|
|
250
|
+
|
|
191
251
|
/**
|
|
192
252
|
* Check if auto-refresh is available on this platform
|
|
193
253
|
*/
|
|
@@ -197,9 +257,8 @@ export function isAutoRefreshAvailable() {
|
|
|
197
257
|
|
|
198
258
|
// ============ Main Token Loader ============
|
|
199
259
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (!forceRefresh && process.env.SLACK_TOKEN && process.env.SLACK_COOKIE) {
|
|
260
|
+
function getStoredTokens() {
|
|
261
|
+
if (process.env.SLACK_TOKEN && process.env.SLACK_COOKIE) {
|
|
203
262
|
return {
|
|
204
263
|
token: process.env.SLACK_TOKEN,
|
|
205
264
|
cookie: process.env.SLACK_COOKIE,
|
|
@@ -207,37 +266,46 @@ export function loadTokens(forceRefresh = false, logger = console) {
|
|
|
207
266
|
};
|
|
208
267
|
}
|
|
209
268
|
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
269
|
+
const fileTokens = getFromFile();
|
|
270
|
+
if (fileTokens?.token && fileTokens?.cookie) {
|
|
271
|
+
return {
|
|
272
|
+
token: fileTokens.token,
|
|
273
|
+
cookie: fileTokens.cookie,
|
|
274
|
+
source: "file",
|
|
275
|
+
updatedAt: fileTokens.updatedAt
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const keychainToken = getFromKeychain("token");
|
|
280
|
+
const keychainCookie = getFromKeychain("cookie");
|
|
281
|
+
if (keychainToken && keychainCookie) {
|
|
282
|
+
return {
|
|
283
|
+
token: keychainToken,
|
|
284
|
+
cookie: keychainCookie,
|
|
285
|
+
source: "keychain"
|
|
286
|
+
};
|
|
221
287
|
}
|
|
222
288
|
|
|
223
|
-
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function loadTokensReadOnly() {
|
|
293
|
+
return getStoredTokens();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function loadTokens(forceRefresh = false, logger = console, options = {}) {
|
|
297
|
+
const { autoExtract = true } = options;
|
|
224
298
|
if (!forceRefresh) {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
if (keychainToken && keychainCookie) {
|
|
228
|
-
return {
|
|
229
|
-
token: keychainToken,
|
|
230
|
-
cookie: keychainCookie,
|
|
231
|
-
source: "keychain"
|
|
232
|
-
};
|
|
233
|
-
}
|
|
299
|
+
const storedTokens = getStoredTokens();
|
|
300
|
+
if (storedTokens) return storedTokens;
|
|
234
301
|
}
|
|
235
302
|
|
|
236
|
-
|
|
237
|
-
|
|
303
|
+
if (!autoExtract) return null;
|
|
304
|
+
|
|
305
|
+
logger.error?.("Attempting Chrome auto-extraction...");
|
|
238
306
|
const chromeTokens = extractFromChrome();
|
|
239
307
|
if (chromeTokens) {
|
|
240
|
-
logger.error("Successfully extracted tokens from Chrome!");
|
|
308
|
+
logger.error?.("Successfully extracted tokens from Chrome!");
|
|
241
309
|
saveTokens(chromeTokens.token, chromeTokens.cookie);
|
|
242
310
|
return {
|
|
243
311
|
token: chromeTokens.token,
|
|
@@ -246,6 +314,11 @@ export function loadTokens(forceRefresh = false, logger = console) {
|
|
|
246
314
|
};
|
|
247
315
|
}
|
|
248
316
|
|
|
317
|
+
if (lastExtractionError?.code === "apple_events_javascript_disabled") {
|
|
318
|
+
logger.error?.(lastExtractionError.message);
|
|
319
|
+
logger.error?.(lastExtractionError.detail);
|
|
320
|
+
}
|
|
321
|
+
|
|
249
322
|
return null;
|
|
250
323
|
}
|
|
251
324
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.4",
|
|
5
5
|
"description": "Session-based Slack access for Claude - DMs, channels, search, and threads. Local-first with your existing Slack session.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "src/server.js",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"tokens:auto": "node scripts/token-cli.js auto",
|
|
25
25
|
"tokens:clear": "node scripts/token-cli.js clear",
|
|
26
26
|
"screenshot": "node scripts/capture-screenshots.js",
|
|
27
|
-
"record-demo": "node scripts/record-demo.js"
|
|
27
|
+
"record-demo": "node scripts/record-demo.js",
|
|
28
|
+
"metrics:release-health": "node scripts/collect-release-health.js",
|
|
29
|
+
"metrics:release-health:delta": "node scripts/build-release-health-delta.js"
|
|
28
30
|
},
|
|
29
31
|
"keywords": [
|
|
30
32
|
"mcp",
|
|
@@ -33,50 +35,27 @@
|
|
|
33
35
|
"slack",
|
|
34
36
|
"slack-api",
|
|
35
37
|
"slack-mcp",
|
|
36
|
-
"slack-bot",
|
|
37
38
|
"slack-integration",
|
|
38
39
|
"claude",
|
|
39
40
|
"claude-desktop",
|
|
40
41
|
"claude-code",
|
|
41
|
-
"
|
|
42
|
+
"cursor",
|
|
43
|
+
"llm",
|
|
42
44
|
"ai",
|
|
43
45
|
"ai-assistant",
|
|
44
|
-
"ai-agent",
|
|
45
|
-
"llm",
|
|
46
|
-
"llm-tools",
|
|
47
|
-
"chatbot",
|
|
48
|
-
"dm",
|
|
49
|
-
"direct-messages",
|
|
50
|
-
"channels",
|
|
51
|
-
"workspace",
|
|
52
46
|
"automation",
|
|
53
47
|
"workflow-automation",
|
|
54
|
-
"browser-tokens",
|
|
55
|
-
"no-oauth",
|
|
56
|
-
"cli",
|
|
57
48
|
"developer-tools",
|
|
58
|
-
"
|
|
49
|
+
"cli",
|
|
59
50
|
"productivity",
|
|
60
51
|
"messaging",
|
|
61
|
-
"chat",
|
|
62
|
-
"model-context-protocol",
|
|
63
|
-
"chatgpt",
|
|
64
|
-
"cursor",
|
|
65
|
-
"open-source",
|
|
66
|
-
"openai",
|
|
67
|
-
"gpt",
|
|
68
|
-
"gemini",
|
|
69
|
-
"copilot",
|
|
70
|
-
"ai-tools",
|
|
71
|
-
"personal-assistant",
|
|
72
52
|
"message-search",
|
|
73
|
-
"
|
|
74
|
-
"
|
|
53
|
+
"direct-messages",
|
|
54
|
+
"channels",
|
|
55
|
+
"workspace",
|
|
56
|
+
"session-based",
|
|
57
|
+
"open-source"
|
|
75
58
|
],
|
|
76
|
-
"funding": {
|
|
77
|
-
"type": "individual",
|
|
78
|
-
"url": "https://github.com/sponsors/jtalk22"
|
|
79
|
-
},
|
|
80
59
|
"author": "jtalk22",
|
|
81
60
|
"license": "MIT",
|
|
82
61
|
"repository": {
|