@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 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 v1.2.x`
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 v1.2.4
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 as copy/paste launch material with minimal edits.
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 (session-based Slack access for Claude)`
8
- - `Show HN: Slack MCP Server (local-first Slack context for Claude)`
9
- - `Show HN: Slack MCP Server (Slack MCP for local and hosted runtimes)`
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
- Built a session-based Slack MCP server so Claude can use the same access already available in your Slack web session.
14
+ Released `@jtalk22/slack-mcp@2.0.0` today.
15
15
 
16
- Current scope:
17
- - DM/channel/thread reads
18
- - workspace search
19
- - message sends and user lookups
20
- - local web mode when MCP is unavailable
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
- Notes up front:
35
- - Local-first usage is fully supported.
36
- - Tokens are Slack session credentials and are stored locally by default.
37
- - macOS supports automatic Chrome extraction; Linux/Windows use guided manual setup.
38
- - Current docs include deployment modes, support boundaries, and troubleshooting.
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 install fails, include OS, Node version, runtime mode (`stdio|web|http|worker`), and exact error output.
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
- ### Is this using Slack app OAuth scopes?
46
- No. It uses existing signed-in session permissions.
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. The default path is local/self-hosted first. Deployment docs describe hosted tradeoffs.
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
- ## Install Check Block
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
@@ -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-26T09:32:53.328Z
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.3
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 (v1.2.3 Cycle)
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-26T09:32:53.328Z
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.3
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 (v1.2.3 Cycle)
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.critical
99
- ? "critical"
100
- : health.warning
101
- ? "warning"
102
- : "healthy";
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 || (health.critical ? "token_critical" : health.warning ? "token_warning" : "ok"),
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,
@@ -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 { healthy: false, reason: 'no_tokens', message: 'No credentials found' };
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": "1.2.4",
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": "./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"
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": {
@@ -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 v1.2.4</span>
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">v1.2.4 • @jtalk22</p>
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 (v1.2.4 Cycle)");
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");
@@ -23,8 +23,9 @@ import {
23
23
  } from "../lib/token-store.js";
24
24
 
25
25
  const IS_MACOS = platform() === 'darwin';
26
- const VERSION = "1.2.4";
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("https://slack.com/api/auth.test", {
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 });
@@ -30,7 +30,7 @@ import {
30
30
  } from "../lib/handlers.js";
31
31
 
32
32
  const SERVER_NAME = "slack-mcp-server";
33
- const SERVER_VERSION = "1.2.4";
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: [{ type: "text", text: `Unknown tool: ${name}` }],
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: [{ type: "text", text: `Error: ${error.message}` }],
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({ status: 'ok', server: SERVER_NAME, version: SERVER_VERSION }));
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 1.2.4
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 = "1.2.4";
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: [{ type: "text", text: `Unknown tool: ${name}` }],
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: [{ type: "text", text: `Error: ${error.message}` }],
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 1.2.4
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({ error: "Invalid API key" });
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: "1.2.4",
120
- status: "running",
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.status(500).json({ error: e.message });
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
- res.json(extractContent(result));
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.status(500).json({ error: e.message });
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
- res.json(extractContent(result));
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.status(500).json({ error: e.message });
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.status(500).json({ error: e.message });
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.status(500).json({ error: e.message });
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.status(500).json({ error: e.message });
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.status(500).json({ error: e.message });
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 res.status(400).json({ error: "Query parameter 'q' is required" });
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.status(500).json({ error: e.message });
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 res.status(400).json({ error: "channel_id and text are required" });
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.status(500).json({ error: e.message });
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.status(500).json({ error: e.message });
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.status(500).json({ error: e.message });
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 v1.2.4`);
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}`);