@jtalk22/slack-mcp 4.4.1 → 4.4.2
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/lib/handlers.js +73 -10
- package/lib/public-pages.js +2 -2
- package/lib/token-store.js +16 -4
- package/lib/tools.js +1 -1
- package/lib/workflow-store.js +27 -12
- package/package.json +2 -2
- package/public/share.html +1 -1
- package/scripts/apply-template.js +1 -1
- package/server.json +2 -2
- package/src/server-http.js +18 -64
- package/src/server.js +18 -98
package/lib/handlers.js
CHANGED
|
@@ -336,14 +336,17 @@ export async function handleListConversations(args) {
|
|
|
336
336
|
export async function handleConversationsHistory(args) {
|
|
337
337
|
const resolveUsers = args.resolve_users !== false;
|
|
338
338
|
const includeRichMessageFields = parseBool(args.include_rich_message_fields);
|
|
339
|
-
const
|
|
339
|
+
const historyParams = {
|
|
340
340
|
channel: args.channel_id,
|
|
341
341
|
limit: args.limit || 50,
|
|
342
342
|
oldest: args.oldest,
|
|
343
343
|
latest: args.latest,
|
|
344
|
-
inclusive: true,
|
|
345
344
|
include_all_metadata: parseBool(args.include_all_metadata)
|
|
346
|
-
}
|
|
345
|
+
};
|
|
346
|
+
// Only opt into boundary-inclusive reads when a boundary was actually
|
|
347
|
+
// provided — otherwise leave Slack's default behavior untouched.
|
|
348
|
+
if (args.oldest || args.latest) historyParams.inclusive = true;
|
|
349
|
+
const result = await slackAPI("conversations.history", historyParams);
|
|
347
350
|
|
|
348
351
|
const messages = await Promise.all((result.messages || []).map(async (msg) => {
|
|
349
352
|
const userName = resolveUsers ? await resolveUser(msg.user) : msg.user;
|
|
@@ -384,15 +387,18 @@ export async function handleGetFullConversation(args) {
|
|
|
384
387
|
|
|
385
388
|
// Fetch all messages with pagination
|
|
386
389
|
while (hasMore && allMessages.length < maxMessages) {
|
|
387
|
-
const
|
|
390
|
+
const historyParams = {
|
|
388
391
|
channel: args.channel_id,
|
|
389
392
|
limit: Math.min(100, maxMessages - allMessages.length),
|
|
390
393
|
oldest: args.oldest,
|
|
391
394
|
latest: args.latest,
|
|
392
395
|
cursor,
|
|
393
|
-
inclusive: true,
|
|
394
396
|
include_all_metadata: parseBool(args.include_all_metadata)
|
|
395
|
-
}
|
|
397
|
+
};
|
|
398
|
+
// Boundary-inclusive only when a boundary was actually provided —
|
|
399
|
+
// otherwise leave Slack's default behavior untouched.
|
|
400
|
+
if (args.oldest || args.latest) historyParams.inclusive = true;
|
|
401
|
+
const result = await slackAPI("conversations.history", historyParams);
|
|
396
402
|
|
|
397
403
|
for (const msg of result.messages || []) {
|
|
398
404
|
const userName = await resolveUser(msg.user);
|
|
@@ -738,13 +744,32 @@ export async function handleConversationsUnreads(args) {
|
|
|
738
744
|
/**
|
|
739
745
|
* Search users handler - client-side filter on users.list
|
|
740
746
|
*/
|
|
747
|
+
// Explicit scan cap for client-side user search: stop paginating after
|
|
748
|
+
// scanning this many workspace users (or when the cursor is exhausted) and
|
|
749
|
+
// flag the result as truncated so total_matches is never misreported as
|
|
750
|
+
// complete.
|
|
751
|
+
const USERS_SEARCH_MAX_SCANNED = 1000;
|
|
752
|
+
|
|
741
753
|
export async function handleUsersSearch(args) {
|
|
742
|
-
const
|
|
754
|
+
const rawQuery = typeof args.query === "string" ? args.query : "";
|
|
755
|
+
const query = rawQuery.trim().toLowerCase();
|
|
743
756
|
const limit = args.limit || 20;
|
|
744
757
|
|
|
745
|
-
//
|
|
758
|
+
// An empty/whitespace query would match every user in the workspace.
|
|
759
|
+
if (!query) {
|
|
760
|
+
return asMcpJson({
|
|
761
|
+
status: "error",
|
|
762
|
+
code: "invalid_arguments",
|
|
763
|
+
message: "query must be a non-empty string (an empty query would match all users).",
|
|
764
|
+
next_action: "Provide a name, display name, real name, or email fragment to search for."
|
|
765
|
+
}, true);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Fetch users (paginated) and filter client-side
|
|
746
769
|
const allUsers = [];
|
|
747
770
|
let cursor;
|
|
771
|
+
let scannedUsers = 0;
|
|
772
|
+
let truncated = false;
|
|
748
773
|
|
|
749
774
|
do {
|
|
750
775
|
const result = await slackAPI("users.list", {
|
|
@@ -753,6 +778,7 @@ export async function handleUsersSearch(args) {
|
|
|
753
778
|
});
|
|
754
779
|
|
|
755
780
|
for (const u of (result.members || [])) {
|
|
781
|
+
scannedUsers++;
|
|
756
782
|
if (u.deleted || u.is_bot || u.id === "USLACKBOT") continue;
|
|
757
783
|
|
|
758
784
|
const searchFields = [
|
|
@@ -776,13 +802,18 @@ export async function handleUsersSearch(args) {
|
|
|
776
802
|
}
|
|
777
803
|
|
|
778
804
|
cursor = result.response_metadata?.next_cursor;
|
|
805
|
+
if (cursor && scannedUsers >= USERS_SEARCH_MAX_SCANNED) {
|
|
806
|
+
truncated = true;
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
779
809
|
if (cursor) await sleep(100);
|
|
780
|
-
} while (cursor
|
|
810
|
+
} while (cursor);
|
|
781
811
|
|
|
782
812
|
return asMcpJson({
|
|
783
813
|
query: args.query,
|
|
784
814
|
count: Math.min(allUsers.length, limit),
|
|
785
815
|
total_matches: allUsers.length,
|
|
816
|
+
truncated,
|
|
786
817
|
users: allUsers.slice(0, limit)
|
|
787
818
|
});
|
|
788
819
|
}
|
|
@@ -841,7 +872,7 @@ const HOSTED_UPGRADE_PAYLOAD = {
|
|
|
841
872
|
signup_url: "https://mcp.revasserlabs.com/signup",
|
|
842
873
|
upgrade_url: "https://mcp.revasserlabs.com/pricing",
|
|
843
874
|
free_tier_quota: "10 smart_search + 3 catch_me_up per month, 5 triage per day",
|
|
844
|
-
pro_value_prop: "Pro $9/mo unlocks unlimited AI tools (scheduled morning catch-up DM at 8am workspace time
|
|
875
|
+
pro_value_prop: "Pro $9/mo unlocks unlimited AI tools (scheduled morning catch-up DM at 8am workspace time in development).",
|
|
845
876
|
};
|
|
846
877
|
|
|
847
878
|
export async function handleSmartSearch(args) {
|
|
@@ -873,3 +904,35 @@ export async function handleTriage(args) {
|
|
|
873
904
|
true
|
|
874
905
|
);
|
|
875
906
|
}
|
|
907
|
+
|
|
908
|
+
// ============ Shared Tool Dispatch Map ============
|
|
909
|
+
// Single source of truth mapping every advertised tool name (lib/tools.js)
|
|
910
|
+
// to its handler. Both transports — stdio (src/server.js) and HTTP
|
|
911
|
+
// (src/server-http.js) — dispatch through this map so the advertised tool
|
|
912
|
+
// list and the dispatch surface cannot drift apart.
|
|
913
|
+
|
|
914
|
+
export const TOOL_HANDLERS = Object.freeze({
|
|
915
|
+
slack_token_status: handleTokenStatus,
|
|
916
|
+
slack_health_check: handleHealthCheck,
|
|
917
|
+
slack_refresh_tokens: handleRefreshTokens,
|
|
918
|
+
slack_list_conversations: handleListConversations,
|
|
919
|
+
slack_conversations_history: handleConversationsHistory,
|
|
920
|
+
slack_get_full_conversation: handleGetFullConversation,
|
|
921
|
+
slack_search_messages: handleSearchMessages,
|
|
922
|
+
slack_users_info: handleUsersInfo,
|
|
923
|
+
slack_send_message: handleSendMessage,
|
|
924
|
+
slack_get_thread: handleGetThread,
|
|
925
|
+
slack_list_users: handleListUsers,
|
|
926
|
+
slack_add_reaction: handleAddReaction,
|
|
927
|
+
slack_remove_reaction: handleRemoveReaction,
|
|
928
|
+
slack_conversations_mark: handleConversationsMark,
|
|
929
|
+
slack_conversations_unreads: handleConversationsUnreads,
|
|
930
|
+
slack_users_search: handleUsersSearch,
|
|
931
|
+
// Workflow profile primitives (OSS local JSON store)
|
|
932
|
+
slack_workflow_save: handleWorkflowSave,
|
|
933
|
+
slack_workflows: handleWorkflows,
|
|
934
|
+
// Hosted-only AI tools (OSS = upgrade stubs)
|
|
935
|
+
slack_smart_search: handleSmartSearch,
|
|
936
|
+
slack_catch_me_up: handleCatchMeUp,
|
|
937
|
+
slack_triage: handleTriage,
|
|
938
|
+
});
|
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 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
|
|
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 in development).`;
|
|
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 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
|
|
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 in development).`;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function demoFooterLinks() {
|
package/lib/token-store.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 4. Chrome auto-extraction (fallback)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync, chmodSync, copyFileSync, mkdtempSync, statSync, readdirSync } from "fs";
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync, rmSync, chmodSync, copyFileSync, mkdtempSync, statSync, readdirSync } from "fs";
|
|
12
12
|
import { homedir, platform, tmpdir } from "os";
|
|
13
13
|
import { join } from "path";
|
|
14
14
|
import { execFileSync } from "child_process";
|
|
@@ -265,13 +265,22 @@ function extractCookieForProfile(profileDir) {
|
|
|
265
265
|
const tmpDb = join(tmpDir, 'Cookies');
|
|
266
266
|
try {
|
|
267
267
|
copyFileSync(cookiesPath, tmpDb);
|
|
268
|
+
// Chrome keeps recent writes in the SQLite WAL sidecar until the next
|
|
269
|
+
// checkpoint — copy Cookies-wal (and -shm) too when present so the
|
|
270
|
+
// snapshot isn't stale.
|
|
271
|
+
for (const suffix of ['-wal', '-shm']) {
|
|
272
|
+
const sidecarPath = `${cookiesPath}${suffix}`;
|
|
273
|
+
if (existsSync(sidecarPath)) {
|
|
274
|
+
try { copyFileSync(sidecarPath, `${tmpDb}${suffix}`); } catch {}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
268
277
|
|
|
269
278
|
const queryResult = execFileSync('sqlite3', [
|
|
270
279
|
tmpDb,
|
|
271
280
|
"SELECT hex(encrypted_value) FROM cookies WHERE host_key LIKE '%.slack.com%' AND name = 'd' LIMIT 1;"
|
|
272
281
|
], { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
273
282
|
|
|
274
|
-
try {
|
|
283
|
+
try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
275
284
|
|
|
276
285
|
if (!queryResult) return null;
|
|
277
286
|
|
|
@@ -304,7 +313,7 @@ function extractCookieForProfile(profileDir) {
|
|
|
304
313
|
if (xoxdIndex < 0) return null;
|
|
305
314
|
return text.substring(xoxdIndex);
|
|
306
315
|
} catch {
|
|
307
|
-
try {
|
|
316
|
+
try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
308
317
|
return null;
|
|
309
318
|
}
|
|
310
319
|
}
|
|
@@ -358,7 +367,10 @@ function extractTokenFromLevelDB(profileDir) {
|
|
|
358
367
|
const txt = readFileSync(f.path).toString('binary');
|
|
359
368
|
XOXC_TOKEN_RE.lastIndex = 0;
|
|
360
369
|
const matches = txt.match(XOXC_TOKEN_RE);
|
|
361
|
-
|
|
370
|
+
// LevelDB .log/.ldb files append newer records after older ones, so
|
|
371
|
+
// the LAST match in a file is the most recently cached token — the
|
|
372
|
+
// first match can be a stale, already-rotated token.
|
|
373
|
+
if (matches && matches.length) return matches[matches.length - 1];
|
|
362
374
|
} catch {
|
|
363
375
|
continue;
|
|
364
376
|
}
|
package/lib/tools.js
CHANGED
|
@@ -537,7 +537,7 @@ export const TOOLS = [
|
|
|
537
537
|
},
|
|
538
538
|
{
|
|
539
539
|
name: "slack_catch_me_up",
|
|
540
|
-
description: "Run a structured catch-up against a saved workflow profile. Returns structured JSON per the profile's workflow_kind: support_inbox returns {open_threads, ack_lag, owner_gaps, escalations, next_actions}; incident_room returns {incident_summary, timeline, open_risks, owner_gaps, next_actions}; exec_brief returns {summary, decisions, risks, asks, action_items}; product_launch_watch returns {launch_signals, feedback_themes, blockers, metrics, next_actions}; custom returns {summary, highlights, open_questions, next_actions}. Hosted-only. Free tier ships 3 calls/month; Pro $9/mo unlocks unlimited (scheduled morning DM at 8am workspace tz
|
|
540
|
+
description: "Run a structured catch-up against a saved workflow profile. Returns structured JSON per the profile's workflow_kind: support_inbox returns {open_threads, ack_lag, owner_gaps, escalations, next_actions}; incident_room returns {incident_summary, timeline, open_risks, owner_gaps, next_actions}; exec_brief returns {summary, decisions, risks, asks, action_items}; product_launch_watch returns {launch_signals, feedback_themes, blockers, metrics, next_actions}; custom returns {summary, highlights, open_questions, next_actions}. Hosted-only. Free tier ships 3 calls/month; Pro $9/mo unlocks unlimited (scheduled morning DM at 8am workspace tz in development).",
|
|
541
541
|
inputSchema: {
|
|
542
542
|
type: "object",
|
|
543
543
|
properties: {
|
package/lib/workflow-store.js
CHANGED
|
@@ -70,32 +70,47 @@ export function loadStore() {
|
|
|
70
70
|
try {
|
|
71
71
|
const data = JSON.parse(raw);
|
|
72
72
|
if (!data || typeof data !== "object" || !data.profiles) {
|
|
73
|
-
|
|
73
|
+
quarantineCorruptStore("shape-invalid");
|
|
74
74
|
return emptyStore();
|
|
75
75
|
}
|
|
76
76
|
if (data.version !== STORE_VERSION) {
|
|
77
|
-
// Future: migration logic. For now,
|
|
78
|
-
// before falling back to empty so the old data is recoverable.
|
|
79
|
-
|
|
77
|
+
// Future: migration logic. For now, quarantine the unrecognized
|
|
78
|
+
// version before falling back to empty so the old data is recoverable.
|
|
79
|
+
quarantineCorruptStore(`version-${data.version}`);
|
|
80
80
|
return emptyStore();
|
|
81
81
|
}
|
|
82
82
|
return data;
|
|
83
83
|
} catch {
|
|
84
|
-
|
|
84
|
+
quarantineCorruptStore("json-parse-error");
|
|
85
85
|
return emptyStore();
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Move an unreadable store aside to <store>.corrupt-<timestamp> before
|
|
91
|
+
* loadStore falls back to an empty store. MOVING (not copying) matters:
|
|
92
|
+
* it guarantees the next saveStore cannot clobber the user's only copy of
|
|
93
|
+
* their profiles, and repeated loads don't spawn one backup per call.
|
|
94
|
+
* Warns on stderr (stdout belongs to the MCP stdio protocol).
|
|
95
|
+
*/
|
|
96
|
+
function quarantineCorruptStore(reasonTag) {
|
|
97
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
98
|
+
const quarantinePath = `${STORE_FILE}.corrupt-${stamp}`;
|
|
90
99
|
try {
|
|
91
|
-
|
|
92
|
-
const backupPath = `${STORE_FILE}.bak.${reasonTag}.${stamp}`;
|
|
93
|
-
writeFileSync(backupPath, raw);
|
|
100
|
+
renameSync(STORE_FILE, quarantinePath);
|
|
94
101
|
if (platform() === "darwin" || platform() === "linux") {
|
|
95
|
-
try { chmodSync(
|
|
102
|
+
try { chmodSync(quarantinePath, 0o600); } catch {}
|
|
96
103
|
}
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
console.error(
|
|
105
|
+
`[slack-mcp] WARNING: workflow store ${STORE_FILE} is unreadable (${reasonTag}). ` +
|
|
106
|
+
`Quarantined it to ${quarantinePath} and continuing with an empty store.`
|
|
107
|
+
);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
// Quarantine is best-effort; never let it break loadStore.
|
|
110
|
+
console.error(
|
|
111
|
+
`[slack-mcp] WARNING: workflow store ${STORE_FILE} is unreadable (${reasonTag}) ` +
|
|
112
|
+
`and could not be quarantined: ${e?.message || e}`
|
|
113
|
+
);
|
|
99
114
|
}
|
|
100
115
|
}
|
|
101
116
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jtalk22/slack-mcp",
|
|
3
3
|
"mcpName": "io.github.jtalk22/slack-mcp-server",
|
|
4
|
-
"version": "4.4.
|
|
5
|
-
"description": "Slack MCP without OAuth
|
|
4
|
+
"version": "4.4.2",
|
|
5
|
+
"description": "Slack MCP without OAuth — 21 tools, session-based, local-first. Free OSS + hosted tier from $9/mo.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "src/server.js",
|
|
8
8
|
"bin": {
|
package/public/share.html
CHANGED
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
<a href="https://mcp.revasserlabs.com" rel="noopener" style="background:rgba(240,194,70,0.18);border-color:rgba(240,194,70,0.45);color:#f0c246">Hosted</a>
|
|
125
125
|
</div>
|
|
126
126
|
|
|
127
|
-
<p class="note"><strong>Verify in 30 seconds:</strong> <code>--version</code>, <code>--doctor</code>, <code>--status</code>. Self-host gives 21 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="https://mcp.revasserlabs.com">mcp.revasserlabs.com</a> — Pro $9/mo unlocks unlimited AI tools (scheduled morning catch-up DM
|
|
127
|
+
<p class="note"><strong>Verify in 30 seconds:</strong> <code>--version</code>, <code>--doctor</code>, <code>--status</code>. Self-host gives 21 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="https://mcp.revasserlabs.com">mcp.revasserlabs.com</a> — Pro $9/mo unlocks unlimited AI tools (scheduled morning catch-up DM in development).</p>
|
|
128
128
|
</main>
|
|
129
129
|
</body>
|
|
130
130
|
</html>
|
|
@@ -113,5 +113,5 @@ if (!profile.channels.length) {
|
|
|
113
113
|
console.log("Or call slack_workflow_save from your MCP client to update.");
|
|
114
114
|
} else {
|
|
115
115
|
console.log("Profile is ready. Run slack_catch_me_up against it from your MCP client.");
|
|
116
|
-
console.log(`(Free tier: 3 catch_me_up calls/month. Pro $9/mo unlocks unlimited; scheduled morning DM
|
|
116
|
+
console.log(`(Free tier: 3 catch_me_up calls/month. Pro $9/mo unlocks unlimited; scheduled morning DM in development.)`);
|
|
117
117
|
}
|
package/server.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"url": "https://github.com/jtalk22/slack-mcp-server",
|
|
18
18
|
"source": "github"
|
|
19
19
|
},
|
|
20
|
-
"version": "4.4.
|
|
20
|
+
"version": "4.4.2",
|
|
21
21
|
"remotes": [
|
|
22
22
|
{
|
|
23
23
|
"type": "streamable-http",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
{
|
|
29
29
|
"registryType": "npm",
|
|
30
30
|
"identifier": "@jtalk22/slack-mcp",
|
|
31
|
-
"version": "4.4.
|
|
31
|
+
"version": "4.4.2",
|
|
32
32
|
"transport": {
|
|
33
33
|
"type": "stdio"
|
|
34
34
|
},
|
package/src/server-http.js
CHANGED
|
@@ -15,24 +15,7 @@ import {
|
|
|
15
15
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
16
16
|
|
|
17
17
|
import { TOOLS } from "../lib/tools.js";
|
|
18
|
-
import {
|
|
19
|
-
handleTokenStatus,
|
|
20
|
-
handleHealthCheck,
|
|
21
|
-
handleRefreshTokens,
|
|
22
|
-
handleListConversations,
|
|
23
|
-
handleConversationsHistory,
|
|
24
|
-
handleGetFullConversation,
|
|
25
|
-
handleSearchMessages,
|
|
26
|
-
handleUsersInfo,
|
|
27
|
-
handleSendMessage,
|
|
28
|
-
handleGetThread,
|
|
29
|
-
handleListUsers,
|
|
30
|
-
handleAddReaction,
|
|
31
|
-
handleRemoveReaction,
|
|
32
|
-
handleConversationsMark,
|
|
33
|
-
handleConversationsUnreads,
|
|
34
|
-
handleUsersSearch,
|
|
35
|
-
} from "../lib/handlers.js";
|
|
18
|
+
import { TOOL_HANDLERS } from "../lib/handlers.js";
|
|
36
19
|
import { RELEASE_VERSION } from "../lib/public-metadata.js";
|
|
37
20
|
|
|
38
21
|
const SERVER_NAME = "slack-mcp-server";
|
|
@@ -98,53 +81,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
98
81
|
const { name, arguments: args } = request.params;
|
|
99
82
|
|
|
100
83
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return await handleUsersInfo(args);
|
|
118
|
-
case "slack_send_message":
|
|
119
|
-
return await handleSendMessage(args);
|
|
120
|
-
case "slack_get_thread":
|
|
121
|
-
return await handleGetThread(args);
|
|
122
|
-
case "slack_list_users":
|
|
123
|
-
return await handleListUsers(args);
|
|
124
|
-
case "slack_add_reaction":
|
|
125
|
-
return await handleAddReaction(args);
|
|
126
|
-
case "slack_remove_reaction":
|
|
127
|
-
return await handleRemoveReaction(args);
|
|
128
|
-
case "slack_conversations_mark":
|
|
129
|
-
return await handleConversationsMark(args);
|
|
130
|
-
case "slack_conversations_unreads":
|
|
131
|
-
return await handleConversationsUnreads(args);
|
|
132
|
-
case "slack_users_search":
|
|
133
|
-
return await handleUsersSearch(args);
|
|
134
|
-
default:
|
|
135
|
-
return {
|
|
136
|
-
content: [{
|
|
137
|
-
type: "text",
|
|
138
|
-
text: JSON.stringify({
|
|
139
|
-
status: "error",
|
|
140
|
-
code: "unknown_tool",
|
|
141
|
-
message: `Unknown tool: ${name}`,
|
|
142
|
-
next_action: "Call tools/list to inspect available tool names."
|
|
143
|
-
}, null, 2)
|
|
144
|
-
}],
|
|
145
|
-
isError: true
|
|
146
|
-
};
|
|
84
|
+
// Shared dispatch map keeps this transport in lockstep with the stdio
|
|
85
|
+
// server and the advertised TOOLS list.
|
|
86
|
+
const handler = TOOL_HANDLERS[name];
|
|
87
|
+
if (!handler) {
|
|
88
|
+
return {
|
|
89
|
+
content: [{
|
|
90
|
+
type: "text",
|
|
91
|
+
text: JSON.stringify({
|
|
92
|
+
status: "error",
|
|
93
|
+
code: "unknown_tool",
|
|
94
|
+
message: `Unknown tool: ${name}`,
|
|
95
|
+
next_action: "Call tools/list to inspect available tool names."
|
|
96
|
+
}, null, 2)
|
|
97
|
+
}],
|
|
98
|
+
isError: true
|
|
99
|
+
};
|
|
147
100
|
}
|
|
101
|
+
return await handler(args);
|
|
148
102
|
} catch (error) {
|
|
149
103
|
return {
|
|
150
104
|
content: [{
|
package/src/server.js
CHANGED
|
@@ -30,27 +30,9 @@ import { RELEASE_VERSION } from "../lib/public-metadata.js";
|
|
|
30
30
|
import { checkTokenHealth } from "../lib/slack-client.js";
|
|
31
31
|
import { TOOLS } from "../lib/tools.js";
|
|
32
32
|
import {
|
|
33
|
-
|
|
33
|
+
TOOL_HANDLERS,
|
|
34
34
|
handleHealthCheck,
|
|
35
|
-
handleRefreshTokens,
|
|
36
35
|
handleListConversations,
|
|
37
|
-
handleConversationsHistory,
|
|
38
|
-
handleGetFullConversation,
|
|
39
|
-
handleSearchMessages,
|
|
40
|
-
handleUsersInfo,
|
|
41
|
-
handleSendMessage,
|
|
42
|
-
handleGetThread,
|
|
43
|
-
handleListUsers,
|
|
44
|
-
handleAddReaction,
|
|
45
|
-
handleRemoveReaction,
|
|
46
|
-
handleConversationsMark,
|
|
47
|
-
handleConversationsUnreads,
|
|
48
|
-
handleUsersSearch,
|
|
49
|
-
handleWorkflowSave,
|
|
50
|
-
handleWorkflows,
|
|
51
|
-
handleSmartSearch,
|
|
52
|
-
handleCatchMeUp,
|
|
53
|
-
handleTriage,
|
|
54
36
|
} from "../lib/handlers.js";
|
|
55
37
|
|
|
56
38
|
// Background refresh interval (4 hours)
|
|
@@ -231,86 +213,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
231
213
|
const { name, arguments: args } = request.params;
|
|
232
214
|
|
|
233
215
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
case "slack_get_full_conversation":
|
|
251
|
-
return await handleGetFullConversation(args);
|
|
252
|
-
|
|
253
|
-
case "slack_search_messages":
|
|
254
|
-
return await handleSearchMessages(args);
|
|
255
|
-
|
|
256
|
-
case "slack_users_info":
|
|
257
|
-
return await handleUsersInfo(args);
|
|
258
|
-
|
|
259
|
-
case "slack_send_message":
|
|
260
|
-
return await handleSendMessage(args);
|
|
261
|
-
|
|
262
|
-
case "slack_get_thread":
|
|
263
|
-
return await handleGetThread(args);
|
|
264
|
-
|
|
265
|
-
case "slack_list_users":
|
|
266
|
-
return await handleListUsers(args);
|
|
267
|
-
|
|
268
|
-
case "slack_add_reaction":
|
|
269
|
-
return await handleAddReaction(args);
|
|
270
|
-
|
|
271
|
-
case "slack_remove_reaction":
|
|
272
|
-
return await handleRemoveReaction(args);
|
|
273
|
-
|
|
274
|
-
case "slack_conversations_mark":
|
|
275
|
-
return await handleConversationsMark(args);
|
|
276
|
-
|
|
277
|
-
case "slack_conversations_unreads":
|
|
278
|
-
return await handleConversationsUnreads(args);
|
|
279
|
-
|
|
280
|
-
case "slack_users_search":
|
|
281
|
-
return await handleUsersSearch(args);
|
|
282
|
-
|
|
283
|
-
// Workflow profile primitives (OSS local JSON store)
|
|
284
|
-
case "slack_workflow_save":
|
|
285
|
-
return await handleWorkflowSave(args);
|
|
286
|
-
|
|
287
|
-
case "slack_workflows":
|
|
288
|
-
return await handleWorkflows(args);
|
|
289
|
-
|
|
290
|
-
// Hosted-only AI tools (OSS = upgrade stubs)
|
|
291
|
-
case "slack_smart_search":
|
|
292
|
-
return await handleSmartSearch(args);
|
|
293
|
-
|
|
294
|
-
case "slack_catch_me_up":
|
|
295
|
-
return await handleCatchMeUp(args);
|
|
296
|
-
|
|
297
|
-
case "slack_triage":
|
|
298
|
-
return await handleTriage(args);
|
|
299
|
-
|
|
300
|
-
default:
|
|
301
|
-
return {
|
|
302
|
-
content: [{
|
|
303
|
-
type: "text",
|
|
304
|
-
text: JSON.stringify({
|
|
305
|
-
status: "error",
|
|
306
|
-
code: "unknown_tool",
|
|
307
|
-
message: `Unknown tool: ${name}`,
|
|
308
|
-
next_action: "Use tools/list to discover available tools."
|
|
309
|
-
}, null, 2)
|
|
310
|
-
}],
|
|
311
|
-
isError: true
|
|
312
|
-
};
|
|
216
|
+
// Shared dispatch map (lib/handlers.js) keeps this transport in lockstep
|
|
217
|
+
// with the HTTP server and the advertised TOOLS list.
|
|
218
|
+
const handler = TOOL_HANDLERS[name];
|
|
219
|
+
if (!handler) {
|
|
220
|
+
return {
|
|
221
|
+
content: [{
|
|
222
|
+
type: "text",
|
|
223
|
+
text: JSON.stringify({
|
|
224
|
+
status: "error",
|
|
225
|
+
code: "unknown_tool",
|
|
226
|
+
message: `Unknown tool: ${name}`,
|
|
227
|
+
next_action: "Use tools/list to discover available tools."
|
|
228
|
+
}, null, 2)
|
|
229
|
+
}],
|
|
230
|
+
isError: true
|
|
231
|
+
};
|
|
313
232
|
}
|
|
233
|
+
return await handler(args);
|
|
314
234
|
} catch (error) {
|
|
315
235
|
if (error?.code === "token_auth_failed") {
|
|
316
236
|
return {
|