@jtalk22/slack-mcp 1.2.4 → 2.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 +6 -2
- package/docs/HN-LAUNCH.md +28 -30
- package/docs/INDEX.md +6 -0
- package/docs/INSTALL-PROOF.md +18 -0
- package/docs/LAUNCH-COPY-v2.0.0.md +59 -0
- package/docs/LAUNCH-MATRIX.md +20 -0
- package/docs/LAUNCH-OPS.md +70 -0
- package/docs/RELEASE-HEALTH.md +15 -0
- package/docs/release-health/2026-02-26.md +3 -3
- package/docs/release-health/latest.md +3 -3
- package/docs/release-health/launch-log-template.md +21 -0
- package/docs/release-health/version-parity.md +21 -0
- package/lib/handlers.js +14 -6
- package/lib/slack-client.js +17 -1
- package/package.json +16 -8
- package/public/demo-claude.html +2 -2
- package/scripts/check-version-parity.js +162 -0
- package/scripts/collect-release-health.js +1 -1
- package/scripts/setup-wizard.js +12 -2
- package/scripts/verify-install-flow.js +36 -1
- package/src/server-http.js +26 -4
- package/src/server.js +20 -4
- package/src/web-server.js +58 -20
package/README.md
CHANGED
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
+
> **Release:** `v2.0.0` is live with deterministic diagnostics and no MCP tool renames/removals.
|
|
40
|
+
> Release notes: [.github/v2.0.0-release-notes.md](.github/v2.0.0-release-notes.md)
|
|
41
|
+
> Install proof: [docs/INSTALL-PROOF.md](docs/INSTALL-PROOF.md)
|
|
42
|
+
|
|
39
43
|
> Free local-first path: install with `npx -y @jtalk22/slack-mcp`, run on your own machine, and keep full control of session tokens.
|
|
40
44
|
|
|
41
45
|
### Why This Exists
|
|
@@ -110,7 +114,7 @@ npx -y @jtalk22/slack-mcp --setup
|
|
|
110
114
|
```
|
|
111
115
|
|
|
112
116
|
Expected:
|
|
113
|
-
- `--version` prints `slack-mcp-server
|
|
117
|
+
- `--version` prints `slack-mcp-server v2.0.x`
|
|
114
118
|
- `--doctor` returns one clear next action with exit code:
|
|
115
119
|
- `0` ready
|
|
116
120
|
- `1` missing credentials
|
|
@@ -335,7 +339,7 @@ npm run web
|
|
|
335
339
|
|
|
336
340
|
```
|
|
337
341
|
════════════════════════════════════════════════════════════
|
|
338
|
-
Slack Web API Server
|
|
342
|
+
Slack Web API Server v2.0.0
|
|
339
343
|
════════════════════════════════════════════════════════════
|
|
340
344
|
|
|
341
345
|
Dashboard: http://localhost:3000/?key=smcp_xxxxxxxxxxxx
|
package/docs/HN-LAUNCH.md
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
# HN Launch Kit
|
|
1
|
+
# HN Launch Kit (v2.0.0)
|
|
2
2
|
|
|
3
|
-
Use this file
|
|
3
|
+
Use this file for Show HN posting and first-comment follow-up.
|
|
4
4
|
|
|
5
5
|
## Title Options
|
|
6
6
|
|
|
7
|
-
- `Show HN: Slack MCP Server (
|
|
8
|
-
- `Show HN: Slack MCP Server (
|
|
9
|
-
- `Show HN: Slack MCP Server (Slack
|
|
7
|
+
- `Show HN: Slack MCP Server v2.0.0 (deterministic Slack MCP diagnostics)`
|
|
8
|
+
- `Show HN: Slack MCP Server v2.0.0 (session-based Slack access, stable contracts)`
|
|
9
|
+
- `Show HN: Slack MCP Server v2.0.0 (local-first Slack context for Claude)`
|
|
10
10
|
|
|
11
11
|
## Launch Post Template
|
|
12
12
|
|
|
13
13
|
```md
|
|
14
|
-
|
|
14
|
+
Released `@jtalk22/slack-mcp@2.0.0` today.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
16
|
+
This release focuses on install reliability and deterministic diagnostics:
|
|
17
|
+
- read-only `--status` behavior enforced in install-path verification
|
|
18
|
+
- deterministic `--doctor` exits (`0/1/2/3`)
|
|
19
|
+
- structured MCP/web error payloads for triage consistency
|
|
20
|
+
- token health handles missing timestamp as unknown age, not false critical
|
|
21
|
+
- no MCP tool renames or removals
|
|
21
22
|
|
|
22
23
|
Verify:
|
|
23
24
|
- `npx -y @jtalk22/slack-mcp --version`
|
|
25
|
+
- `npx -y @jtalk22/slack-mcp --doctor`
|
|
24
26
|
- `npx -y @jtalk22/slack-mcp --status`
|
|
25
|
-
- `npx -y @jtalk22/slack-mcp --setup`
|
|
26
27
|
|
|
27
28
|
Repo: https://github.com/jtalk22/slack-mcp-server
|
|
28
29
|
npm: https://www.npmjs.com/package/@jtalk22/slack-mcp
|
|
@@ -31,33 +32,30 @@ npm: https://www.npmjs.com/package/@jtalk22/slack-mcp
|
|
|
31
32
|
## First Comment Draft
|
|
32
33
|
|
|
33
34
|
```md
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
35
|
+
Quick notes:
|
|
36
|
+
- Default path is local-first (`stdio`) and remains fully supported.
|
|
37
|
+
- `--status` is read-only by design in this release.
|
|
38
|
+
- `--doctor` has deterministic exit codes for automation and triage.
|
|
39
|
+
- If registry pages lag, metadata is propagating; npm and GitHub release are authoritative first.
|
|
39
40
|
|
|
40
|
-
If
|
|
41
|
+
If anything fails, include OS, Node version, runtime mode (`stdio|web|http|worker`), and exact output.
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
## FAQ
|
|
44
|
+
## FAQ Macro
|
|
44
45
|
|
|
45
|
-
###
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
### What about token expiry?
|
|
49
|
-
Session tokens expire. `--setup` refreshes credentials, and macOS supports automatic extraction from Chrome.
|
|
46
|
+
### Why session-based instead of OAuth app scopes?
|
|
47
|
+
Session mirroring provides the same access visible in the signed-in Slack web session.
|
|
50
48
|
|
|
51
49
|
### Is hosted deployment required?
|
|
52
|
-
No.
|
|
53
|
-
|
|
54
|
-
### Is this suitable for production teams?
|
|
55
|
-
Treat as operator-managed infrastructure and validate against your own security and compliance requirements.
|
|
50
|
+
No. Local operator path is primary. Hosted paths are optional.
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
### Are tool contracts changed in v2.0.0?
|
|
53
|
+
No. This release keeps existing MCP tool names.
|
|
58
54
|
|
|
55
|
+
### What should I run first?
|
|
56
|
+
Use:
|
|
59
57
|
```bash
|
|
60
58
|
npx -y @jtalk22/slack-mcp --version
|
|
59
|
+
npx -y @jtalk22/slack-mcp --doctor
|
|
61
60
|
npx -y @jtalk22/slack-mcp --status
|
|
62
|
-
npx -y @jtalk22/slack-mcp --setup
|
|
63
61
|
```
|
package/docs/INDEX.md
CHANGED
|
@@ -19,9 +19,15 @@ Start here for setup, compatibility checks, operations, and support.
|
|
|
19
19
|
## Launch and Communication
|
|
20
20
|
|
|
21
21
|
- [HN Launch Kit](HN-LAUNCH.md)
|
|
22
|
+
- [Launch Copy (v2.0.0)](LAUNCH-COPY-v2.0.0.md)
|
|
23
|
+
- [Launch Ops Runbook](LAUNCH-OPS.md)
|
|
24
|
+
- [Capability Matrix](LAUNCH-MATRIX.md)
|
|
25
|
+
- [Install Proof Block](INSTALL-PROOF.md)
|
|
22
26
|
- [Communication Style](COMMUNICATION-STYLE.md)
|
|
23
27
|
- [Release Health Guide](RELEASE-HEALTH.md)
|
|
24
28
|
- [Latest Release Health Snapshot](release-health/latest.md)
|
|
29
|
+
- [Version Parity Report](release-health/version-parity.md)
|
|
30
|
+
- [Launch Log Template](release-health/launch-log-template.md)
|
|
25
31
|
- [Changelog](../CHANGELOG.md)
|
|
26
32
|
|
|
27
33
|
## Issue Intake
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Install Proof Block (v2.0.0)
|
|
2
|
+
|
|
3
|
+
Use this command block in release notes, HN/X/Reddit follow-ups, and issue replies.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx -y @jtalk22/slack-mcp --version
|
|
7
|
+
npx -y @jtalk22/slack-mcp --doctor
|
|
8
|
+
npx -y @jtalk22/slack-mcp --status
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Expected:
|
|
12
|
+
- `--version` prints `slack-mcp-server v2.0.0`
|
|
13
|
+
- `--doctor` exits with:
|
|
14
|
+
- `0` ready
|
|
15
|
+
- `1` missing credentials
|
|
16
|
+
- `2` invalid or expired credentials
|
|
17
|
+
- `3` runtime or connectivity issue
|
|
18
|
+
- `--status` is read-only and does not trigger Chrome extraction.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Launch Copy (v2.0.0)
|
|
2
|
+
|
|
3
|
+
Use this file for channel-specific copy with consistent technical claims.
|
|
4
|
+
|
|
5
|
+
## Short Release Summary (150 words)
|
|
6
|
+
|
|
7
|
+
`@jtalk22/slack-mcp v2.0.0` is out with a focus on deterministic diagnostics and clean operator workflows. This release enforces read-only `--status` behavior in install-path verification, expands `--doctor` coverage to a strict `0/1/2/3` exit matrix, and standardizes structured error envelopes across MCP tool failures and web API validation/runtime paths. Token health handling now reports explicit unknown-age semantics when timestamps are missing, so operators do not get false critical warnings. Metadata is aligned for distribution parity with updated registry-facing server metadata and a new parity checker (`npm run verify:version-parity`) that reports local/npm/registry alignment. MCP tool contracts stay compatible: no renames, no removals. If you use Claude Desktop, Claude Code, or local web mode, this is a drop-in upgrade focused on faster triage and fewer install surprises.
|
|
8
|
+
|
|
9
|
+
## X Thread (7 posts)
|
|
10
|
+
|
|
11
|
+
1. `Slack MCP Server v2.0.0 is live.`
|
|
12
|
+
`Focus: deterministic diagnostics + install reliability.`
|
|
13
|
+
2. `No MCP tool renames/removals in this release.`
|
|
14
|
+
`Compatibility stays stable.`
|
|
15
|
+
3. `Install-path checks now enforce read-only --status behavior.`
|
|
16
|
+
`No token extraction side effects during status checks.`
|
|
17
|
+
4. `--doctor now enforces deterministic exits:`
|
|
18
|
+
`0 ready / 1 missing creds / 2 invalid creds / 3 runtime issue.`
|
|
19
|
+
5. `Token health now reports unknown-age semantics when timestamp is missing.`
|
|
20
|
+
`No false critical warning from missing metadata.`
|
|
21
|
+
6. `Parity checker added: npm/local/registry view in one report.`
|
|
22
|
+
`npm run verify:version-parity`
|
|
23
|
+
7. `Try it:`
|
|
24
|
+
`npx -y @jtalk22/slack-mcp --version`
|
|
25
|
+
`npx -y @jtalk22/slack-mcp --doctor`
|
|
26
|
+
`Repo: https://github.com/jtalk22/slack-mcp-server`
|
|
27
|
+
|
|
28
|
+
## Reddit Post (Technical Variant)
|
|
29
|
+
|
|
30
|
+
Title:
|
|
31
|
+
- `Slack MCP Server v2.0.0: deterministic install diagnostics, stable tool contracts`
|
|
32
|
+
|
|
33
|
+
Body:
|
|
34
|
+
```md
|
|
35
|
+
Released `@jtalk22/slack-mcp@2.0.0` today.
|
|
36
|
+
|
|
37
|
+
Main changes are reliability-focused:
|
|
38
|
+
- install-path verification enforces read-only `--status`
|
|
39
|
+
- `--doctor` exit matrix enforced (`0/1/2/3`)
|
|
40
|
+
- standardized structured error payloads for MCP tool failures and web API errors
|
|
41
|
+
- token health handles missing timestamp as unknown age (no false critical)
|
|
42
|
+
- new version parity report script for npm/local/registry checks
|
|
43
|
+
|
|
44
|
+
No MCP tool renames or removals in this release.
|
|
45
|
+
|
|
46
|
+
Verify:
|
|
47
|
+
`npx -y @jtalk22/slack-mcp --version`
|
|
48
|
+
`npx -y @jtalk22/slack-mcp --doctor`
|
|
49
|
+
`npx -y @jtalk22/slack-mcp --status`
|
|
50
|
+
|
|
51
|
+
Repo: https://github.com/jtalk22/slack-mcp-server
|
|
52
|
+
npm: https://www.npmjs.com/package/@jtalk22/slack-mcp
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Registry Propagation Note
|
|
56
|
+
|
|
57
|
+
If registry versions are still syncing right after publish, use this sentence:
|
|
58
|
+
|
|
59
|
+
`Release is published; registry metadata is propagating. Timestamp: <UTC timestamp>.`
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Capability Matrix (v2.0.0)
|
|
2
|
+
|
|
3
|
+
Comparison matrix for release notes and channel posts. Keep competitor references unnamed.
|
|
4
|
+
|
|
5
|
+
| Capability | This Release (`v2.0.0`) | Typical Session-Based Alternatives |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| Read-only status checks | Enforced and verified in install flow | Often undocumented or mixed with refresh side effects |
|
|
8
|
+
| Deterministic doctor exits | Enforced (`0/1/2/3`) with install-path coverage | Often ad-hoc or text-only |
|
|
9
|
+
| Structured diagnostics | Shared fields in MCP/web error payloads | Mixed payload shapes |
|
|
10
|
+
| Unknown token age handling | Explicit `unknown_age` semantics | Missing timestamp may appear as stale/failing |
|
|
11
|
+
| Tool contract stability | No MCP tool rename/removal | Varies by release |
|
|
12
|
+
| Local-first operator path | First-class (`stdio`, `web`) | Varies by runtime emphasis |
|
|
13
|
+
| Version parity check | Scripted local/npm/registry report | Often manual |
|
|
14
|
+
| Smithery/registry alignment | Metadata prepared in release wave | Often delayed post-release |
|
|
15
|
+
|
|
16
|
+
## Usage Guidance
|
|
17
|
+
|
|
18
|
+
1. Use this table in release notes and social threads.
|
|
19
|
+
2. Do not name external projects.
|
|
20
|
+
3. Keep claims tied to verifiable commands and docs.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Launch Ops Runbook (v2.0.0)
|
|
2
|
+
|
|
3
|
+
This runbook defines launch-day monitoring, response rules, and reinforcement loops.
|
|
4
|
+
|
|
5
|
+
## Same-Day Fanout Order
|
|
6
|
+
|
|
7
|
+
1. GitHub release publish (`v2.0.0`)
|
|
8
|
+
2. npm publish confirm (`@jtalk22/slack-mcp@2.0.0`)
|
|
9
|
+
3. MCP registry metadata update
|
|
10
|
+
4. Smithery listing metadata parity update
|
|
11
|
+
5. `awesome-mcp-servers` version update PR
|
|
12
|
+
6. Glama listing refresh/update
|
|
13
|
+
7. HN post + first comment
|
|
14
|
+
8. X thread
|
|
15
|
+
9. Reddit technical post
|
|
16
|
+
|
|
17
|
+
## Monitoring Cadence
|
|
18
|
+
|
|
19
|
+
- First 4 hours: every 30 minutes
|
|
20
|
+
- Up to 24 hours: every 60 minutes
|
|
21
|
+
|
|
22
|
+
Track:
|
|
23
|
+
- install reports and blocker count
|
|
24
|
+
- npm version/install confirmation
|
|
25
|
+
- registry parity status
|
|
26
|
+
- inbound issue volume and severity
|
|
27
|
+
|
|
28
|
+
## Triage Rules
|
|
29
|
+
|
|
30
|
+
P1 install blocker:
|
|
31
|
+
- acknowledge within 30-60 minutes
|
|
32
|
+
- provide immediate workaround
|
|
33
|
+
- add fix to patch queue
|
|
34
|
+
|
|
35
|
+
Non-blocking request:
|
|
36
|
+
- acknowledge and route to issue template
|
|
37
|
+
- provide timeline as best effort
|
|
38
|
+
|
|
39
|
+
## Escalation Triggers
|
|
40
|
+
|
|
41
|
+
1. If install failures exceed 3 unique reports in 24h:
|
|
42
|
+
- pause outbound posting
|
|
43
|
+
- prioritize hotfix
|
|
44
|
+
|
|
45
|
+
2. If support load exceeds 2 hours/day for 2 days:
|
|
46
|
+
- move to stability-only mode
|
|
47
|
+
- defer non-critical requests
|
|
48
|
+
|
|
49
|
+
## 24h / 48h / 72h Follow-Up
|
|
50
|
+
|
|
51
|
+
24h:
|
|
52
|
+
- publish release-health delta and short technical update
|
|
53
|
+
|
|
54
|
+
48h:
|
|
55
|
+
- answer top 5 recurring questions in docs
|
|
56
|
+
|
|
57
|
+
72h:
|
|
58
|
+
- publish `v2.0.1` only if launch defects are confirmed
|
|
59
|
+
|
|
60
|
+
## Evidence Log
|
|
61
|
+
|
|
62
|
+
Use:
|
|
63
|
+
- `docs/release-health/launch-log-template.md`
|
|
64
|
+
|
|
65
|
+
Capture:
|
|
66
|
+
- channel
|
|
67
|
+
- UTC timestamp
|
|
68
|
+
- URL
|
|
69
|
+
- action taken
|
|
70
|
+
- observed result
|
package/docs/RELEASE-HEALTH.md
CHANGED
|
@@ -13,6 +13,21 @@ Outputs:
|
|
|
13
13
|
- `docs/release-health/YYYY-MM-DD.md`
|
|
14
14
|
- `docs/release-health/automation-delta.md` (when delta script is run)
|
|
15
15
|
|
|
16
|
+
## Version parity report
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm run verify:version-parity
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Output:
|
|
23
|
+
- `docs/release-health/version-parity.md`
|
|
24
|
+
|
|
25
|
+
If external registries are still propagating immediately after publish:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run verify:version-parity -- --allow-propagation
|
|
29
|
+
```
|
|
30
|
+
|
|
16
31
|
24-hour loop artifacts:
|
|
17
32
|
- `docs/release-health/24h-start.md`
|
|
18
33
|
- `docs/release-health/24h-end.md`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Release Health Snapshot
|
|
2
2
|
|
|
3
|
-
- Generated: 2026-02-
|
|
3
|
+
- Generated: 2026-02-26T12:46:11.970Z
|
|
4
4
|
- Repo: `jtalk22/slack-mcp-server`
|
|
5
5
|
- Package: `@jtalk22/slack-mcp`
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- npm downloads (last week): 532
|
|
10
10
|
- npm downloads (last month): 1011
|
|
11
|
-
- npm latest version: 1.2.
|
|
11
|
+
- npm latest version: 1.2.4
|
|
12
12
|
|
|
13
13
|
## GitHub Reach
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- 14d unique cloners: 113
|
|
22
22
|
- deployment-intake submissions (all-time): 1
|
|
23
23
|
|
|
24
|
-
## 14-Day Reliability Targets (
|
|
24
|
+
## 14-Day Reliability Targets (v2.0.0 Cycle)
|
|
25
25
|
|
|
26
26
|
- weekly downloads: >= 180
|
|
27
27
|
- qualified deployment-intake submissions: >= 2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Release Health Snapshot
|
|
2
2
|
|
|
3
|
-
- Generated: 2026-02-
|
|
3
|
+
- Generated: 2026-02-26T12:46:11.970Z
|
|
4
4
|
- Repo: `jtalk22/slack-mcp-server`
|
|
5
5
|
- Package: `@jtalk22/slack-mcp`
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- npm downloads (last week): 532
|
|
10
10
|
- npm downloads (last month): 1011
|
|
11
|
-
- npm latest version: 1.2.
|
|
11
|
+
- npm latest version: 1.2.4
|
|
12
12
|
|
|
13
13
|
## GitHub Reach
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- 14d unique cloners: 113
|
|
22
22
|
- deployment-intake submissions (all-time): 1
|
|
23
23
|
|
|
24
|
-
## 14-Day Reliability Targets (
|
|
24
|
+
## 14-Day Reliability Targets (v2.0.0 Cycle)
|
|
25
25
|
|
|
26
26
|
- weekly downloads: >= 180
|
|
27
27
|
- qualified deployment-intake submissions: >= 2
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Launch Log Template
|
|
2
|
+
|
|
3
|
+
Use this template to record public distribution actions and outcomes.
|
|
4
|
+
|
|
5
|
+
## Entry Format
|
|
6
|
+
|
|
7
|
+
| UTC Timestamp | Channel | Action | URL | Outcome | Notes |
|
|
8
|
+
|---|---|---|---|---|---|
|
|
9
|
+
| 2026-02-26T00:00:00Z | GitHub | Published release | <url> | success/pending/fail | <note> |
|
|
10
|
+
|
|
11
|
+
## Recommended Channels
|
|
12
|
+
|
|
13
|
+
- GitHub release
|
|
14
|
+
- npm publish
|
|
15
|
+
- MCP registry update
|
|
16
|
+
- Smithery update
|
|
17
|
+
- awesome-mcp-servers PR
|
|
18
|
+
- Glama listing update
|
|
19
|
+
- HN post/comment
|
|
20
|
+
- X thread
|
|
21
|
+
- Reddit post
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Version Parity Report
|
|
2
|
+
|
|
3
|
+
- Generated: 2026-02-26T12:45:39.684Z
|
|
4
|
+
- Local target version: 2.0.0
|
|
5
|
+
|
|
6
|
+
## Surface Matrix
|
|
7
|
+
|
|
8
|
+
| Surface | Version | Status | Notes |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| package.json | 2.0.0 | ok | |
|
|
11
|
+
| server.json (root) | 2.0.0 | ok | |
|
|
12
|
+
| server.json (package entry) | 2.0.0 | ok | |
|
|
13
|
+
| npm dist-tag latest | 1.2.4 | mismatch | |
|
|
14
|
+
| MCP Registry latest | 1.1.8 | mismatch | |
|
|
15
|
+
| Smithery endpoint | n/a | unreachable | check_error: HTTP 401 for https://server.smithery.ai/jtalk22/slack-mcp-server |
|
|
16
|
+
|
|
17
|
+
## Interpretation
|
|
18
|
+
|
|
19
|
+
- Local metadata parity: pass.
|
|
20
|
+
- External parity mismatch: npm latest, MCP registry latest.
|
|
21
|
+
- Propagation mode enabled: external mismatch accepted temporarily.
|
package/lib/handlers.js
CHANGED
|
@@ -95,17 +95,25 @@ export async function handleTokenStatus() {
|
|
|
95
95
|
const dmCache = loadDMCache();
|
|
96
96
|
const tokenStatus = health.reason === 'no_tokens'
|
|
97
97
|
? "missing"
|
|
98
|
-
: health.
|
|
99
|
-
? "
|
|
100
|
-
: health.
|
|
101
|
-
? "
|
|
102
|
-
:
|
|
98
|
+
: health.age_state === "unknown"
|
|
99
|
+
? "unknown_age"
|
|
100
|
+
: health.critical
|
|
101
|
+
? "critical"
|
|
102
|
+
: health.warning
|
|
103
|
+
? "warning"
|
|
104
|
+
: "healthy";
|
|
103
105
|
|
|
104
106
|
return asMcpJson({
|
|
105
107
|
status: tokenStatus,
|
|
106
|
-
code: health.reason
|
|
108
|
+
code: health.reason
|
|
109
|
+
|| (health.age_state === "unknown" ? "unknown_age" : null)
|
|
110
|
+
|| (health.critical ? "token_critical" : health.warning ? "token_warning" : "ok"),
|
|
107
111
|
message: health.message,
|
|
108
112
|
next_action: health.reason === 'no_tokens' ? "Run npx -y @jtalk22/slack-mcp --setup" : null,
|
|
113
|
+
details: {
|
|
114
|
+
age_known: health.age_known,
|
|
115
|
+
age_state: health.age_state
|
|
116
|
+
},
|
|
109
117
|
token: {
|
|
110
118
|
status: tokenStatus,
|
|
111
119
|
age_hours: health.age_hours,
|
package/lib/slack-client.js
CHANGED
|
@@ -97,7 +97,13 @@ export async function checkTokenHealth(logger = console) {
|
|
|
97
97
|
const creds = loadTokens(false, silentLogger, { autoExtract: false });
|
|
98
98
|
|
|
99
99
|
if (!creds) {
|
|
100
|
-
return {
|
|
100
|
+
return {
|
|
101
|
+
healthy: false,
|
|
102
|
+
reason: 'no_tokens',
|
|
103
|
+
age_known: false,
|
|
104
|
+
age_state: 'missing',
|
|
105
|
+
message: 'No credentials found'
|
|
106
|
+
};
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
const updatedAtMs = creds.updatedAt ? new Date(creds.updatedAt).getTime() : Number.NaN;
|
|
@@ -120,6 +126,8 @@ export async function checkTokenHealth(logger = console) {
|
|
|
120
126
|
healthy: true,
|
|
121
127
|
refreshed: true,
|
|
122
128
|
age_hours: 0,
|
|
129
|
+
age_known: true,
|
|
130
|
+
age_state: 'fresh',
|
|
123
131
|
source: 'chrome-auto',
|
|
124
132
|
message: 'Tokens refreshed successfully'
|
|
125
133
|
};
|
|
@@ -131,6 +139,14 @@ export async function checkTokenHealth(logger = console) {
|
|
|
131
139
|
return {
|
|
132
140
|
healthy: !hasKnownAge || tokenAge < TOKEN_CRITICAL_AGE,
|
|
133
141
|
age_hours: ageHours,
|
|
142
|
+
age_known: hasKnownAge,
|
|
143
|
+
age_state: !hasKnownAge
|
|
144
|
+
? 'unknown'
|
|
145
|
+
: tokenAge > TOKEN_CRITICAL_AGE
|
|
146
|
+
? 'critical'
|
|
147
|
+
: tokenAge > TOKEN_WARNING_AGE
|
|
148
|
+
? 'warning'
|
|
149
|
+
: 'healthy',
|
|
134
150
|
warning: hasKnownAge && tokenAge > TOKEN_WARNING_AGE,
|
|
135
151
|
critical: hasKnownAge && tokenAge > TOKEN_CRITICAL_AGE,
|
|
136
152
|
source: creds.source,
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jtalk22/slack-mcp",
|
|
3
3
|
"mcpName": "io.github.jtalk22/slack-mcp-server",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
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",
|
|
8
8
|
"bin": {
|
|
9
|
-
"slack-mcp": "
|
|
10
|
-
"slack-mcp-server": "
|
|
11
|
-
"slack-mcp-http": "
|
|
12
|
-
"slack-mcp-web": "
|
|
13
|
-
"slack-mcp-setup": "
|
|
9
|
+
"slack-mcp": "src/cli.js",
|
|
10
|
+
"slack-mcp-server": "src/server.js",
|
|
11
|
+
"slack-mcp-http": "src/server-http.js",
|
|
12
|
+
"slack-mcp-web": "src/web-server.js",
|
|
13
|
+
"slack-mcp-setup": "scripts/setup-wizard.js"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"start": "node src/server.js",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"screenshot": "node scripts/capture-screenshots.js",
|
|
27
27
|
"record-demo": "node scripts/record-demo.js",
|
|
28
28
|
"metrics:release-health": "node scripts/collect-release-health.js",
|
|
29
|
-
"metrics:release-health:delta": "node scripts/build-release-health-delta.js"
|
|
29
|
+
"metrics:release-health:delta": "node scripts/build-release-health-delta.js",
|
|
30
|
+
"verify:version-parity": "node scripts/check-version-parity.js"
|
|
30
31
|
},
|
|
31
32
|
"keywords": [
|
|
32
33
|
"mcp",
|
|
@@ -54,13 +55,20 @@
|
|
|
54
55
|
"channels",
|
|
55
56
|
"workspace",
|
|
56
57
|
"session-based",
|
|
58
|
+
"session-mirroring",
|
|
59
|
+
"slack-dm",
|
|
60
|
+
"slack-threads",
|
|
61
|
+
"workspace-search",
|
|
62
|
+
"mcp-tools",
|
|
63
|
+
"mcp-stdio",
|
|
64
|
+
"mcp-http",
|
|
57
65
|
"open-source"
|
|
58
66
|
],
|
|
59
67
|
"author": "jtalk22",
|
|
60
68
|
"license": "MIT",
|
|
61
69
|
"repository": {
|
|
62
70
|
"type": "git",
|
|
63
|
-
"url": "https://github.com/jtalk22/slack-mcp-server.git"
|
|
71
|
+
"url": "git+https://github.com/jtalk22/slack-mcp-server.git"
|
|
64
72
|
},
|
|
65
73
|
"homepage": "https://jtalk22.github.io/slack-mcp-server/public/demo.html",
|
|
66
74
|
"bugs": {
|
package/public/demo-claude.html
CHANGED
|
@@ -1161,7 +1161,7 @@
|
|
|
1161
1161
|
<header class="page-header">
|
|
1162
1162
|
<h1>
|
|
1163
1163
|
<span>Slack MCP Server</span>
|
|
1164
|
-
<span class="badge">🔧 MCP Demo
|
|
1164
|
+
<span class="badge">🔧 MCP Demo v2.0.0</span>
|
|
1165
1165
|
</h1>
|
|
1166
1166
|
<p>See how Claude uses MCP tools to access your Slack workspace</p>
|
|
1167
1167
|
</header>
|
|
@@ -1233,7 +1233,7 @@
|
|
|
1233
1233
|
<div class="title-logo">💬</div>
|
|
1234
1234
|
<h1>Slack MCP Server</h1>
|
|
1235
1235
|
<p class="title-tagline">Full Slack access for Claude Desktop</p>
|
|
1236
|
-
<p class="title-version">
|
|
1236
|
+
<p class="title-version">v2.0.0 • @jtalk22</p>
|
|
1237
1237
|
</div>
|
|
1238
1238
|
|
|
1239
1239
|
<!-- Scenario Caption Overlay -->
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const repoRoot = join(__dirname, "..");
|
|
9
|
+
|
|
10
|
+
const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
|
|
11
|
+
const serverMeta = JSON.parse(readFileSync(join(repoRoot, "server.json"), "utf8"));
|
|
12
|
+
|
|
13
|
+
const outputArg = process.argv.includes("--out")
|
|
14
|
+
? process.argv[process.argv.indexOf("--out") + 1]
|
|
15
|
+
: "docs/release-health/version-parity.md";
|
|
16
|
+
const allowPropagation = process.argv.includes("--allow-propagation");
|
|
17
|
+
|
|
18
|
+
const mcpServerName = serverMeta.name;
|
|
19
|
+
const smitheryEndpoint = "https://server.smithery.ai/jtalk22/slack-mcp-server";
|
|
20
|
+
|
|
21
|
+
async function fetchJson(url) {
|
|
22
|
+
const res = await fetch(url);
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
throw new Error(`HTTP ${res.status} for ${url}`);
|
|
25
|
+
}
|
|
26
|
+
return res.json();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function fetchText(url) {
|
|
30
|
+
const res = await fetch(url);
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
throw new Error(`HTTP ${res.status} for ${url}`);
|
|
33
|
+
}
|
|
34
|
+
return res.text();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function row(surface, version, status, note = "") {
|
|
38
|
+
return `| ${surface} | ${version || "n/a"} | ${status} | ${note} |`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function main() {
|
|
42
|
+
const localVersion = pkg.version;
|
|
43
|
+
const localServerVersion = serverMeta.version;
|
|
44
|
+
const localServerPkgVersion = serverMeta.packages?.[0]?.version || null;
|
|
45
|
+
|
|
46
|
+
let npmVersion = null;
|
|
47
|
+
let mcpRegistryVersion = null;
|
|
48
|
+
let smitheryReachable = null;
|
|
49
|
+
let npmError = null;
|
|
50
|
+
let mcpError = null;
|
|
51
|
+
let smitheryError = null;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const npmMeta = await fetchJson(`https://registry.npmjs.org/${encodeURIComponent(pkg.name)}`);
|
|
55
|
+
npmVersion = npmMeta?.["dist-tags"]?.latest || null;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
npmError = String(error?.message || error);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const registry = await fetchJson(
|
|
62
|
+
`https://registry.modelcontextprotocol.io/v0/servers/${encodeURIComponent(mcpServerName)}/versions/latest`
|
|
63
|
+
);
|
|
64
|
+
mcpRegistryVersion = registry?.server?.version || null;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
mcpError = String(error?.message || error);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const html = await fetchText(smitheryEndpoint);
|
|
71
|
+
smitheryReachable = html.length > 0;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
smitheryError = String(error?.message || error);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const parityChecks = [
|
|
77
|
+
{ name: "package.json vs server.json", ok: localVersion === localServerVersion },
|
|
78
|
+
{ name: "package.json vs server.json package", ok: localVersion === localServerPkgVersion },
|
|
79
|
+
{ name: "npm latest", ok: npmVersion === localVersion },
|
|
80
|
+
{ name: "MCP registry latest", ok: mcpRegistryVersion === localVersion },
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const externalMismatches = parityChecks
|
|
84
|
+
.filter((check) => !check.ok && (check.name === "npm latest" || check.name === "MCP registry latest"));
|
|
85
|
+
const hardFailures = parityChecks
|
|
86
|
+
.filter((check) => !check.ok && check.name !== "npm latest" && check.name !== "MCP registry latest");
|
|
87
|
+
|
|
88
|
+
const now = new Date().toISOString();
|
|
89
|
+
const lines = [
|
|
90
|
+
"# Version Parity Report",
|
|
91
|
+
"",
|
|
92
|
+
`- Generated: ${now}`,
|
|
93
|
+
`- Local target version: ${localVersion}`,
|
|
94
|
+
"",
|
|
95
|
+
"## Surface Matrix",
|
|
96
|
+
"",
|
|
97
|
+
"| Surface | Version | Status | Notes |",
|
|
98
|
+
"|---|---|---|---|",
|
|
99
|
+
row(
|
|
100
|
+
"package.json",
|
|
101
|
+
localVersion,
|
|
102
|
+
"ok"
|
|
103
|
+
),
|
|
104
|
+
row(
|
|
105
|
+
"server.json (root)",
|
|
106
|
+
localServerVersion,
|
|
107
|
+
localServerVersion === localVersion ? "ok" : "mismatch"
|
|
108
|
+
),
|
|
109
|
+
row(
|
|
110
|
+
"server.json (package entry)",
|
|
111
|
+
localServerPkgVersion,
|
|
112
|
+
localServerPkgVersion === localVersion ? "ok" : "mismatch"
|
|
113
|
+
),
|
|
114
|
+
row(
|
|
115
|
+
"npm dist-tag latest",
|
|
116
|
+
npmVersion,
|
|
117
|
+
npmVersion === localVersion ? "ok" : "mismatch",
|
|
118
|
+
npmError ? `fetch_error: ${npmError}` : ""
|
|
119
|
+
),
|
|
120
|
+
row(
|
|
121
|
+
"MCP Registry latest",
|
|
122
|
+
mcpRegistryVersion,
|
|
123
|
+
mcpRegistryVersion === localVersion ? "ok" : "mismatch",
|
|
124
|
+
mcpError ? `fetch_error: ${mcpError}` : ""
|
|
125
|
+
),
|
|
126
|
+
row(
|
|
127
|
+
"Smithery endpoint",
|
|
128
|
+
"n/a",
|
|
129
|
+
smitheryReachable ? "reachable" : "unreachable",
|
|
130
|
+
smitheryError ? `check_error: ${smitheryError}` : "Version check is manual."
|
|
131
|
+
),
|
|
132
|
+
"",
|
|
133
|
+
"## Interpretation",
|
|
134
|
+
"",
|
|
135
|
+
hardFailures.length === 0
|
|
136
|
+
? "- Local metadata parity: pass."
|
|
137
|
+
: `- Local metadata parity: fail (${hardFailures.map((f) => f.name).join(", ")}).`,
|
|
138
|
+
externalMismatches.length === 0
|
|
139
|
+
? "- External parity: pass."
|
|
140
|
+
: `- External parity mismatch: ${externalMismatches.map((f) => f.name).join(", ")}.`,
|
|
141
|
+
allowPropagation && externalMismatches.length > 0
|
|
142
|
+
? "- Propagation mode enabled: external mismatch accepted temporarily."
|
|
143
|
+
: "- Propagation mode disabled: external mismatch is a release gate failure.",
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const outPath = join(repoRoot, outputArg);
|
|
147
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
148
|
+
writeFileSync(outPath, `${lines.join("\n")}\n`, "utf8");
|
|
149
|
+
console.log(`Wrote ${outputArg}`);
|
|
150
|
+
|
|
151
|
+
if (hardFailures.length > 0) {
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
if (!allowPropagation && externalMismatches.length > 0) {
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
main().catch((error) => {
|
|
160
|
+
console.error(error);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
});
|
|
@@ -70,7 +70,7 @@ function buildMarkdown(data) {
|
|
|
70
70
|
lines.push(`- deployment-intake submissions (all-time): ${data.github.deploymentIntakeCount ?? "n/a"}`);
|
|
71
71
|
lines.push("");
|
|
72
72
|
|
|
73
|
-
lines.push("## 14-Day Reliability Targets (
|
|
73
|
+
lines.push("## 14-Day Reliability Targets (v2.0.0 Cycle)");
|
|
74
74
|
lines.push("");
|
|
75
75
|
lines.push("- weekly downloads: >= 180");
|
|
76
76
|
lines.push("- qualified deployment-intake submissions: >= 2");
|
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 = "2.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,13 +1,15 @@
|
|
|
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 localVersion = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")).version;
|
|
11
13
|
|
|
12
14
|
function runNpx(args, options = {}) {
|
|
13
15
|
const cmdArgs = ["-y", PKG, ...args];
|
|
@@ -97,6 +99,18 @@ function main() {
|
|
|
97
99
|
"Expected --version to exit 0",
|
|
98
100
|
versionResult.stderr || versionResult.stdout,
|
|
99
101
|
);
|
|
102
|
+
const publishedMatchesLocal = versionResult.stdout.includes(localVersion);
|
|
103
|
+
if (strictPublished) {
|
|
104
|
+
assert(
|
|
105
|
+
publishedMatchesLocal,
|
|
106
|
+
`Expected published npx version to match local ${localVersion}`,
|
|
107
|
+
versionResult.stdout,
|
|
108
|
+
);
|
|
109
|
+
} else if (!publishedMatchesLocal) {
|
|
110
|
+
console.log(
|
|
111
|
+
`warning: npx resolved ${versionResult.stdout || "unknown"} while local version is ${localVersion}; strict published checks are deferred until publish.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
100
114
|
|
|
101
115
|
const helpResult = runNpx(["--help"], { cwd: testHome, env });
|
|
102
116
|
printResult("help", helpResult);
|
|
@@ -113,6 +127,15 @@ function main() {
|
|
|
113
127
|
"Expected --status to exit non-zero when credentials are missing",
|
|
114
128
|
statusResult.stderr || statusResult.stdout,
|
|
115
129
|
);
|
|
130
|
+
if (strictPublished) {
|
|
131
|
+
assert(
|
|
132
|
+
!statusResult.stderr.includes("Attempting Chrome auto-extraction"),
|
|
133
|
+
"Expected npx --status to be read-only without auto-extraction side effects",
|
|
134
|
+
statusResult.stderr,
|
|
135
|
+
);
|
|
136
|
+
} else if (statusResult.stderr.includes("Attempting Chrome auto-extraction")) {
|
|
137
|
+
console.log("warning: published npx --status still has extraction side effects; re-run with --strict-published after publish.");
|
|
138
|
+
}
|
|
116
139
|
|
|
117
140
|
const localStatusResult = runLocalSetupStatus({ env });
|
|
118
141
|
printResult("local-status", localStatusResult);
|
|
@@ -148,6 +171,18 @@ function main() {
|
|
|
148
171
|
localDoctorInvalidResult.stderr || localDoctorInvalidResult.stdout,
|
|
149
172
|
);
|
|
150
173
|
|
|
174
|
+
const runtimeEnv = {
|
|
175
|
+
...invalidEnv,
|
|
176
|
+
SLACK_MCP_AUTH_TEST_URL: "http://127.0.0.1:9/auth.test"
|
|
177
|
+
};
|
|
178
|
+
const localDoctorRuntimeResult = runLocalDoctor({ env: runtimeEnv });
|
|
179
|
+
printResult("local-doctor-runtime", localDoctorRuntimeResult);
|
|
180
|
+
assert(
|
|
181
|
+
localDoctorRuntimeResult.status === 3,
|
|
182
|
+
"Expected local --doctor to exit 3 when runtime connectivity fails",
|
|
183
|
+
localDoctorRuntimeResult.stderr || localDoctorRuntimeResult.stdout,
|
|
184
|
+
);
|
|
185
|
+
|
|
151
186
|
console.log("\nInstall flow verification passed.");
|
|
152
187
|
} finally {
|
|
153
188
|
rmSync(testHome, { recursive: true, force: true });
|
package/src/server-http.js
CHANGED
|
@@ -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 = "
|
|
33
|
+
const SERVER_VERSION = "2.0.0";
|
|
34
34
|
const PORT = process.env.PORT || 3000;
|
|
35
35
|
|
|
36
36
|
// Create MCP server
|
|
@@ -74,13 +74,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
74
74
|
return await handleListUsers(args);
|
|
75
75
|
default:
|
|
76
76
|
return {
|
|
77
|
-
content: [{
|
|
77
|
+
content: [{
|
|
78
|
+
type: "text",
|
|
79
|
+
text: JSON.stringify({
|
|
80
|
+
status: "error",
|
|
81
|
+
code: "unknown_tool",
|
|
82
|
+
message: `Unknown tool: ${name}`,
|
|
83
|
+
next_action: "Call tools/list to inspect available tool names."
|
|
84
|
+
}, null, 2)
|
|
85
|
+
}],
|
|
78
86
|
isError: true
|
|
79
87
|
};
|
|
80
88
|
}
|
|
81
89
|
} catch (error) {
|
|
82
90
|
return {
|
|
83
|
-
content: [{
|
|
91
|
+
content: [{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: JSON.stringify({
|
|
94
|
+
status: "error",
|
|
95
|
+
code: "tool_call_failed",
|
|
96
|
+
message: String(error?.message || error),
|
|
97
|
+
next_action: "Retry with validated input payload."
|
|
98
|
+
}, null, 2)
|
|
99
|
+
}],
|
|
84
100
|
isError: true
|
|
85
101
|
};
|
|
86
102
|
}
|
|
@@ -110,7 +126,13 @@ const httpServer = http.createServer(async (req, res) => {
|
|
|
110
126
|
// Health check endpoint
|
|
111
127
|
if (req.url === '/health') {
|
|
112
128
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
113
|
-
res.end(JSON.stringify({
|
|
129
|
+
res.end(JSON.stringify({
|
|
130
|
+
status: 'ok',
|
|
131
|
+
code: 'ok',
|
|
132
|
+
message: 'HTTP transport healthy',
|
|
133
|
+
server: SERVER_NAME,
|
|
134
|
+
version: SERVER_VERSION
|
|
135
|
+
}));
|
|
114
136
|
return;
|
|
115
137
|
}
|
|
116
138
|
|
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
|
|
14
|
+
* @version 2.0.0
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -47,7 +47,7 @@ const BACKGROUND_REFRESH_INTERVAL = 4 * 60 * 60 * 1000;
|
|
|
47
47
|
|
|
48
48
|
// Package info
|
|
49
49
|
const SERVER_NAME = "slack-mcp-server";
|
|
50
|
-
const SERVER_VERSION = "
|
|
50
|
+
const SERVER_VERSION = "2.0.0";
|
|
51
51
|
|
|
52
52
|
// MCP Prompts - predefined prompt templates for common Slack operations
|
|
53
53
|
const PROMPTS = [
|
|
@@ -256,13 +256,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
256
256
|
|
|
257
257
|
default:
|
|
258
258
|
return {
|
|
259
|
-
content: [{
|
|
259
|
+
content: [{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: JSON.stringify({
|
|
262
|
+
status: "error",
|
|
263
|
+
code: "unknown_tool",
|
|
264
|
+
message: `Unknown tool: ${name}`,
|
|
265
|
+
next_action: "Use tools/list to discover available tools."
|
|
266
|
+
}, null, 2)
|
|
267
|
+
}],
|
|
260
268
|
isError: true
|
|
261
269
|
};
|
|
262
270
|
}
|
|
263
271
|
} catch (error) {
|
|
264
272
|
return {
|
|
265
|
-
content: [{
|
|
273
|
+
content: [{
|
|
274
|
+
type: "text",
|
|
275
|
+
text: JSON.stringify({
|
|
276
|
+
status: "error",
|
|
277
|
+
code: "tool_call_failed",
|
|
278
|
+
message: String(error?.message || error),
|
|
279
|
+
next_action: "Retry the call and include full arguments."
|
|
280
|
+
}, null, 2)
|
|
281
|
+
}],
|
|
266
282
|
isError: true
|
|
267
283
|
};
|
|
268
284
|
}
|
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
|
|
8
|
+
* @version 2.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import express from "express";
|
|
@@ -95,11 +95,27 @@ function authenticate(req, res, next) {
|
|
|
95
95
|
const providedKey = authHeader?.replace("Bearer ", "");
|
|
96
96
|
|
|
97
97
|
if (providedKey !== API_KEY) {
|
|
98
|
-
return res.status(401).json({
|
|
98
|
+
return res.status(401).json({
|
|
99
|
+
status: "error",
|
|
100
|
+
code: "unauthorized",
|
|
101
|
+
message: "Invalid API key",
|
|
102
|
+
next_action: "Provide Authorization: Bearer <api-key>."
|
|
103
|
+
});
|
|
99
104
|
}
|
|
100
105
|
next();
|
|
101
106
|
}
|
|
102
107
|
|
|
108
|
+
function sendStructuredError(res, statusCode, code, message, nextAction = null, details = null) {
|
|
109
|
+
const payload = {
|
|
110
|
+
status: "error",
|
|
111
|
+
code,
|
|
112
|
+
message,
|
|
113
|
+
next_action: nextAction,
|
|
114
|
+
};
|
|
115
|
+
if (details) payload.details = details;
|
|
116
|
+
return res.status(statusCode).json(payload);
|
|
117
|
+
}
|
|
118
|
+
|
|
103
119
|
// Helper to extract response content
|
|
104
120
|
function extractContent(result) {
|
|
105
121
|
if (result.content && result.content[0] && result.content[0].text) {
|
|
@@ -116,8 +132,10 @@ function extractContent(result) {
|
|
|
116
132
|
app.get("/", (req, res) => {
|
|
117
133
|
res.json({
|
|
118
134
|
name: "Slack Web API Server",
|
|
119
|
-
version: "
|
|
120
|
-
status: "
|
|
135
|
+
version: "2.0.0",
|
|
136
|
+
status: "ok",
|
|
137
|
+
code: "ok",
|
|
138
|
+
message: "Web API server is running.",
|
|
121
139
|
endpoints: [
|
|
122
140
|
"GET /health",
|
|
123
141
|
"POST /refresh",
|
|
@@ -140,7 +158,7 @@ app.get("/token-status", authenticate, async (req, res) => {
|
|
|
140
158
|
const result = await handleTokenStatus();
|
|
141
159
|
res.json(extractContent(result));
|
|
142
160
|
} catch (e) {
|
|
143
|
-
res
|
|
161
|
+
sendStructuredError(res, 500, "token_status_failed", String(e?.message || e));
|
|
144
162
|
}
|
|
145
163
|
});
|
|
146
164
|
|
|
@@ -148,9 +166,13 @@ app.get("/token-status", authenticate, async (req, res) => {
|
|
|
148
166
|
app.get("/health", authenticate, async (req, res) => {
|
|
149
167
|
try {
|
|
150
168
|
const result = await handleHealthCheck();
|
|
151
|
-
|
|
169
|
+
const payload = extractContent(result);
|
|
170
|
+
if (result.isError) {
|
|
171
|
+
return res.status(400).json(payload);
|
|
172
|
+
}
|
|
173
|
+
res.json(payload);
|
|
152
174
|
} catch (e) {
|
|
153
|
-
res
|
|
175
|
+
sendStructuredError(res, 500, "health_check_failed", String(e?.message || e));
|
|
154
176
|
}
|
|
155
177
|
});
|
|
156
178
|
|
|
@@ -158,9 +180,13 @@ app.get("/health", authenticate, async (req, res) => {
|
|
|
158
180
|
app.post("/refresh", authenticate, async (req, res) => {
|
|
159
181
|
try {
|
|
160
182
|
const result = await handleRefreshTokens();
|
|
161
|
-
|
|
183
|
+
const payload = extractContent(result);
|
|
184
|
+
if (result.isError) {
|
|
185
|
+
return res.status(400).json(payload);
|
|
186
|
+
}
|
|
187
|
+
res.json(payload);
|
|
162
188
|
} catch (e) {
|
|
163
|
-
res
|
|
189
|
+
sendStructuredError(res, 500, "refresh_failed", String(e?.message || e));
|
|
164
190
|
}
|
|
165
191
|
});
|
|
166
192
|
|
|
@@ -173,7 +199,7 @@ app.get("/conversations", authenticate, async (req, res) => {
|
|
|
173
199
|
});
|
|
174
200
|
res.json(extractContent(result));
|
|
175
201
|
} catch (e) {
|
|
176
|
-
res
|
|
202
|
+
sendStructuredError(res, 500, "list_conversations_failed", String(e?.message || e));
|
|
177
203
|
}
|
|
178
204
|
});
|
|
179
205
|
|
|
@@ -189,7 +215,7 @@ app.get("/conversations/:id/history", authenticate, async (req, res) => {
|
|
|
189
215
|
});
|
|
190
216
|
res.json(extractContent(result));
|
|
191
217
|
} catch (e) {
|
|
192
|
-
res
|
|
218
|
+
sendStructuredError(res, 500, "history_failed", String(e?.message || e));
|
|
193
219
|
}
|
|
194
220
|
});
|
|
195
221
|
|
|
@@ -206,7 +232,7 @@ app.get("/conversations/:id/full", authenticate, async (req, res) => {
|
|
|
206
232
|
});
|
|
207
233
|
res.json(extractContent(result));
|
|
208
234
|
} catch (e) {
|
|
209
|
-
res
|
|
235
|
+
sendStructuredError(res, 500, "full_export_failed", String(e?.message || e));
|
|
210
236
|
}
|
|
211
237
|
});
|
|
212
238
|
|
|
@@ -219,7 +245,7 @@ app.get("/conversations/:id/thread/:ts", authenticate, async (req, res) => {
|
|
|
219
245
|
});
|
|
220
246
|
res.json(extractContent(result));
|
|
221
247
|
} catch (e) {
|
|
222
|
-
res
|
|
248
|
+
sendStructuredError(res, 500, "thread_fetch_failed", String(e?.message || e));
|
|
223
249
|
}
|
|
224
250
|
});
|
|
225
251
|
|
|
@@ -227,7 +253,13 @@ app.get("/conversations/:id/thread/:ts", authenticate, async (req, res) => {
|
|
|
227
253
|
app.get("/search", authenticate, async (req, res) => {
|
|
228
254
|
try {
|
|
229
255
|
if (!req.query.q) {
|
|
230
|
-
return
|
|
256
|
+
return sendStructuredError(
|
|
257
|
+
res,
|
|
258
|
+
400,
|
|
259
|
+
"invalid_request",
|
|
260
|
+
"Query parameter 'q' is required",
|
|
261
|
+
"Set the q query string and retry."
|
|
262
|
+
);
|
|
231
263
|
}
|
|
232
264
|
const result = await handleSearchMessages({
|
|
233
265
|
query: req.query.q,
|
|
@@ -235,7 +267,7 @@ app.get("/search", authenticate, async (req, res) => {
|
|
|
235
267
|
});
|
|
236
268
|
res.json(extractContent(result));
|
|
237
269
|
} catch (e) {
|
|
238
|
-
res
|
|
270
|
+
sendStructuredError(res, 500, "search_failed", String(e?.message || e));
|
|
239
271
|
}
|
|
240
272
|
});
|
|
241
273
|
|
|
@@ -243,7 +275,13 @@ app.get("/search", authenticate, async (req, res) => {
|
|
|
243
275
|
app.post("/messages", authenticate, async (req, res) => {
|
|
244
276
|
try {
|
|
245
277
|
if (!req.body.channel_id || !req.body.text) {
|
|
246
|
-
return
|
|
278
|
+
return sendStructuredError(
|
|
279
|
+
res,
|
|
280
|
+
400,
|
|
281
|
+
"invalid_request",
|
|
282
|
+
"channel_id and text are required",
|
|
283
|
+
"Include channel_id and text in request JSON."
|
|
284
|
+
);
|
|
247
285
|
}
|
|
248
286
|
const result = await handleSendMessage({
|
|
249
287
|
channel_id: req.body.channel_id,
|
|
@@ -252,7 +290,7 @@ app.post("/messages", authenticate, async (req, res) => {
|
|
|
252
290
|
});
|
|
253
291
|
res.json(extractContent(result));
|
|
254
292
|
} catch (e) {
|
|
255
|
-
res
|
|
293
|
+
sendStructuredError(res, 500, "send_message_failed", String(e?.message || e));
|
|
256
294
|
}
|
|
257
295
|
});
|
|
258
296
|
|
|
@@ -264,7 +302,7 @@ app.get("/users", authenticate, async (req, res) => {
|
|
|
264
302
|
});
|
|
265
303
|
res.json(extractContent(result));
|
|
266
304
|
} catch (e) {
|
|
267
|
-
res
|
|
305
|
+
sendStructuredError(res, 500, "list_users_failed", String(e?.message || e));
|
|
268
306
|
}
|
|
269
307
|
});
|
|
270
308
|
|
|
@@ -276,7 +314,7 @@ app.get("/users/:id", authenticate, async (req, res) => {
|
|
|
276
314
|
});
|
|
277
315
|
res.json(extractContent(result));
|
|
278
316
|
} catch (e) {
|
|
279
|
-
res
|
|
317
|
+
sendStructuredError(res, 500, "user_info_failed", String(e?.message || e));
|
|
280
318
|
}
|
|
281
319
|
});
|
|
282
320
|
|
|
@@ -295,7 +333,7 @@ async function main() {
|
|
|
295
333
|
app.listen(PORT, '127.0.0.1', () => {
|
|
296
334
|
// Print to stderr to keep logs clean (stdout reserved for JSON in some setups)
|
|
297
335
|
console.error(`\n${"═".repeat(60)}`);
|
|
298
|
-
console.error(` Slack Web API Server
|
|
336
|
+
console.error(` Slack Web API Server v2.0.0`);
|
|
299
337
|
console.error(`${"═".repeat(60)}`);
|
|
300
338
|
console.error(`\n Dashboard: http://localhost:${PORT}/?key=${API_KEY}`);
|
|
301
339
|
console.error(`\n API Key: ${API_KEY}`);
|