yt-slack-mcp 0.3.2 → 0.5.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 +37 -7
- package/index.js +58 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,8 @@ A host-agnostic [Model Context Protocol](https://modelcontextprotocol.io) server
|
|
|
8
8
|
- Read message permalinks from channels, DMs, and group DMs.
|
|
9
9
|
- Return image attachments inline, extract text from PDFs, and return `text/*` attachment contents inline.
|
|
10
10
|
- Retrieve private attachment contents through the authenticated Slack browser session.
|
|
11
|
-
- Reply to a thread, add a reaction, edit an authored message or thread reply, or upload local files only in explicitly allowed channel IDs.
|
|
11
|
+
- Reply to a thread, add or remove a reaction, edit an authored message or thread reply, or upload local files only in explicitly allowed channel IDs.
|
|
12
|
+
- Prefix every message the agent posts with `🤖:` — or `🤖 <model>:` when the call names the model — so agent replies stand apart from the account's own messages; an edit can opt out with `agent_authored: false`.
|
|
12
13
|
|
|
13
14
|
Attachment limits: images and text files are limited to 10 MB; text attachments are truncated at 100,000 characters. PDFs are limited to 20 MB, 50 pages, and 100,000 extracted characters.
|
|
14
15
|
|
|
@@ -19,10 +20,38 @@ Attachment limits: images and text files are limited to 10 MB; text attachments
|
|
|
19
20
|
| `slack_read_permalink` | Read a message or thread from a channel, DM, or group-DM permalink, including supported attachments. |
|
|
20
21
|
| `slack_get_replies` | Read replies for a thread by channel ID and timestamp. |
|
|
21
22
|
| `slack_get_history` | Read recent channel history. |
|
|
22
|
-
| `slack_reply_to_thread` | Reply to a thread; restricted to allowed channels. |
|
|
23
|
+
| `slack_reply_to_thread` | Reply to a thread, with optional `model`, in a `🤖:`-prefixed message; restricted to allowed channels. |
|
|
23
24
|
| `slack_add_reaction` | Add a reaction; restricted to allowed channels. |
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
25
|
+
| `slack_remove_reaction` | Remove a reaction added by this account; restricted to allowed channels. |
|
|
26
|
+
| `slack_upload_files` | Upload up to 10 local files to a channel or thread, with an optional `initial_comment` prefixed with `🤖:` and an optional `model` (only used with a comment); returns file and message permalinks; restricted to allowed channels. |
|
|
27
|
+
| `slack_edit_message` | Edit an authored message or thread reply by channel ID and timestamp; replaces the message text with `🤖:`-prefixed text and an optional `model`, or with the text as given when `agent_authored` is `false` (Slack removes any existing Block Kit blocks) and returns the updated timestamp and permalink; restricted to allowed channels. |
|
|
28
|
+
|
|
29
|
+
## Agent message prefix
|
|
30
|
+
|
|
31
|
+
The write tools post through the account's own browser session, so Slack shows
|
|
32
|
+
the account name on them. To keep agent-written messages distinguishable, the
|
|
33
|
+
text of every posted message is prefixed with `🤖:` by default:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
🤖: deployed revision 4f2c1a9
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The MCP transport does not report which model is calling, so a write tool call
|
|
40
|
+
can pass `model` with the model it is running; the prefix then names it:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
🤖 claude-sonnet-4-5: deployed revision 4f2c1a9
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The prefix is added once: text that already starts with `🤖` is sent unchanged,
|
|
47
|
+
so editing a prefixed message does not stack prefixes. A file upload without an
|
|
48
|
+
`initial_comment` — or with only whitespace — gets no comment rather than a bare
|
|
49
|
+
prefix, and `model` has no effect then.
|
|
50
|
+
|
|
51
|
+
`slack_edit_message` can also replace a message with text that is not
|
|
52
|
+
agent-written, such as a correction to the user's own message: pass
|
|
53
|
+
`agent_authored: false` and the text is stored as given, without the prefix.
|
|
54
|
+
`model` has no effect when `agent_authored` is `false`.
|
|
26
55
|
|
|
27
56
|
## Setup
|
|
28
57
|
|
|
@@ -60,9 +89,10 @@ All configuration can live in a JSON config file at
|
|
|
60
89
|
```
|
|
61
90
|
|
|
62
91
|
By default the write tools (`slack_reply_to_thread`, `slack_add_reaction`,
|
|
63
|
-
`slack_edit_message`) and `slack_upload_files` are
|
|
64
|
-
writable. Each upload is limited to 10 MB. Every
|
|
65
|
-
missing file just means defaults. Env vars always override
|
|
92
|
+
`slack_remove_reaction`, `slack_edit_message`) and `slack_upload_files` are
|
|
93
|
+
locked down — no channel is writable. Each upload is limited to 10 MB. Every
|
|
94
|
+
field is optional; a missing file just means defaults. Env vars always override
|
|
95
|
+
the config file.
|
|
66
96
|
|
|
67
97
|
## Configuration
|
|
68
98
|
|
package/index.js
CHANGED
|
@@ -370,8 +370,33 @@ function assertAllowedWriteChannel(channelId, allowed) {
|
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
const AGENT_PREFIX_EMOJI = "🤖";
|
|
374
|
+
|
|
375
|
+
// Slack shows the account's own name on everything this server posts, so mark
|
|
376
|
+
// agent-written text with a robot emoji and name the model that wrote it when
|
|
377
|
+
// the caller says which one it is running. Text is trimmed and blank text has no
|
|
378
|
+
// prefix to carry, so an optional comment stays absent. Text that already
|
|
379
|
+
// carries the prefix is returned unchanged, so editing a prefixed message does
|
|
380
|
+
// not stack prefixes.
|
|
381
|
+
function withAgentPrefix(text, model) {
|
|
382
|
+
const value = typeof text === "string" ? text.trim() : "";
|
|
383
|
+
if (!value) {
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
if (value.startsWith(AGENT_PREFIX_EMOJI)) {
|
|
387
|
+
return value;
|
|
388
|
+
}
|
|
389
|
+
const name = typeof model === "string" ? model.trim() : "";
|
|
390
|
+
return `${AGENT_PREFIX_EMOJI}${name ? ` ${name}` : ""}: ${value}`;
|
|
391
|
+
}
|
|
392
|
+
|
|
373
393
|
// ── Schemas ─────────────────────────────────────────────────────────────────
|
|
374
394
|
|
|
395
|
+
const AgentModelField = z
|
|
396
|
+
.string()
|
|
397
|
+
.optional()
|
|
398
|
+
.describe("Model you are running, e.g. gpt-5.1, shown in the 🤖 prefix. Omit when unknown.");
|
|
399
|
+
|
|
375
400
|
const ReadPermalinkSchema = z.object({
|
|
376
401
|
url: z.string().describe("Slack permalink, e.g. https://<workspace>.slack.com/archives/C.../p..."),
|
|
377
402
|
});
|
|
@@ -391,6 +416,7 @@ const ReplyToThreadSchema = z.object({
|
|
|
391
416
|
channel_id: z.string(),
|
|
392
417
|
thread_ts: z.string(),
|
|
393
418
|
text: z.string(),
|
|
419
|
+
model: AgentModelField,
|
|
394
420
|
});
|
|
395
421
|
|
|
396
422
|
const AddReactionSchema = z.object({
|
|
@@ -404,12 +430,19 @@ const UploadFilesSchema = z.object({
|
|
|
404
430
|
file_paths: z.array(z.string().min(1)).min(1).max(10),
|
|
405
431
|
thread_ts: z.string().optional(),
|
|
406
432
|
initial_comment: z.string().optional(),
|
|
433
|
+
model: AgentModelField,
|
|
407
434
|
});
|
|
408
435
|
|
|
409
436
|
const EditMessageSchema = z.object({
|
|
410
437
|
channel_id: z.string(),
|
|
411
438
|
timestamp: z.string().describe("Timestamp of the message or thread reply to edit."),
|
|
412
439
|
text: z.string(),
|
|
440
|
+
agent_authored: z
|
|
441
|
+
.boolean()
|
|
442
|
+
.optional()
|
|
443
|
+
.default(true)
|
|
444
|
+
.describe("Set false when this text is not agent-written, for example correcting the user's own message, so it is stored without the 🤖 prefix; model is ignored when this is false."),
|
|
445
|
+
model: AgentModelField,
|
|
413
446
|
});
|
|
414
447
|
|
|
415
448
|
// ── Tool definitions ─────────────────────────────────────────────────────────
|
|
@@ -432,7 +465,7 @@ const TOOLS = [
|
|
|
432
465
|
},
|
|
433
466
|
{
|
|
434
467
|
name: "slack_reply_to_thread",
|
|
435
|
-
description: "Reply to a Slack thread. Restricted to allowed channels.",
|
|
468
|
+
description: "Reply to a Slack thread. The posted text is prefixed with `🤖:` — or `🤖 <model>:` when model is given — so readers can tell it came from an agent; pass model to name the model writing it. Restricted to allowed channels.",
|
|
436
469
|
inputSchema: zodToJsonSchema(ReplyToThreadSchema),
|
|
437
470
|
},
|
|
438
471
|
{
|
|
@@ -440,14 +473,19 @@ const TOOLS = [
|
|
|
440
473
|
description: "Add a reaction to a Slack message. Restricted to allowed channels.",
|
|
441
474
|
inputSchema: zodToJsonSchema(AddReactionSchema),
|
|
442
475
|
},
|
|
476
|
+
{
|
|
477
|
+
name: "slack_remove_reaction",
|
|
478
|
+
description: "Remove a reaction added by this account from a Slack message. Restricted to allowed channels.",
|
|
479
|
+
inputSchema: zodToJsonSchema(AddReactionSchema),
|
|
480
|
+
},
|
|
443
481
|
{
|
|
444
482
|
name: "slack_upload_files",
|
|
445
|
-
description: "Upload one or more local files to a channel or thread. Restricted to allowed channels.",
|
|
483
|
+
description: "Upload one or more local files to a channel or thread. A given initial_comment is prefixed with `🤖:` — or `🤖 <model>:` when model is given — so readers can tell it came from an agent; pass model to name the model writing it, and note that model has no effect without initial_comment. Restricted to allowed channels.",
|
|
446
484
|
inputSchema: zodToJsonSchema(UploadFilesSchema),
|
|
447
485
|
},
|
|
448
486
|
{
|
|
449
487
|
name: "slack_edit_message",
|
|
450
|
-
description: "Edit an authored Slack message or thread reply by channel id and timestamp. Replaces the message text;
|
|
488
|
+
description: "Edit an authored Slack message or thread reply by channel id and timestamp. Replaces the message text, prefixed with `🤖:` — or `🤖 <model>:` when model is given — unless it already carries the prefix; pass model to name the model writing it, and set agent_authored to false when the new text is not agent-written. Any existing Block Kit blocks are removed by Slack. Restricted to allowed channels.",
|
|
451
489
|
inputSchema: zodToJsonSchema(EditMessageSchema),
|
|
452
490
|
},
|
|
453
491
|
];
|
|
@@ -495,7 +533,7 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
|
|
|
495
533
|
const response = await slackApi("chat.postMessage", {
|
|
496
534
|
channel: channelId,
|
|
497
535
|
thread_ts: requireString(a.thread_ts, "thread_ts"),
|
|
498
|
-
text: requireString(a.text, "text"),
|
|
536
|
+
text: withAgentPrefix(requireString(a.text, "text"), a.model),
|
|
499
537
|
}, "POST");
|
|
500
538
|
return { content: [{ type: "text", text: `posted reply at ${response.ts}` }] };
|
|
501
539
|
}
|
|
@@ -512,6 +550,18 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
|
|
|
512
550
|
return { content: [{ type: "text", text: "added reaction" }] };
|
|
513
551
|
}
|
|
514
552
|
|
|
553
|
+
case "slack_remove_reaction": {
|
|
554
|
+
const a = AddReactionSchema.parse(args);
|
|
555
|
+
const channelId = requireString(a.channel_id, "channel_id");
|
|
556
|
+
assertAllowedWriteChannel(channelId, allowed);
|
|
557
|
+
await slackApi("reactions.remove", {
|
|
558
|
+
channel: channelId,
|
|
559
|
+
timestamp: requireString(a.timestamp, "timestamp"),
|
|
560
|
+
name: requireString(a.reaction, "reaction"),
|
|
561
|
+
}, "POST");
|
|
562
|
+
return { content: [{ type: "text", text: "removed reaction" }] };
|
|
563
|
+
}
|
|
564
|
+
|
|
515
565
|
case "slack_upload_files": {
|
|
516
566
|
const a = UploadFilesSchema.parse(args);
|
|
517
567
|
const channelId = requireString(a.channel_id, "channel_id");
|
|
@@ -520,7 +570,7 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
|
|
|
520
570
|
filePaths: a.file_paths,
|
|
521
571
|
channelId,
|
|
522
572
|
threadTs: a.thread_ts && requireString(a.thread_ts, "thread_ts"),
|
|
523
|
-
initialComment: a.initial_comment,
|
|
573
|
+
initialComment: withAgentPrefix(a.initial_comment, a.model),
|
|
524
574
|
});
|
|
525
575
|
return {
|
|
526
576
|
content: files.map((file) => ({
|
|
@@ -541,7 +591,7 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
|
|
|
541
591
|
updated = await slackApi("chat.update", {
|
|
542
592
|
channel: channelId,
|
|
543
593
|
ts: timestamp,
|
|
544
|
-
text,
|
|
594
|
+
text: a.agent_authored ? withAgentPrefix(text, a.model) : text,
|
|
545
595
|
}, "POST");
|
|
546
596
|
} catch (error) {
|
|
547
597
|
const reason = String(error?.message ?? error).replace(/^slack chat\.update failed: /, "");
|
|
@@ -599,7 +649,7 @@ async function main() {
|
|
|
599
649
|
{
|
|
600
650
|
capabilities: { tools: {} },
|
|
601
651
|
instructions:
|
|
602
|
-
"MCP server for Slack. Read with slack_read_permalink, slack_get_replies, slack_get_history; write with slack_reply_to_thread, slack_add_reaction, slack_upload_files, slack_edit_message (restricted to allowed channels).",
|
|
652
|
+
"MCP server for Slack. Read with slack_read_permalink, slack_get_replies, slack_get_history; write with slack_reply_to_thread, slack_add_reaction, slack_remove_reaction, slack_upload_files, slack_edit_message (restricted to allowed channels). Messages sent by the write tools are prefixed with `🤖:` by default, and slack_edit_message can opt out with agent_authored false; pass model with the model you are running to include it in the prefix.",
|
|
603
653
|
}
|
|
604
654
|
);
|
|
605
655
|
|
|
@@ -644,6 +694,7 @@ export {
|
|
|
644
694
|
parsePermalink,
|
|
645
695
|
requireString,
|
|
646
696
|
assertAllowedWriteChannel,
|
|
697
|
+
withAgentPrefix,
|
|
647
698
|
createExecuteToolCall,
|
|
648
699
|
createSlackClient,
|
|
649
700
|
loadConfig,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yt-slack-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Slack — read permalinks, threads, and channel history; reply to threads; add reactions; edit messages.",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "MCP server for Slack — read permalinks, threads, and channel history; reply to threads; add or remove reactions; edit messages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|