@jtalk22/slack-mcp 1.2.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -61
- package/docs/CLOUDFLARE-BROWSER-TOOLKIT.md +67 -0
- package/docs/DEPLOYMENT-MODES.md +10 -3
- package/docs/HN-LAUNCH.md +45 -36
- package/docs/INDEX.md +9 -0
- package/docs/INSTALL-PROOF.md +18 -0
- package/docs/LAUNCH-COPY-v3.0.0.md +73 -0
- package/docs/LAUNCH-MATRIX.md +22 -0
- package/docs/LAUNCH-OPS.md +71 -0
- package/docs/RELEASE-HEALTH.md +24 -0
- package/docs/TROUBLESHOOTING.md +27 -0
- package/docs/WEB-API.md +13 -4
- package/docs/images/demo-channel-messages.png +0 -0
- package/docs/images/demo-channels.png +0 -0
- package/docs/images/demo-claude-mobile-360x800.png +0 -0
- package/docs/images/demo-claude-mobile-390x844.png +0 -0
- package/docs/images/demo-main-mobile-360x800.png +0 -0
- package/docs/images/demo-main-mobile-390x844.png +0 -0
- package/docs/images/demo-main.png +0 -0
- package/docs/images/demo-poster.png +0 -0
- package/docs/images/demo-sidebar.png +0 -0
- package/docs/images/web-api-mobile-360x800.png +0 -0
- package/docs/images/web-api-mobile-390x844.png +0 -0
- package/lib/handlers.js +14 -6
- package/lib/slack-client.js +17 -1
- package/package.json +28 -12
- package/public/demo-claude.html +83 -10
- package/public/demo-video.html +33 -4
- package/public/demo.html +136 -2
- package/public/index.html +132 -69
- package/scripts/capture-screenshots.js +103 -53
- package/scripts/check-version-parity.js +176 -0
- package/scripts/cloudflare-browser-tool.js +237 -0
- package/scripts/collect-release-health.js +1 -1
- package/scripts/record-demo.js +22 -9
- package/scripts/release-preflight.js +243 -0
- package/scripts/setup-wizard.js +12 -2
- package/scripts/verify-install-flow.js +38 -2
- package/scripts/verify-web.js +49 -1
- package/server.json +47 -0
- package/smithery.yaml +34 -0
- package/src/server-http.js +123 -8
- package/src/server.js +36 -8
- package/src/web-server.js +60 -20
- package/docs/images/demo-claude-v1.2.gif +0 -0
- package/docs/images/demo-readme.gif +0 -0
- package/docs/release-health/2026-02-25.md +0 -33
- package/docs/release-health/2026-02-26.md +0 -33
- package/docs/release-health/24h-delta.md +0 -21
- package/docs/release-health/24h-end.md +0 -33
- package/docs/release-health/24h-start.md +0 -33
- package/docs/release-health/latest.md +0 -33
- package/docs/videos/.gitkeep +0 -0
- package/docs/videos/demo-claude-v1.2.webm +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "child_process";
|
|
4
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
5
|
+
import { dirname, resolve } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const ROOT = resolve(__dirname, "..");
|
|
10
|
+
const REPORT_PATH = resolve(ROOT, "docs", "release-health", "prepublish-dry-run.md");
|
|
11
|
+
|
|
12
|
+
const EXPECTED_NAME = process.env.EXPECTED_GIT_NAME || "jtalk22";
|
|
13
|
+
const EXPECTED_EMAIL = process.env.EXPECTED_GIT_EMAIL || "james@revasser.nyc";
|
|
14
|
+
const OWNER_RANGE = process.env.OWNER_CHECK_RANGE || "origin/main..HEAD";
|
|
15
|
+
|
|
16
|
+
function run(command, args = [], options = {}) {
|
|
17
|
+
return spawnSync(command, args, {
|
|
18
|
+
cwd: ROOT,
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
env: process.env,
|
|
21
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
22
|
+
...options
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function trimOutput(text = "", maxChars = 1200) {
|
|
27
|
+
const normalized = String(text || "").trim();
|
|
28
|
+
if (!normalized) return "";
|
|
29
|
+
if (normalized.length <= maxChars) return normalized;
|
|
30
|
+
return `${normalized.slice(0, maxChars)}... [truncated]`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stepResult(name, command, ok, details = "", commandOutput = "") {
|
|
34
|
+
return { name, command, ok, details, commandOutput };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function gitIdentityStep() {
|
|
38
|
+
const nameResult = run("git", ["config", "--get", "user.name"]);
|
|
39
|
+
const emailResult = run("git", ["config", "--get", "user.email"]);
|
|
40
|
+
|
|
41
|
+
const actualName = nameResult.stdout.trim();
|
|
42
|
+
const actualEmail = emailResult.stdout.trim();
|
|
43
|
+
const ok =
|
|
44
|
+
nameResult.status === 0 &&
|
|
45
|
+
emailResult.status === 0 &&
|
|
46
|
+
actualName === EXPECTED_NAME &&
|
|
47
|
+
actualEmail === EXPECTED_EMAIL;
|
|
48
|
+
|
|
49
|
+
const details = ok
|
|
50
|
+
? `Configured as ${actualName} <${actualEmail}>`
|
|
51
|
+
: `Expected ${EXPECTED_NAME} <${EXPECTED_EMAIL}>, found ${actualName || "(missing)"} <${actualEmail || "(missing)"}>`;
|
|
52
|
+
|
|
53
|
+
return stepResult(
|
|
54
|
+
"Git identity",
|
|
55
|
+
"git config --get user.name && git config --get user.email",
|
|
56
|
+
ok,
|
|
57
|
+
details,
|
|
58
|
+
`${nameResult.stdout}${nameResult.stderr}${emailResult.stdout}${emailResult.stderr}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ownerAttributionStep() {
|
|
63
|
+
const result = run("bash", ["scripts/check-owner-attribution.sh", OWNER_RANGE]);
|
|
64
|
+
return stepResult(
|
|
65
|
+
"Owner attribution",
|
|
66
|
+
`bash scripts/check-owner-attribution.sh ${OWNER_RANGE}`,
|
|
67
|
+
result.status === 0,
|
|
68
|
+
result.status === 0 ? "All commits in range are owner-attributed." : "Owner attribution check failed.",
|
|
69
|
+
`${result.stdout}${result.stderr}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function publicLanguageStep() {
|
|
74
|
+
const result = run("bash", ["scripts/check-public-language.sh"]);
|
|
75
|
+
return stepResult(
|
|
76
|
+
"Public language",
|
|
77
|
+
"bash scripts/check-public-language.sh",
|
|
78
|
+
result.status === 0,
|
|
79
|
+
result.status === 0 ? "Public wording guardrail passed." : "Disallowed wording found.",
|
|
80
|
+
`${result.stdout}${result.stderr}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function markerScanStep() {
|
|
85
|
+
const pattern = "Co-authored-by|co-authored-by|Generated with|generated with";
|
|
86
|
+
const scanPaths = [
|
|
87
|
+
"README.md",
|
|
88
|
+
"docs",
|
|
89
|
+
"public",
|
|
90
|
+
".github/RELEASE_NOTES_TEMPLATE.md",
|
|
91
|
+
".github/ISSUE_REPLY_TEMPLATE.md"
|
|
92
|
+
];
|
|
93
|
+
const result = run("rg", [
|
|
94
|
+
"-n",
|
|
95
|
+
pattern,
|
|
96
|
+
"--glob",
|
|
97
|
+
"!docs/release-health/**",
|
|
98
|
+
...scanPaths
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
if (result.status === 1) {
|
|
102
|
+
return stepResult(
|
|
103
|
+
"Public attribution markers",
|
|
104
|
+
"marker-scan",
|
|
105
|
+
true,
|
|
106
|
+
"No non-owner attribution markers found in public surfaces."
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return stepResult(
|
|
111
|
+
"Public attribution markers",
|
|
112
|
+
"marker-scan",
|
|
113
|
+
false,
|
|
114
|
+
result.status === 0
|
|
115
|
+
? "Found disallowed markers on public surfaces."
|
|
116
|
+
: "Marker scan failed.",
|
|
117
|
+
`${result.stdout}${result.stderr}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function runNodeStep(name, scriptPath, extraArgs = []) {
|
|
122
|
+
const result = run("node", [scriptPath, ...extraArgs]);
|
|
123
|
+
return stepResult(
|
|
124
|
+
name,
|
|
125
|
+
`node ${scriptPath}${extraArgs.length ? ` ${extraArgs.join(" ")}` : ""}`,
|
|
126
|
+
result.status === 0,
|
|
127
|
+
result.status === 0 ? "Passed." : "Failed.",
|
|
128
|
+
`${result.stdout}${result.stderr}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function npmPackSnapshot() {
|
|
133
|
+
const result = run("npm", ["pack", "--dry-run", "--json"]);
|
|
134
|
+
if (result.status !== 0) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
details: "Unable to generate npm pack snapshot.",
|
|
138
|
+
output: `${result.stdout}${result.stderr}`
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(result.stdout);
|
|
144
|
+
const entry = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
145
|
+
const fileCount = Array.isArray(entry.files) ? entry.files.length : 0;
|
|
146
|
+
const details = `package size ${entry.size} bytes, unpacked ${entry.unpackedSize} bytes, files ${fileCount}`;
|
|
147
|
+
return { ok: true, details, output: result.stdout };
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
details: "npm pack output was not valid JSON.",
|
|
152
|
+
output: `${result.stdout}\n${String(error)}`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildReport(results, packSnapshot) {
|
|
158
|
+
const generated = new Date().toISOString();
|
|
159
|
+
const failed = results.filter((step) => !step.ok);
|
|
160
|
+
const lines = [];
|
|
161
|
+
lines.push("# Prepublish Dry Run");
|
|
162
|
+
lines.push("");
|
|
163
|
+
lines.push(`- Generated: ${generated}`);
|
|
164
|
+
lines.push(`- Expected owner: \`${EXPECTED_NAME} <${EXPECTED_EMAIL}>\``);
|
|
165
|
+
lines.push(`- Owner range: \`${OWNER_RANGE}\``);
|
|
166
|
+
lines.push("");
|
|
167
|
+
lines.push("## Step Matrix");
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push("| Step | Status | Command | Details |");
|
|
170
|
+
lines.push("|---|---|---|---|");
|
|
171
|
+
for (const step of results) {
|
|
172
|
+
lines.push(`| ${step.name} | ${step.ok ? "pass" : "fail"} | \`${step.command}\` | ${step.details} |`);
|
|
173
|
+
}
|
|
174
|
+
lines.push("");
|
|
175
|
+
lines.push("## npm Pack Snapshot");
|
|
176
|
+
lines.push("");
|
|
177
|
+
lines.push(`- Status: ${packSnapshot.ok ? "pass" : "fail"}`);
|
|
178
|
+
lines.push(`- Details: ${packSnapshot.details}`);
|
|
179
|
+
lines.push("");
|
|
180
|
+
|
|
181
|
+
if (failed.length === 0 && packSnapshot.ok) {
|
|
182
|
+
lines.push("## Result");
|
|
183
|
+
lines.push("");
|
|
184
|
+
lines.push("Prepublish dry run passed.");
|
|
185
|
+
} else {
|
|
186
|
+
lines.push("## Result");
|
|
187
|
+
lines.push("");
|
|
188
|
+
lines.push("Prepublish dry run failed.");
|
|
189
|
+
lines.push("");
|
|
190
|
+
lines.push("### Failing checks");
|
|
191
|
+
for (const step of failed) {
|
|
192
|
+
lines.push(`- ${step.name}`);
|
|
193
|
+
}
|
|
194
|
+
if (!packSnapshot.ok) lines.push("- npm pack snapshot");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
lines.push("");
|
|
198
|
+
lines.push("## Command Output (Truncated)");
|
|
199
|
+
lines.push("");
|
|
200
|
+
for (const step of results) {
|
|
201
|
+
lines.push(`### ${step.name}`);
|
|
202
|
+
lines.push("");
|
|
203
|
+
lines.push("```text");
|
|
204
|
+
lines.push(trimOutput(step.commandOutput) || "(no output)");
|
|
205
|
+
lines.push("```");
|
|
206
|
+
lines.push("");
|
|
207
|
+
}
|
|
208
|
+
lines.push("### npm Pack Snapshot");
|
|
209
|
+
lines.push("");
|
|
210
|
+
lines.push("```text");
|
|
211
|
+
lines.push(trimOutput(packSnapshot.output, 4000) || "(no output)");
|
|
212
|
+
lines.push("```");
|
|
213
|
+
lines.push("");
|
|
214
|
+
|
|
215
|
+
return lines.join("\n");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function main() {
|
|
219
|
+
const steps = [
|
|
220
|
+
gitIdentityStep(),
|
|
221
|
+
ownerAttributionStep(),
|
|
222
|
+
publicLanguageStep(),
|
|
223
|
+
markerScanStep(),
|
|
224
|
+
runNodeStep("Core verification", "scripts/verify-core.js"),
|
|
225
|
+
runNodeStep("Web verification", "scripts/verify-web.js"),
|
|
226
|
+
runNodeStep("Install-flow verification", "scripts/verify-install-flow.js"),
|
|
227
|
+
runNodeStep("Version parity", "scripts/check-version-parity.js", ["--allow-propagation"])
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
const packSnapshot = npmPackSnapshot();
|
|
231
|
+
const report = buildReport(steps, packSnapshot);
|
|
232
|
+
|
|
233
|
+
mkdirSync(resolve(ROOT, "docs", "release-health"), { recursive: true });
|
|
234
|
+
writeFileSync(REPORT_PATH, `${report}\n`);
|
|
235
|
+
|
|
236
|
+
console.log(`Wrote ${REPORT_PATH}`);
|
|
237
|
+
const failed = steps.some((step) => !step.ok) || !packSnapshot.ok;
|
|
238
|
+
if (failed) {
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
main();
|
package/scripts/setup-wizard.js
CHANGED
|
@@ -23,8 +23,9 @@ import {
|
|
|
23
23
|
} from "../lib/token-store.js";
|
|
24
24
|
|
|
25
25
|
const IS_MACOS = platform() === 'darwin';
|
|
26
|
-
const VERSION = "
|
|
26
|
+
const VERSION = "3.0.0";
|
|
27
27
|
const MIN_NODE_MAJOR = 20;
|
|
28
|
+
const AUTH_TEST_URL = process.env.SLACK_MCP_AUTH_TEST_URL || "https://slack.com/api/auth.test";
|
|
28
29
|
|
|
29
30
|
// ANSI colors
|
|
30
31
|
const colors = {
|
|
@@ -78,7 +79,7 @@ async function pressEnterToContinue(rl) {
|
|
|
78
79
|
|
|
79
80
|
async function validateTokens(token, cookie) {
|
|
80
81
|
try {
|
|
81
|
-
const response = await fetch(
|
|
82
|
+
const response = await fetch(AUTH_TEST_URL, {
|
|
82
83
|
method: "POST",
|
|
83
84
|
headers: {
|
|
84
85
|
"Authorization": `Bearer ${token}`,
|
|
@@ -251,6 +252,8 @@ async function showStatus() {
|
|
|
251
252
|
|
|
252
253
|
if (!creds) {
|
|
253
254
|
error("No tokens found");
|
|
255
|
+
print("Code: missing_credentials");
|
|
256
|
+
print("Message: No credentials available from environment, file, or keychain.");
|
|
254
257
|
print();
|
|
255
258
|
print("Run setup wizard: npx -y @jtalk22/slack-mcp --setup");
|
|
256
259
|
process.exit(1);
|
|
@@ -268,6 +271,7 @@ async function showStatus() {
|
|
|
268
271
|
const result = await validateTokens(creds.token, creds.cookie);
|
|
269
272
|
if (!result.valid) {
|
|
270
273
|
error("Status: INVALID");
|
|
274
|
+
print("Code: auth_failed");
|
|
271
275
|
print(`Error: ${result.error}`);
|
|
272
276
|
print();
|
|
273
277
|
print("Run setup wizard to refresh: npx -y @jtalk22/slack-mcp --setup");
|
|
@@ -275,6 +279,8 @@ async function showStatus() {
|
|
|
275
279
|
}
|
|
276
280
|
|
|
277
281
|
success("Status: VALID");
|
|
282
|
+
print("Code: ok");
|
|
283
|
+
print("Message: Slack auth valid.");
|
|
278
284
|
print(`User: ${result.user}`);
|
|
279
285
|
print(`Team: ${result.team}`);
|
|
280
286
|
print(`User ID: ${result.userId}`);
|
|
@@ -331,6 +337,7 @@ async function runDoctor() {
|
|
|
331
337
|
const nodeMajor = parseNodeMajor();
|
|
332
338
|
if (Number.isNaN(nodeMajor) || nodeMajor < MIN_NODE_MAJOR) {
|
|
333
339
|
error(`Node.js ${process.versions.node} detected (requires Node ${MIN_NODE_MAJOR}+)`);
|
|
340
|
+
print("Code: runtime_node_unsupported");
|
|
334
341
|
print();
|
|
335
342
|
print("Next action:");
|
|
336
343
|
print(` npx -y @jtalk22/slack-mcp --doctor # rerun after upgrading Node ${MIN_NODE_MAJOR}+`);
|
|
@@ -341,6 +348,7 @@ async function runDoctor() {
|
|
|
341
348
|
const creds = getDoctorCredentials();
|
|
342
349
|
if (!creds) {
|
|
343
350
|
error("Credentials: not found");
|
|
351
|
+
print("Code: missing_credentials");
|
|
344
352
|
print();
|
|
345
353
|
print("Next action:");
|
|
346
354
|
print(" npx -y @jtalk22/slack-mcp --setup");
|
|
@@ -361,6 +369,7 @@ async function runDoctor() {
|
|
|
361
369
|
if (!validation.valid) {
|
|
362
370
|
const exitCode = classifyAuthError(validation.error);
|
|
363
371
|
error(`Slack auth failed: ${validation.error}`);
|
|
372
|
+
print(`Code: ${exitCode === 2 ? "auth_invalid" : "runtime_auth_check_failed"}`);
|
|
364
373
|
print();
|
|
365
374
|
print("Next action:");
|
|
366
375
|
if (exitCode === 2) {
|
|
@@ -373,6 +382,7 @@ async function runDoctor() {
|
|
|
373
382
|
}
|
|
374
383
|
|
|
375
384
|
success(`Slack auth valid for ${validation.user} @ ${validation.team}`);
|
|
385
|
+
print("Code: ok");
|
|
376
386
|
print();
|
|
377
387
|
print("Ready. Next command:");
|
|
378
388
|
print(" npx -y @jtalk22/slack-mcp");
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
9
|
const PKG = "@jtalk22/slack-mcp";
|
|
10
10
|
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
const strictPublished = process.argv.includes("--strict-published");
|
|
12
|
+
const PUBLISHED_SPEC = `${PKG}@latest`;
|
|
13
|
+
const localVersion = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")).version;
|
|
11
14
|
|
|
12
15
|
function runNpx(args, options = {}) {
|
|
13
|
-
const cmdArgs = ["-y",
|
|
16
|
+
const cmdArgs = ["-y", PUBLISHED_SPEC, ...args];
|
|
14
17
|
const result = spawnSync("npx", cmdArgs, {
|
|
15
18
|
cwd: options.cwd,
|
|
16
19
|
env: options.env,
|
|
@@ -97,6 +100,18 @@ function main() {
|
|
|
97
100
|
"Expected --version to exit 0",
|
|
98
101
|
versionResult.stderr || versionResult.stdout,
|
|
99
102
|
);
|
|
103
|
+
const publishedMatchesLocal = versionResult.stdout.includes(localVersion);
|
|
104
|
+
if (strictPublished) {
|
|
105
|
+
assert(
|
|
106
|
+
publishedMatchesLocal,
|
|
107
|
+
`Expected published npx version to match local ${localVersion}`,
|
|
108
|
+
versionResult.stdout,
|
|
109
|
+
);
|
|
110
|
+
} else if (!publishedMatchesLocal) {
|
|
111
|
+
console.log(
|
|
112
|
+
`warning: npx resolved ${versionResult.stdout || "unknown"} while local version is ${localVersion}; strict published checks are deferred until publish.`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
100
115
|
|
|
101
116
|
const helpResult = runNpx(["--help"], { cwd: testHome, env });
|
|
102
117
|
printResult("help", helpResult);
|
|
@@ -113,6 +128,15 @@ function main() {
|
|
|
113
128
|
"Expected --status to exit non-zero when credentials are missing",
|
|
114
129
|
statusResult.stderr || statusResult.stdout,
|
|
115
130
|
);
|
|
131
|
+
if (strictPublished) {
|
|
132
|
+
assert(
|
|
133
|
+
!statusResult.stderr.includes("Attempting Chrome auto-extraction"),
|
|
134
|
+
"Expected npx --status to be read-only without auto-extraction side effects",
|
|
135
|
+
statusResult.stderr,
|
|
136
|
+
);
|
|
137
|
+
} else if (statusResult.stderr.includes("Attempting Chrome auto-extraction")) {
|
|
138
|
+
console.log("warning: published npx --status still has extraction side effects; re-run with --strict-published after publish.");
|
|
139
|
+
}
|
|
116
140
|
|
|
117
141
|
const localStatusResult = runLocalSetupStatus({ env });
|
|
118
142
|
printResult("local-status", localStatusResult);
|
|
@@ -148,6 +172,18 @@ function main() {
|
|
|
148
172
|
localDoctorInvalidResult.stderr || localDoctorInvalidResult.stdout,
|
|
149
173
|
);
|
|
150
174
|
|
|
175
|
+
const runtimeEnv = {
|
|
176
|
+
...invalidEnv,
|
|
177
|
+
SLACK_MCP_AUTH_TEST_URL: "http://127.0.0.1:9/auth.test"
|
|
178
|
+
};
|
|
179
|
+
const localDoctorRuntimeResult = runLocalDoctor({ env: runtimeEnv });
|
|
180
|
+
printResult("local-doctor-runtime", localDoctorRuntimeResult);
|
|
181
|
+
assert(
|
|
182
|
+
localDoctorRuntimeResult.status === 3,
|
|
183
|
+
"Expected local --doctor to exit 3 when runtime connectivity fails",
|
|
184
|
+
localDoctorRuntimeResult.stderr || localDoctorRuntimeResult.stdout,
|
|
185
|
+
);
|
|
186
|
+
|
|
151
187
|
console.log("\nInstall flow verification passed.");
|
|
152
188
|
} finally {
|
|
153
189
|
rmSync(testHome, { recursive: true, force: true });
|
package/scripts/verify-web.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* 1. Server starts and prints Magic Link
|
|
7
7
|
* 2. /demo.html contains "STATIC PREVIEW" banner
|
|
8
8
|
* 3. /?key=... serves the dashboard (index.html)
|
|
9
|
-
* 4.
|
|
9
|
+
* 4. /demo-video.html media assets are reachable
|
|
10
|
+
* 5. Server shuts down cleanly
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { spawn } from "child_process";
|
|
@@ -142,6 +143,45 @@ async function testApiWithKey(apiKey) {
|
|
|
142
143
|
return true;
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
async function testDemoVideoAssets() {
|
|
147
|
+
const demoVideoUrl = `http://localhost:${PORT}/demo-video.html`;
|
|
148
|
+
const demoVideoRes = await fetch(demoVideoUrl);
|
|
149
|
+
|
|
150
|
+
if (!demoVideoRes.ok) {
|
|
151
|
+
throw new Error(`Failed to fetch demo-video.html: ${demoVideoRes.status}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const demoVideoHtml = await demoVideoRes.text();
|
|
155
|
+
const requiredAssetCandidates = [
|
|
156
|
+
[
|
|
157
|
+
"/docs/images/demo-poster.png",
|
|
158
|
+
"https://jtalk22.github.io/slack-mcp-server/docs/images/demo-poster.png",
|
|
159
|
+
],
|
|
160
|
+
[
|
|
161
|
+
"/docs/videos/demo-claude.webm",
|
|
162
|
+
"https://jtalk22.github.io/slack-mcp-server/docs/videos/demo-claude.webm",
|
|
163
|
+
],
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
for (const candidates of requiredAssetCandidates) {
|
|
167
|
+
const matched = candidates.find((candidate) => demoVideoHtml.includes(candidate));
|
|
168
|
+
if (!matched) {
|
|
169
|
+
throw new Error(`demo-video.html missing expected media reference: ${candidates.join(" OR ")}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const assetUrl = matched.startsWith("http")
|
|
173
|
+
? matched
|
|
174
|
+
: `http://localhost:${PORT}${matched}`;
|
|
175
|
+
|
|
176
|
+
const assetRes = await fetch(assetUrl);
|
|
177
|
+
if (!assetRes.ok) {
|
|
178
|
+
throw new Error(`Demo media not reachable: ${assetUrl} (status ${assetRes.status})`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
145
185
|
async function main() {
|
|
146
186
|
console.log("╔════════════════════════════════════════╗");
|
|
147
187
|
console.log("║ Web UI Verification Tests ║");
|
|
@@ -192,6 +232,14 @@ async function main() {
|
|
|
192
232
|
log("PASS: API correctly rejects bad keys");
|
|
193
233
|
results.push(true);
|
|
194
234
|
|
|
235
|
+
// Test 5: Demo video/media paths
|
|
236
|
+
console.log("\n[TEST 5] Demo Video Media Reachability");
|
|
237
|
+
console.log("─".repeat(40));
|
|
238
|
+
|
|
239
|
+
await testDemoVideoAssets();
|
|
240
|
+
log("PASS: demo-video media assets are reachable");
|
|
241
|
+
results.push(true);
|
|
242
|
+
|
|
195
243
|
} catch (err) {
|
|
196
244
|
console.log(` FAIL: ${err.message}`);
|
|
197
245
|
results.push(false);
|
package/server.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.jtalk22/slack-mcp-server",
|
|
4
|
+
"title": "Slack MCP Server",
|
|
5
|
+
"description": "Session-based Slack access for Claude: DMs, channels, search, and threads via your Slack session.",
|
|
6
|
+
"websiteUrl": "https://jtalk22.github.io/slack-mcp-server/public/demo.html",
|
|
7
|
+
"icons": [
|
|
8
|
+
{
|
|
9
|
+
"src": "https://raw.githubusercontent.com/jtalk22/slack-mcp-server/main/docs/assets/icon-512.png",
|
|
10
|
+
"mimeType": "image/png",
|
|
11
|
+
"sizes": [
|
|
12
|
+
"512x512"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"url": "https://github.com/jtalk22/slack-mcp-server",
|
|
18
|
+
"source": "github"
|
|
19
|
+
},
|
|
20
|
+
"version": "3.0.0",
|
|
21
|
+
"packages": [
|
|
22
|
+
{
|
|
23
|
+
"registryType": "npm",
|
|
24
|
+
"identifier": "@jtalk22/slack-mcp",
|
|
25
|
+
"version": "3.0.0",
|
|
26
|
+
"transport": {
|
|
27
|
+
"type": "stdio"
|
|
28
|
+
},
|
|
29
|
+
"environmentVariables": [
|
|
30
|
+
{
|
|
31
|
+
"description": "Slack xoxc- token from browser session",
|
|
32
|
+
"isRequired": false,
|
|
33
|
+
"format": "string",
|
|
34
|
+
"isSecret": true,
|
|
35
|
+
"name": "SLACK_TOKEN"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"description": "Slack xoxd- cookie from browser session",
|
|
39
|
+
"isRequired": false,
|
|
40
|
+
"format": "string",
|
|
41
|
+
"isSecret": true,
|
|
42
|
+
"name": "SLACK_COOKIE"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Smithery configuration for slack-mcp-server
|
|
2
|
+
# https://smithery.ai/docs/build/project-config/smithery-yaml
|
|
3
|
+
|
|
4
|
+
startCommand:
|
|
5
|
+
type: stdio
|
|
6
|
+
configSchema:
|
|
7
|
+
type: object
|
|
8
|
+
properties:
|
|
9
|
+
slackToken:
|
|
10
|
+
type: string
|
|
11
|
+
title: "Slack Token"
|
|
12
|
+
description: "Your xoxc- token from browser session. Optional on macOS (auto-extracted from Chrome)."
|
|
13
|
+
pattern: "^xoxc-.*$"
|
|
14
|
+
slackCookie:
|
|
15
|
+
type: string
|
|
16
|
+
title: "Slack Cookie"
|
|
17
|
+
description: "Your xoxd- cookie from browser session. Optional on macOS (auto-extracted from Chrome)."
|
|
18
|
+
pattern: "^xoxd-.*$"
|
|
19
|
+
autoRefresh:
|
|
20
|
+
type: boolean
|
|
21
|
+
title: "Auto Refresh"
|
|
22
|
+
description: "Enable automatic token refresh from Chrome (macOS only)"
|
|
23
|
+
default: true
|
|
24
|
+
additionalProperties: false
|
|
25
|
+
commandFunction: |-
|
|
26
|
+
(config) => ({
|
|
27
|
+
command: 'node',
|
|
28
|
+
args: ['src/server.js'],
|
|
29
|
+
env: {
|
|
30
|
+
...(config.slackToken && { SLACK_TOKEN: config.slackToken }),
|
|
31
|
+
...(config.slackCookie && { SLACK_COOKIE: config.slackCookie }),
|
|
32
|
+
...(config.autoRefresh === false && { SLACK_NO_AUTO_REFRESH: 'true' })
|
|
33
|
+
}
|
|
34
|
+
})
|