yt-slack-mcp 0.4.0 → 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.
Files changed (4) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +31 -3
  3. package/index.js +41 -7
  4. package/package.json +1 -1
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Maksim Korinets
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maksim Korinets
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -9,6 +9,7 @@ A host-agnostic [Model Context Protocol](https://modelcontextprotocol.io) server
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
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,11 +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
  | `slack_remove_reaction` | Remove a reaction added by this account; restricted to allowed channels. |
25
- | `slack_upload_files` | Upload up to 10 local files to a channel or thread, with an optional initial comment; returns file and message permalinks; restricted to allowed channels. |
26
- | `slack_edit_message` | Edit an authored message or thread reply by channel ID and timestamp; replaces the message text (Slack removes any existing Block Kit blocks) and returns the updated timestamp and permalink; 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`.
27
55
 
28
56
  ## Setup
29
57
 
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
  {
@@ -447,12 +480,12 @@ const TOOLS = [
447
480
  },
448
481
  {
449
482
  name: "slack_upload_files",
450
- 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.",
451
484
  inputSchema: zodToJsonSchema(UploadFilesSchema),
452
485
  },
453
486
  {
454
487
  name: "slack_edit_message",
455
- description: "Edit an authored Slack message or thread reply by channel id and timestamp. Replaces the message text; any existing Block Kit blocks are removed by Slack. Restricted to allowed channels.",
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.",
456
489
  inputSchema: zodToJsonSchema(EditMessageSchema),
457
490
  },
458
491
  ];
@@ -500,7 +533,7 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
500
533
  const response = await slackApi("chat.postMessage", {
501
534
  channel: channelId,
502
535
  thread_ts: requireString(a.thread_ts, "thread_ts"),
503
- text: requireString(a.text, "text"),
536
+ text: withAgentPrefix(requireString(a.text, "text"), a.model),
504
537
  }, "POST");
505
538
  return { content: [{ type: "text", text: `posted reply at ${response.ts}` }] };
506
539
  }
@@ -537,7 +570,7 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
537
570
  filePaths: a.file_paths,
538
571
  channelId,
539
572
  threadTs: a.thread_ts && requireString(a.thread_ts, "thread_ts"),
540
- initialComment: a.initial_comment,
573
+ initialComment: withAgentPrefix(a.initial_comment, a.model),
541
574
  });
542
575
  return {
543
576
  content: files.map((file) => ({
@@ -558,7 +591,7 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
558
591
  updated = await slackApi("chat.update", {
559
592
  channel: channelId,
560
593
  ts: timestamp,
561
- text,
594
+ text: a.agent_authored ? withAgentPrefix(text, a.model) : text,
562
595
  }, "POST");
563
596
  } catch (error) {
564
597
  const reason = String(error?.message ?? error).replace(/^slack chat\.update failed: /, "");
@@ -616,7 +649,7 @@ async function main() {
616
649
  {
617
650
  capabilities: { tools: {} },
618
651
  instructions:
619
- "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).",
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.",
620
653
  }
621
654
  );
622
655
 
@@ -661,6 +694,7 @@ export {
661
694
  parsePermalink,
662
695
  requireString,
663
696
  assertAllowedWriteChannel,
697
+ withAgentPrefix,
664
698
  createExecuteToolCall,
665
699
  createSlackClient,
666
700
  loadConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-slack-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
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",