@jtalk22/slack-mcp 4.1.0 → 4.2.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 +52 -7
- package/docs/DEPLOYMENT-MODES.md +56 -0
- package/docs/TROUBLESHOOTING.md +3 -1
- package/lib/handlers.js +92 -0
- package/lib/public-metadata.js +1 -1
- package/lib/public-pages.js +4 -4
- package/lib/slack-client.js +70 -17
- package/lib/token-store.js +279 -93
- package/lib/tools.js +160 -0
- package/lib/workflow-store.js +188 -0
- package/package.json +6 -3
- package/public/index.html +5 -4
- package/public/share.html +5 -5
- package/scripts/apply-template.js +117 -0
- package/scripts/setup-wizard.js +19 -0
- package/server.json +3 -3
- package/smithery.yaml +2 -0
- package/src/cli.js +3 -0
- package/src/server.js +58 -2
- package/templates/workflow-profiles/customer-feedback.json +10 -0
- package/templates/workflow-profiles/exec-monday.json +10 -0
- package/templates/workflow-profiles/incident-room.json +10 -0
- package/templates/workflow-profiles/oncall-handoff.json +10 -0
- package/templates/workflow-profiles/sprint-tracker.json +10 -0
- package/templates/workflow-profiles/support-triage.json +10 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://registry.modelcontextprotocol.io)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
Give your AI agent full Slack access. No app registration, no admin approval, no OAuth. One command,
|
|
7
|
+
Give your AI agent full Slack access — and structured workflow output the AI can actually use. No app registration, no admin approval, no OAuth. One command, 21 tools, works with any MCP client.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx -y @jtalk22/slack-mcp --setup
|
|
@@ -24,7 +24,7 @@ Slack's official MCP server requires a registered app, admin approval, and [does
|
|
|
24
24
|
|
|
25
25
|
This server uses your browser's session tokens instead. If you can see it in Slack, your AI agent can see it too. No app install, no scopes, no admin.
|
|
26
26
|
|
|
27
|
-
**
|
|
27
|
+
**Session-token transport:** No bot user appears in the workspace admin panel, no app install shows up, no audit trail entry is created. Your AI agent operates with the same workspace footprint as your browser tab — nothing more, nothing less.
|
|
28
28
|
|
|
29
29
|

|
|
30
30
|
|
|
@@ -39,8 +39,28 @@ This server uses your browser's session tokens instead. If you can see it in Sla
|
|
|
39
39
|
| Works with Gemini CLI | No | **Yes** |
|
|
40
40
|
| Works with Codex CLI | No | **Yes** |
|
|
41
41
|
| Setup time | ~30 min | **~2 min** |
|
|
42
|
-
| Tools | Limited | **
|
|
43
|
-
| Visible to admins | Yes | **No —
|
|
42
|
+
| Tools | Limited | **21** |
|
|
43
|
+
| Visible to admins | Yes | **No — session-token transport** |
|
|
44
|
+
|
|
45
|
+
## Workflow Primitives (new in 4.2)
|
|
46
|
+
|
|
47
|
+
Save a workflow profile that binds a `workflow_kind` to channels + priority people + retention + cadence. Stored locally at `~/.slack-mcp-workflows.json`. The hosted brain at [mcp.revasserlabs.com](https://mcp.revasserlabs.com) reads these profiles and returns **structured JSON per workflow_kind** — downstream automation (Linear, Notion, status dashboards) consumes the JSON directly.
|
|
48
|
+
|
|
49
|
+
| `workflow_kind` | Returns (structured JSON) |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `incident_room` | `{incident_summary, timeline, open_risks, owner_gaps, next_actions}` |
|
|
52
|
+
| `exec_brief` | `{summary, decisions, risks, asks, action_items}` |
|
|
53
|
+
| `support_inbox` | `{open_threads, ack_lag, owner_gaps, escalations, next_actions}` |
|
|
54
|
+
| `product_launch_watch` | `{launch_signals, feedback_themes, blockers, metrics, next_actions}` |
|
|
55
|
+
| `custom` | `{summary, highlights, open_questions, next_actions}` |
|
|
56
|
+
|
|
57
|
+
Six prebuilt templates ship with the package:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx -y @jtalk22/slack-mcp --apply-template oncall-handoff --channels C012345,C067890
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Available templates: `oncall-handoff`, `support-triage`, `exec-monday`, `sprint-tracker`, `customer-feedback`, `incident-room`. The structural primitives (`slack_workflow_save`, `slack_workflows`) are free forever in OSS; the hosted brain is `$0` to start (no card) and `$9/mo` Pro for unlimited AI tools (scheduled morning catch-up DM rolling out Q2 2026).
|
|
44
64
|
|
|
45
65
|
## Quick Start per Client
|
|
46
66
|
|
|
@@ -129,11 +149,18 @@ Or via CLI: `codex mcp add slack -- npx -y @jtalk22/slack-mcp`
|
|
|
129
149
|
| `slack_add_reaction` | Add an emoji reaction to a message | **destructive** |
|
|
130
150
|
| `slack_remove_reaction` | Remove an emoji reaction from a message | **destructive** |
|
|
131
151
|
| `slack_conversations_mark` | Mark a conversation as read | **destructive** |
|
|
152
|
+
| `slack_workflow_save` | Save a workflow profile (channels, kind, retention, cadence) to `~/.slack-mcp-workflows.json` | local-write |
|
|
153
|
+
| `slack_workflows` | List saved workflow profiles | read-only |
|
|
154
|
+
| `slack_smart_search` | Semantic search across indexed channels — hosted brain | hosted-stub† |
|
|
155
|
+
| `slack_catch_me_up` | AI-summarized digest of unreads + priority threads — hosted brain | hosted-stub† |
|
|
156
|
+
| `slack_triage` | Prioritized action queue across channels — hosted brain | hosted-stub† |
|
|
132
157
|
|
|
133
|
-
12 read-only, 4 write-path. All carry [MCP safety annotations](https://modelcontextprotocol.io/specification/2025-03-26/server/tools#annotations).
|
|
158
|
+
21 tools total: 12 read-only Slack, 4 write-path Slack, 2 workflow profile primitives (1 local-write, 1 read-only), 3 hosted stubs. All carry [MCP safety annotations](https://modelcontextprotocol.io/specification/2025-03-26/server/tools#annotations).
|
|
134
159
|
|
|
135
160
|
\* `slack_refresh_tokens` modifies local token file only.
|
|
136
161
|
|
|
162
|
+
† Hosted stubs return a structured upgrade payload (`signup_url`, `free_tier_quota`, `pro_value_prop`) — no Slack write occurs from OSS. Activate the brain at [mcp.revasserlabs.com](https://mcp.revasserlabs.com) (free tier, no card).
|
|
163
|
+
|
|
137
164
|
## Install
|
|
138
165
|
|
|
139
166
|
**Node.js 20+**
|
|
@@ -230,7 +257,15 @@ On macOS, tokens are auto-extracted from Chrome — `env` block is optional.
|
|
|
230
257
|
<details>
|
|
231
258
|
<summary><strong>Claude Web / Remote MCP</strong></summary>
|
|
232
259
|
|
|
233
|
-
Hosted
|
|
260
|
+
Hosted tiers at [mcp.revasserlabs.com](https://mcp.revasserlabs.com):
|
|
261
|
+
|
|
262
|
+
| Tier | Price | What it owns |
|
|
263
|
+
|------|-------|-------------|
|
|
264
|
+
| Self-host | Free (MIT) | Local stdio, all 21 tools (16 read/write Slack + 2 workflow profile primitives + 3 discoverable upgrade stubs to hosted brain) |
|
|
265
|
+
| Hosted Free | $0 (no card) | Email signup, 1 workspace, 10 smart_search/mo + 3 catch_me_up/mo + 5 triage/day. All 5 workflow profile types. 7-day index retention. |
|
|
266
|
+
| Pro | $9/mo | Unlimited AI tools, **scheduled morning catch-up DM** *(rolling out Q2 2026, 8am workspace tz)*, permanent OAuth, 90-day Vectorize, 2 workspaces |
|
|
267
|
+
| Team | $49/mo flat | Pro + shared workflow profiles + audit log + 24h support + scheduled catch-up to channel + 5 workspaces |
|
|
268
|
+
| Ops | from $199/mo (custom) | SLA, custom retention, SOC2 evidence path, multi-tenant isolation, 10+ workspaces, dedicated workflow tuning |
|
|
234
269
|
|
|
235
270
|
</details>
|
|
236
271
|
|
|
@@ -270,6 +305,16 @@ Session tokens (`xoxc-` + `xoxd-`) from your browser. If you can see it in Slack
|
|
|
270
305
|
|
|
271
306
|
Tokens expire. The server notices before you do — proactive health monitoring, automatic refresh on macOS, warnings when tokens age out. File writes are atomic (temp file → chmod → rename) to prevent corruption. Concurrent refresh attempts are mutex-locked.
|
|
272
307
|
|
|
308
|
+
## What's New in 4.2.0
|
|
309
|
+
|
|
310
|
+
- **Workflow primitives** — `slack_workflow_save` + `slack_workflows` bind a `workflow_kind` (`incident_room`, `exec_brief`, `support_inbox`, `product_launch_watch`, `custom`) to channels, priority people, retention, and cadence. The hosted brain returns structured JSON per kind — `incident_room` returns `{incident_summary, timeline, open_risks, owner_gaps, next_actions}`, `exec_brief` returns `{summary, decisions, risks, asks, action_items}`. Downstream automation (Linear, Notion, dashboards) consumes the JSON directly.
|
|
311
|
+
- **Discoverable upgrade stubs** — `slack_smart_search`, `slack_catch_me_up`, `slack_triage` appear in OSS as upgrade payloads pointing at the hosted brain. Response shape is `{signup_url, free_tier_quota, pro_value_prop}` — no interruptions, the AI routes the user cleanly.
|
|
312
|
+
- **Six prebuilt templates** — apply with `npx -y @jtalk22/slack-mcp --apply-template <name> --channels C012,C034`. Names: `oncall-handoff`, `support-triage`, `exec-monday`, `sprint-tracker`, `customer-feedback`, `incident-room`. Read them, fork them, edit them — they're JSON profiles.
|
|
313
|
+
- **Setup wizard hosted bridge** — six in-wizard moments surface the hosted free tier (no card) where it matches the user's pain. Stays out of the way otherwise.
|
|
314
|
+
- **Prior reliability fixes carried forward** — LevelDB token extraction, multi-profile enumeration, and explicit SIGTERM/SIGINT/SIGHUP/stdin shutdown handlers ship in 4.2.0 too.
|
|
315
|
+
|
|
316
|
+
Full release notes on [GitHub releases/latest](https://github.com/jtalk22/slack-mcp-server/releases/latest).
|
|
317
|
+
|
|
273
318
|
## Hosted HTTP Mode
|
|
274
319
|
|
|
275
320
|
For remote MCP endpoints (Cloudflare Worker, VPS, etc.):
|
|
@@ -326,4 +371,4 @@ Not affiliated with Slack Technologies, Inc. Uses browser session credentials
|
|
|
326
371
|
|
|
327
372
|
---
|
|
328
373
|
|
|
329
|
-
Hosted version
|
|
374
|
+
Hosted version live at [mcp.revasserlabs.com](https://mcp.revasserlabs.com): Free tier (no card), $9/mo Pro, $49/mo Team flat, Ops from $199/mo. Hosted owns the AI brain (smart_search, catch_me_up, triage), the scheduled morning catch-up DM at 8am workspace time *(rolling out Q2 2026)*, permanent OAuth (no 2-week token rotation), 90-day Vectorize retention, and shared workflow profiles. The OSS package owns local stdio + all 16 read/write Slack tools + workflow profile primitives (slack_workflow_save, slack_workflows). The 3 paid stubs (slack_smart_search, slack_catch_me_up, slack_triage) appear in OSS as discoverable upgrade prompts.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Deployment Modes
|
|
2
|
+
|
|
3
|
+
Use this guide to choose the right operating mode before rollout.
|
|
4
|
+
|
|
5
|
+
## Quick Chooser
|
|
6
|
+
|
|
7
|
+
- Choose `stdio` for personal self-hosted use in Claude Desktop/Claude Code.
|
|
8
|
+
- Choose local `web` for browser workflows and manual Slack browsing.
|
|
9
|
+
- Choose hosted HTTP only when you need remote execution and can handle token operations.
|
|
10
|
+
- Choose Smithery/Worker only when your consumers require registry-hosted MCP transport.
|
|
11
|
+
- A managed **Hosted** version is live at [mcp.revasserlabs.com](https://mcp.revasserlabs.com) — Free tier (no card), Pro $9/mo, Team $49/mo flat, Ops from $199/mo. See [pricing](https://mcp.revasserlabs.com/pricing).
|
|
12
|
+
|
|
13
|
+
## Mode Matrix
|
|
14
|
+
|
|
15
|
+
| Mode | Start Command | Best For | Auth Material | Exposure | Notes |
|
|
16
|
+
|------|---------------|----------|---------------|----------|-------|
|
|
17
|
+
| Local MCP (`stdio`) | `npx -y @jtalk22/slack-mcp` | Individual daily usage in Claude | `SLACK_TOKEN` + `SLACK_COOKIE` via token file/env | Local process | Lowest ops burden. Free. 21 tools (16 read/write + 2 workflow profile primitives + 3 discoverable upgrade stubs to hosted). |
|
|
18
|
+
| Local Web UI (`web`) | `npx -y @jtalk22/slack-mcp web` | Browser-first usage, manual search/send | Same as above + generated API key | `localhost` by default | Useful when MCP is not available |
|
|
19
|
+
| Hosted MCP (`http`) | `node src/server-http.js` | Controlled hosted integration | Env-injected Slack token/cookie + HTTP bearer token | Remote endpoint | `/mcp` is bearer-protected by default; configure CORS allowlist |
|
|
20
|
+
| Smithery/Worker | `wrangler deploy` + Smithery publish flow | Registry distribution for hosted consumers | Query/env token handoff | Remote endpoint | Keep worker version parity with npm release |
|
|
21
|
+
|
|
22
|
+
## Team Deployment Guidance
|
|
23
|
+
|
|
24
|
+
If you are deploying for more than one operator:
|
|
25
|
+
|
|
26
|
+
1. Start with one maintainer on local `stdio`.
|
|
27
|
+
2. Document token lifecycle and rotation ownership.
|
|
28
|
+
3. Define support window and incident contact before enabling hosted mode.
|
|
29
|
+
4. Validate `/health` and MCP initialize responses on every release.
|
|
30
|
+
|
|
31
|
+
## Release Checklist by Mode
|
|
32
|
+
|
|
33
|
+
### Local `stdio`
|
|
34
|
+
|
|
35
|
+
1. `npx -y @jtalk22/slack-mcp --status`
|
|
36
|
+
2. `npx -y @jtalk22/slack-mcp --help`
|
|
37
|
+
3. Confirm tool list in Claude client.
|
|
38
|
+
|
|
39
|
+
### Local `web`
|
|
40
|
+
|
|
41
|
+
1. `npx -y @jtalk22/slack-mcp web`
|
|
42
|
+
2. Verify API key generation at `~/.slack-mcp-api-key`.
|
|
43
|
+
3. Verify `/health`, `/conversations`, and `/search` endpoints.
|
|
44
|
+
|
|
45
|
+
### Hosted (`http` or Worker)
|
|
46
|
+
|
|
47
|
+
1. Verify `version` parity across `package.json`, server metadata, and health responses.
|
|
48
|
+
2. Verify HTTP auth behavior:
|
|
49
|
+
- missing `SLACK_MCP_HTTP_AUTH_TOKEN` returns `503`
|
|
50
|
+
- bad bearer token returns `401`
|
|
51
|
+
- valid bearer token succeeds
|
|
52
|
+
3. Verify CORS behavior:
|
|
53
|
+
- denied by default
|
|
54
|
+
- allowed origins work when listed in `SLACK_MCP_HTTP_ALLOWED_ORIGINS`
|
|
55
|
+
4. Confirm `slack_get_thread`, `slack_search_messages`, and `slack_users_info` behavior.
|
|
56
|
+
5. Confirm token handling mode (ephemeral vs env persistence) is documented.
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -31,7 +31,9 @@ If `--version` fails here, the issue is install/runtime path, not Slack credenti
|
|
|
31
31
|
|
|
32
32
|
## Hosted Version
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
The hosted version is live at [mcp.revasserlabs.com](https://mcp.revasserlabs.com). Free tier (no card) ships 10 smart_search/mo + 3 catch_me_up/mo + 5 triage/day + all 5 workflow profile types. Pro at $9/mo unlocks unlimited AI tools, the scheduled morning catch-up DM at 8am workspace time *(rolling out Q2 2026)*, permanent OAuth (no 2-week token rotation), and 90-day Vectorize retention. Team at $49/mo flat covers 5 workspaces with shared workflow profiles and audit log. Ops engagement starts at $199/mo (custom) for 10+ workspace organizations with SLA, custom retention, SOC2 evidence, or multi-tenant isolation.
|
|
35
|
+
|
|
36
|
+
The OSS package keeps the local-machine path. The hosted version adds the AI brain (smart_search, catch_me_up, triage) — these tools also appear in the OSS package as discoverable upgrade stubs that point at the hosted signup.
|
|
35
37
|
|
|
36
38
|
---
|
|
37
39
|
|
package/lib/handlers.js
CHANGED
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
getLastExtractionError
|
|
17
17
|
} from "./token-store.js";
|
|
18
18
|
import { slackAPI, resolveUser, formatTimestamp, sleep, checkTokenHealth, getUserCacheStats } from "./slack-client.js";
|
|
19
|
+
import {
|
|
20
|
+
saveProfile as workflowSaveProfile,
|
|
21
|
+
listProfiles as workflowListProfiles,
|
|
22
|
+
ALLOWED_WORKFLOW_KINDS_LIST,
|
|
23
|
+
} from "./workflow-store.js";
|
|
19
24
|
|
|
20
25
|
// ============ Utilities ============
|
|
21
26
|
|
|
@@ -773,3 +778,90 @@ export async function handleUsersSearch(args) {
|
|
|
773
778
|
users: allUsers.slice(0, limit)
|
|
774
779
|
});
|
|
775
780
|
}
|
|
781
|
+
|
|
782
|
+
// ============ Workflow Profile Primitives (OSS) ============
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Save (or update) a workflow profile to ~/.slack-mcp-workflows.json
|
|
786
|
+
* Profile binds a workflow_kind to channels + priority_people + retention + cadence.
|
|
787
|
+
*/
|
|
788
|
+
export async function handleWorkflowSave(args) {
|
|
789
|
+
const safeArgs = args || {};
|
|
790
|
+
const result = workflowSaveProfile({
|
|
791
|
+
profile_name: safeArgs.profile_name,
|
|
792
|
+
workflow_kind: safeArgs.workflow_kind,
|
|
793
|
+
channels: safeArgs.channels,
|
|
794
|
+
priority_people: safeArgs.priority_people,
|
|
795
|
+
retention_mode: safeArgs.retention_mode,
|
|
796
|
+
summary_cadence: safeArgs.summary_cadence,
|
|
797
|
+
});
|
|
798
|
+
if (!result.ok) {
|
|
799
|
+
return asMcpJson({ error: "invalid_workflow_profile", errors: result.errors }, true);
|
|
800
|
+
}
|
|
801
|
+
return asMcpJson({
|
|
802
|
+
ok: true,
|
|
803
|
+
profile_name: result.profile_name,
|
|
804
|
+
profile: result.profile,
|
|
805
|
+
note: "Profile saved locally. Hosted free tier syncs profiles automatically when you connect your account; OSS-only mode keeps them on your machine.",
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* List saved workflow profiles, optionally filtered by workflow_kind.
|
|
811
|
+
*/
|
|
812
|
+
export async function handleWorkflows(args) {
|
|
813
|
+
const result = workflowListProfiles({ workflow_kind: args && args.workflow_kind });
|
|
814
|
+
if (!result.ok) {
|
|
815
|
+
return asMcpJson({ error: "invalid_workflow_filter", errors: result.errors }, true);
|
|
816
|
+
}
|
|
817
|
+
return asMcpJson({
|
|
818
|
+
ok: true,
|
|
819
|
+
count: result.profiles.length,
|
|
820
|
+
workflow_kinds: ALLOWED_WORKFLOW_KINDS_LIST,
|
|
821
|
+
profiles: result.profiles,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// ============ Hosted-Only AI Tool Stubs ============
|
|
826
|
+
// These three tools appear in the OSS package's MCP tool list so users
|
|
827
|
+
// discover them. The handlers return a structured upgrade message —
|
|
828
|
+
// actual execution happens on the hosted worker (mcp.revasserlabs.com).
|
|
829
|
+
|
|
830
|
+
const HOSTED_UPGRADE_PAYLOAD = {
|
|
831
|
+
error: "tool_requires_hosted",
|
|
832
|
+
message: "This tool needs hosted mode (Vectorize + Workers AI). Get free monthly credits at mcp.revasserlabs.com — no card required.",
|
|
833
|
+
signup_url: "https://mcp.revasserlabs.com/signup",
|
|
834
|
+
upgrade_url: "https://mcp.revasserlabs.com/pricing",
|
|
835
|
+
free_tier_quota: "10 smart_search + 3 catch_me_up per month, 5 triage per day",
|
|
836
|
+
pro_value_prop: "Pro $9/mo unlocks unlimited AI tools (scheduled morning catch-up DM at 8am workspace time rolling out Q2 2026).",
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
export async function handleSmartSearch(args) {
|
|
840
|
+
return asMcpJson(
|
|
841
|
+
{
|
|
842
|
+
...HOSTED_UPGRADE_PAYLOAD,
|
|
843
|
+
requested: { tool: "slack_smart_search", args: args || {} },
|
|
844
|
+
},
|
|
845
|
+
true
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
export async function handleCatchMeUp(args) {
|
|
850
|
+
return asMcpJson(
|
|
851
|
+
{
|
|
852
|
+
...HOSTED_UPGRADE_PAYLOAD,
|
|
853
|
+
requested: { tool: "slack_catch_me_up", args: args || {} },
|
|
854
|
+
},
|
|
855
|
+
true
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export async function handleTriage(args) {
|
|
860
|
+
return asMcpJson(
|
|
861
|
+
{
|
|
862
|
+
...HOSTED_UPGRADE_PAYLOAD,
|
|
863
|
+
requested: { tool: "slack_triage", args: args || {} },
|
|
864
|
+
},
|
|
865
|
+
true
|
|
866
|
+
);
|
|
867
|
+
}
|
package/lib/public-metadata.js
CHANGED
|
@@ -18,5 +18,5 @@ export const PUBLIC_METADATA = Object.freeze({
|
|
|
18
18
|
cloudSupportUrl: "https://mcp.revasserlabs.com/support",
|
|
19
19
|
cloudStatusUrl: "https://mcp.revasserlabs.com/status",
|
|
20
20
|
supportEmail: "support@revasserlabs.com",
|
|
21
|
-
selfHostedToolCount:
|
|
21
|
+
selfHostedToolCount: 21,
|
|
22
22
|
});
|
package/lib/public-pages.js
CHANGED
|
@@ -63,7 +63,7 @@ function shareLinks() {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function shareNote() {
|
|
66
|
-
return `<strong>Verify in 30 seconds:</strong> <code>--version</code>, <code>--doctor</code>, <code>--status</code>. Self-host gives ${PUBLIC_METADATA.selfHostedToolCount} tools with session-based auth. Works with any MCP client — Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf. Hosted
|
|
66
|
+
return `<strong>Verify in 30 seconds:</strong> <code>--version</code>, <code>--doctor</code>, <code>--status</code>. Self-host gives ${PUBLIC_METADATA.selfHostedToolCount} tools with session-based auth. Works with any MCP client — Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf. Hosted free tier (no card) live at <a href="${PUBLIC_METADATA.canonicalSiteUrl}">mcp.revasserlabs.com</a> — Pro $9/mo unlocks unlimited AI tools (scheduled morning catch-up DM rolling out Q2 2026).`;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function demoLinks() {
|
|
@@ -75,7 +75,7 @@ function demoLinks() {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function demoNote() {
|
|
78
|
-
return `Self-host free for ${PUBLIC_METADATA.selfHostedToolCount} tools with session-based auth. Works with Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf, and any other MCP client. No OAuth app, no admin approval. Hosted
|
|
78
|
+
return `Self-host free for ${PUBLIC_METADATA.selfHostedToolCount} tools with session-based auth. Works with Claude, ChatGPT, Cursor, Copilot, Gemini, Windsurf, and any other MCP client. No OAuth app, no admin approval. Hosted free tier (no card) live at <a href="${PUBLIC_METADATA.canonicalSiteUrl}" target="_blank" rel="noopener noreferrer">mcp.revasserlabs.com</a> — Pro $9/mo unlocks unlimited AI tools (scheduled morning catch-up DM rolling out Q2 2026).`;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function demoFooterLinks() {
|
|
@@ -113,8 +113,8 @@ function commonTokens() {
|
|
|
113
113
|
SELF_HOSTED_TOOL_COUNT: String(PUBLIC_METADATA.selfHostedToolCount),
|
|
114
114
|
CLOUD_MANAGED_TOOL_COUNT: "15",
|
|
115
115
|
TEAM_AI_WORKFLOW_COUNT: "3",
|
|
116
|
-
CLOUD_SOLO_PRICE: "
|
|
117
|
-
CLOUD_TEAM_PRICE: "
|
|
116
|
+
CLOUD_SOLO_PRICE: "$9/mo",
|
|
117
|
+
CLOUD_TEAM_PRICE: "$49/mo",
|
|
118
118
|
CLOUD_TURNKEY_LAUNCH_PRICE: "contact us",
|
|
119
119
|
CLOUD_MANAGED_RELIABILITY_PRICE: "contact us",
|
|
120
120
|
SUPPORT_EMAIL: PUBLIC_METADATA.supportEmail,
|
package/lib/slack-client.js
CHANGED
|
@@ -9,12 +9,19 @@
|
|
|
9
9
|
* - Proactive token health checking
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
loadTokens,
|
|
14
|
+
saveTokens,
|
|
15
|
+
extractFromChrome,
|
|
16
|
+
getLastExtractionError,
|
|
17
|
+
saveAutoHealTelemetry,
|
|
18
|
+
} from "./token-store.js";
|
|
13
19
|
|
|
14
20
|
// ============ Configuration ============
|
|
15
21
|
|
|
16
22
|
const TOKEN_WARNING_AGE = 10 * 24 * 60 * 60 * 1000; // 10 days
|
|
17
23
|
const TOKEN_CRITICAL_AGE = 13 * 24 * 60 * 60 * 1000; // 13 days
|
|
24
|
+
const STUCK_THRESHOLD_MS = 24 * 60 * 60 * 1000; // Escalate to 'stuck' after 24h of repeated auto-heal failures
|
|
18
25
|
const REFRESH_COOLDOWN = 60 * 60 * 1000; // 1 hour between refresh attempts
|
|
19
26
|
const USER_CACHE_MAX_SIZE = 500;
|
|
20
27
|
const USER_CACHE_TTL = 60 * 60 * 1000; // 1 hour
|
|
@@ -113,14 +120,21 @@ export async function checkTokenHealth(logger = console) {
|
|
|
113
120
|
? Math.round(tokenAge / (60 * 60 * 1000) * 10) / 10
|
|
114
121
|
: null;
|
|
115
122
|
|
|
123
|
+
// Read auto-heal telemetry (only populated when source is "file")
|
|
124
|
+
let lastAutoHealAttempt = creds.lastAutoHealAttempt || null;
|
|
125
|
+
let lastAutoHealError = creds.lastAutoHealError || null;
|
|
126
|
+
let stuckSince = creds.stuckSince || null;
|
|
127
|
+
|
|
116
128
|
// Attempt proactive refresh if token is getting old
|
|
117
129
|
if (hasKnownAge && tokenAge > TOKEN_WARNING_AGE && Date.now() - lastRefreshAttempt > REFRESH_COOLDOWN) {
|
|
118
130
|
lastRefreshAttempt = Date.now();
|
|
131
|
+
const attemptAt = new Date().toISOString();
|
|
119
132
|
logger.error?.(`Token is ${ageHours}h old, attempting proactive refresh...`);
|
|
120
133
|
|
|
121
134
|
const newTokens = extractFromChrome();
|
|
122
135
|
if (newTokens) {
|
|
123
136
|
saveTokens(newTokens.token, newTokens.cookie);
|
|
137
|
+
saveAutoHealTelemetry({ attemptAt, error: null });
|
|
124
138
|
logger.error?.('Proactively refreshed tokens from Chrome');
|
|
125
139
|
return {
|
|
126
140
|
healthy: true,
|
|
@@ -129,35 +143,59 @@ export async function checkTokenHealth(logger = console) {
|
|
|
129
143
|
age_known: true,
|
|
130
144
|
age_state: 'fresh',
|
|
131
145
|
source: 'chrome-auto',
|
|
146
|
+
last_auto_heal_attempt: attemptAt,
|
|
147
|
+
last_auto_heal_error: null,
|
|
148
|
+
stuck_since: null,
|
|
132
149
|
message: 'Tokens refreshed successfully'
|
|
133
150
|
};
|
|
134
151
|
} else {
|
|
135
|
-
|
|
152
|
+
const extractionError = getLastExtractionError();
|
|
153
|
+
const errorCode = extractionError?.code || 'chrome_extraction_failed';
|
|
154
|
+
saveAutoHealTelemetry({ attemptAt, error: errorCode });
|
|
155
|
+
lastAutoHealAttempt = attemptAt;
|
|
156
|
+
if (lastAutoHealError !== errorCode) {
|
|
157
|
+
stuckSince = attemptAt;
|
|
158
|
+
}
|
|
159
|
+
lastAutoHealError = errorCode;
|
|
160
|
+
logger.error?.(`Could not refresh from Chrome: ${extractionError?.message || 'unknown error'}`);
|
|
136
161
|
}
|
|
137
162
|
}
|
|
138
163
|
|
|
164
|
+
const stuckSinceMs = stuckSince ? new Date(stuckSince).getTime() : Number.NaN;
|
|
165
|
+
const isStuck = Number.isFinite(stuckSinceMs)
|
|
166
|
+
&& (Date.now() - stuckSinceMs) > STUCK_THRESHOLD_MS
|
|
167
|
+
&& !!lastAutoHealError;
|
|
168
|
+
|
|
139
169
|
return {
|
|
140
170
|
healthy: !hasKnownAge || tokenAge < TOKEN_CRITICAL_AGE,
|
|
141
171
|
age_hours: ageHours,
|
|
142
172
|
age_known: hasKnownAge,
|
|
143
|
-
age_state:
|
|
144
|
-
? '
|
|
145
|
-
:
|
|
146
|
-
? '
|
|
147
|
-
: tokenAge >
|
|
148
|
-
? '
|
|
149
|
-
:
|
|
173
|
+
age_state: isStuck
|
|
174
|
+
? 'stuck'
|
|
175
|
+
: !hasKnownAge
|
|
176
|
+
? 'unknown'
|
|
177
|
+
: tokenAge > TOKEN_CRITICAL_AGE
|
|
178
|
+
? 'critical'
|
|
179
|
+
: tokenAge > TOKEN_WARNING_AGE
|
|
180
|
+
? 'warning'
|
|
181
|
+
: 'healthy',
|
|
150
182
|
warning: hasKnownAge && tokenAge > TOKEN_WARNING_AGE,
|
|
151
183
|
critical: hasKnownAge && tokenAge > TOKEN_CRITICAL_AGE,
|
|
184
|
+
stuck: isStuck,
|
|
152
185
|
source: creds.source,
|
|
153
186
|
updated_at: creds.updatedAt,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
187
|
+
last_auto_heal_attempt: lastAutoHealAttempt,
|
|
188
|
+
last_auto_heal_error: lastAutoHealError,
|
|
189
|
+
stuck_since: stuckSince,
|
|
190
|
+
message: isStuck
|
|
191
|
+
? `Auto-heal has been failing since ${stuckSince} (last error: ${lastAutoHealError}). Open Chrome > View > Developer > Allow JavaScript from Apple Events, then run npm run tokens:auto.`
|
|
192
|
+
: !hasKnownAge
|
|
193
|
+
? 'Token age unknown (missing timestamp) - auth can still be valid'
|
|
194
|
+
: tokenAge > TOKEN_CRITICAL_AGE
|
|
195
|
+
? 'Token may expire soon - open Slack in Chrome'
|
|
196
|
+
: tokenAge > TOKEN_WARNING_AGE
|
|
197
|
+
? 'Token is getting old - will auto-refresh if Slack tab is open'
|
|
198
|
+
: 'Token is healthy'
|
|
161
199
|
};
|
|
162
200
|
}
|
|
163
201
|
|
|
@@ -250,13 +288,28 @@ export async function slackAPI(method, params = {}, options = {}) {
|
|
|
250
288
|
// Handle auth errors with auto-retry
|
|
251
289
|
if ((data.error === "invalid_auth" || data.error === "token_expired") && retryOnAuthFail) {
|
|
252
290
|
logger.error?.("Token expired, attempting Chrome auto-extraction...");
|
|
291
|
+
const attemptAt = new Date().toISOString();
|
|
253
292
|
const chromeTokens = extractFromChrome();
|
|
254
293
|
if (chromeTokens) {
|
|
255
294
|
saveTokens(chromeTokens.token, chromeTokens.cookie);
|
|
295
|
+
saveAutoHealTelemetry({ attemptAt, error: null });
|
|
256
296
|
// Retry the request
|
|
257
297
|
return slackAPI(method, params, { ...options, retryOnAuthFail: false });
|
|
258
298
|
}
|
|
259
|
-
|
|
299
|
+
const extractionError = getLastExtractionError() || {
|
|
300
|
+
code: 'chrome_extraction_failed',
|
|
301
|
+
message: 'Auto-heal attempted but no structured error surfaced.',
|
|
302
|
+
detail: null
|
|
303
|
+
};
|
|
304
|
+
saveAutoHealTelemetry({ attemptAt, error: extractionError.code });
|
|
305
|
+
const err = new Error(
|
|
306
|
+
`Slack auth failed (${data.error}) and auto-heal could not refresh tokens: ${extractionError.message}`
|
|
307
|
+
);
|
|
308
|
+
err.code = 'token_auth_failed';
|
|
309
|
+
err.slack_error = data.error;
|
|
310
|
+
err.extraction_error = extractionError;
|
|
311
|
+
err.next_action = 'Open http://localhost:3000 and click Refresh, OR run `npm run tokens:auto` with Slack open in Chrome, OR check Chrome > View > Developer > Allow JavaScript from Apple Events.';
|
|
312
|
+
throw err;
|
|
260
313
|
}
|
|
261
314
|
throw new Error(data.error || "Slack API error");
|
|
262
315
|
}
|