yt-slack-mcp 0.4.0 → 0.6.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 +61 -19
- package/index.js +76 -23
- package/package.json +3 -2
- package/skills/slack-mcp/SKILL.md +134 -0
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ 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
|
+
- Have no plain top-level text post: a reply needs an existing thread, and a file upload with an `initial_comment` is the only way to start a top-level message.
|
|
13
|
+
- 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
14
|
|
|
13
15
|
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
16
|
|
|
@@ -18,12 +20,39 @@ Attachment limits: images and text files are limited to 10 MB; text attachments
|
|
|
18
20
|
|------|-------------|
|
|
19
21
|
| `slack_read_permalink` | Read a message or thread from a channel, DM, or group-DM permalink, including supported attachments. |
|
|
20
22
|
| `slack_get_replies` | Read replies for a thread by channel ID and timestamp. |
|
|
21
|
-
| `slack_get_history` | Read recent channel history. |
|
|
22
|
-
| `slack_reply_to_thread` | Reply to a thread; restricted to allowed channels. |
|
|
23
|
+
| `slack_get_history` | Read recent channel history by channel ID (up to 200 messages). |
|
|
24
|
+
| `slack_reply_to_thread` | Reply to a thread, with optional `model`, in a `🤖:`-prefixed message; restricted to allowed channels. |
|
|
23
25
|
| `slack_add_reaction` | Add a reaction; restricted to allowed channels. |
|
|
24
26
|
| `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
|
|
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. |
|
|
27
|
+
| `slack_upload_files` | Upload up to 10 local files (10 MB each) 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. |
|
|
28
|
+
| `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. |
|
|
29
|
+
|
|
30
|
+
## Agent message prefix
|
|
31
|
+
|
|
32
|
+
The write tools post through the account's own browser session, so Slack shows
|
|
33
|
+
the account name on them. To keep agent-written messages distinguishable, the
|
|
34
|
+
text of every posted message is prefixed with `🤖:` by default:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
🤖: deployed revision 4f2c1a9
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The MCP transport does not report which model is calling, so a write tool call
|
|
41
|
+
can pass `model` with the model it is running; the prefix then names it:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
🤖 claude-sonnet-4-5: deployed revision 4f2c1a9
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The prefix is added once: text that already starts with `🤖` is sent unchanged,
|
|
48
|
+
so editing a prefixed message does not stack prefixes. A file upload without an
|
|
49
|
+
`initial_comment` — or with only whitespace — gets no comment rather than a bare
|
|
50
|
+
prefix, and `model` has no effect then.
|
|
51
|
+
|
|
52
|
+
`slack_edit_message` can also replace a message with text that is not
|
|
53
|
+
agent-written, such as a correction to the user's own message: pass
|
|
54
|
+
`agent_authored: false` and the text is stored as given, without the prefix.
|
|
55
|
+
`model` has no effect when `agent_authored` is `false`.
|
|
27
56
|
|
|
28
57
|
## Setup
|
|
29
58
|
|
|
@@ -43,12 +72,12 @@ Save each token as a single-line file:
|
|
|
43
72
|
~/.config/yt-slack-mcp/secrets/slack-xoxd.txt
|
|
44
73
|
```
|
|
45
74
|
|
|
46
|
-
The server reads these paths by default. Override them
|
|
75
|
+
The server reads these paths by default. Override them in the config file described below if your MCP host manages secrets elsewhere.
|
|
47
76
|
|
|
48
77
|
### 3. (Optional) Configure allowed write channels and paths
|
|
49
78
|
|
|
50
|
-
All configuration
|
|
51
|
-
`~/.config/yt-slack-mcp/config.json
|
|
79
|
+
All configuration lives in a JSON config file at
|
|
80
|
+
`~/.config/yt-slack-mcp/config.json`:
|
|
52
81
|
|
|
53
82
|
```json
|
|
54
83
|
{
|
|
@@ -62,22 +91,27 @@ All configuration can live in a JSON config file at
|
|
|
62
91
|
|
|
63
92
|
By default the write tools (`slack_reply_to_thread`, `slack_add_reaction`,
|
|
64
93
|
`slack_remove_reaction`, `slack_edit_message`) and `slack_upload_files` are
|
|
65
|
-
locked down — no channel is writable. Each
|
|
66
|
-
field is optional; a missing file just
|
|
67
|
-
|
|
94
|
+
locked down — no channel is writable. Each uploaded file is limited to 10 MB, and
|
|
95
|
+
a call accepts at most 10 files. Every field is optional; a missing file just
|
|
96
|
+
means defaults.
|
|
68
97
|
|
|
69
98
|
## Configuration
|
|
70
99
|
|
|
71
|
-
|
|
100
|
+
Configuration comes from the JSON config file, with built-in defaults for
|
|
101
|
+
absent keys.
|
|
72
102
|
|
|
73
|
-
| Config
|
|
74
|
-
|
|
75
|
-
| `secretsDir` |
|
|
76
|
-
| `xoxcPath` |
|
|
77
|
-
| `xoxdPath` |
|
|
78
|
-
| `allowedWriteChannels` |
|
|
79
|
-
| `userAgent` | `
|
|
80
|
-
|
|
103
|
+
| Config key | Default | Description |
|
|
104
|
+
|------------|---------|-------------|
|
|
105
|
+
| `secretsDir` | `~/.config/yt-slack-mcp/secrets` | Directory holding the token files |
|
|
106
|
+
| `xoxcPath` | `$SECRETS_DIR/slack-xoxc.txt` | Path to the `xoxc` token |
|
|
107
|
+
| `xoxdPath` | `$SECRETS_DIR/slack-xoxd.txt` | Path to the `xoxd` token |
|
|
108
|
+
| `allowedWriteChannels` | *(none)* | Channel IDs allowed for write tools |
|
|
109
|
+
| `userAgent` | `Mozilla/5.0` | HTTP `User-Agent` header |
|
|
110
|
+
|
|
111
|
+
Environment-variable configuration has been removed. Move any environment-supplied
|
|
112
|
+
values into `~/.config/yt-slack-mcp/config.json`; the config path is fixed for the
|
|
113
|
+
CLI. `allowedWriteChannels` accepts an array or a comma-separated string, so an
|
|
114
|
+
old comma-separated value can be copied as-is.
|
|
81
115
|
|
|
82
116
|
## MCP host configuration
|
|
83
117
|
|
|
@@ -100,6 +134,14 @@ For example, Claude Desktop uses:
|
|
|
100
134
|
}
|
|
101
135
|
```
|
|
102
136
|
|
|
137
|
+
## Install as a skill
|
|
138
|
+
|
|
139
|
+
[`skills/slack-mcp/SKILL.md`](skills/slack-mcp/SKILL.md) packages this server as
|
|
140
|
+
an Agent Skill so an agent knows when to read Slack, when a write is allowed,
|
|
141
|
+
and which tool fits the request. Copy or symlink the `skills/slack-mcp`
|
|
142
|
+
directory into your agent's skills directory (for example
|
|
143
|
+
`.agents/skills/slack-mcp`); the server still needs the setup above.
|
|
144
|
+
|
|
103
145
|
## License
|
|
104
146
|
|
|
105
147
|
MIT
|
package/index.js
CHANGED
|
@@ -20,6 +20,8 @@ import { fileURLToPath } from "url";
|
|
|
20
20
|
|
|
21
21
|
const DEFAULT_SECRETS_DIR = path.join(os.homedir(), ".config", "yt-slack-mcp", "secrets");
|
|
22
22
|
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".config", "yt-slack-mcp", "config.json");
|
|
23
|
+
const PACKAGE_INFO = JSON.parse(fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"));
|
|
24
|
+
const SERVER_INFO = { name: "yt-slack-mcp", version: PACKAGE_INFO.version };
|
|
23
25
|
|
|
24
26
|
const maxImageBytes = 10 * 1024 * 1024;
|
|
25
27
|
const maxPdfBytes = 20 * 1024 * 1024;
|
|
@@ -46,14 +48,29 @@ function parseChannelList(value) {
|
|
|
46
48
|
if (value === undefined || value === null || value === "") {
|
|
47
49
|
return null;
|
|
48
50
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const list = Array.isArray(value)
|
|
52
|
+
? value
|
|
53
|
+
: typeof value === "string"
|
|
54
|
+
? value.split(",")
|
|
55
|
+
: null;
|
|
56
|
+
if (list === null) {
|
|
57
|
+
throw new Error("channel IDs must be an array or a comma-separated string");
|
|
58
|
+
}
|
|
59
|
+
return list
|
|
60
|
+
.map((channel) => {
|
|
61
|
+
if (typeof channel !== "string") {
|
|
62
|
+
throw new Error("channel IDs must be strings");
|
|
63
|
+
}
|
|
64
|
+
return channel.trim();
|
|
65
|
+
})
|
|
52
66
|
.filter(Boolean);
|
|
53
67
|
}
|
|
54
68
|
|
|
55
|
-
function loadConfig(
|
|
56
|
-
const configPath =
|
|
69
|
+
function loadConfig(input = DEFAULT_CONFIG_PATH) {
|
|
70
|
+
const configPath =
|
|
71
|
+
typeof input === "string"
|
|
72
|
+
? input
|
|
73
|
+
: input?.SLACK_MCP_CONFIG || DEFAULT_CONFIG_PATH;
|
|
57
74
|
let fileConfig = {};
|
|
58
75
|
if (fs.existsSync(configPath)) {
|
|
59
76
|
try {
|
|
@@ -63,15 +80,16 @@ function loadConfig(env = process.env) {
|
|
|
63
80
|
}
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
const secretsDir =
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
const secretsDir = expandHome(fileConfig.secretsDir) || DEFAULT_SECRETS_DIR;
|
|
84
|
+
const xoxcPath = expandHome(fileConfig.xoxcPath) || path.join(secretsDir, "slack-xoxc.txt");
|
|
85
|
+
const xoxdPath = expandHome(fileConfig.xoxdPath) || path.join(secretsDir, "slack-xoxd.txt");
|
|
86
|
+
let allowedWriteChannels;
|
|
87
|
+
try {
|
|
88
|
+
allowedWriteChannels = parseChannelList(fileConfig.allowedWriteChannels) ?? [];
|
|
89
|
+
} catch (e) {
|
|
90
|
+
throw new Error(`invalid Slack MCP config ${configPath}: ${e.message}`);
|
|
91
|
+
}
|
|
92
|
+
const userAgent = fileConfig.userAgent || "Mozilla/5.0";
|
|
75
93
|
|
|
76
94
|
return { configPath, secretsDir, xoxcPath, xoxdPath, allowedWriteChannels, userAgent };
|
|
77
95
|
}
|
|
@@ -370,8 +388,33 @@ function assertAllowedWriteChannel(channelId, allowed) {
|
|
|
370
388
|
}
|
|
371
389
|
}
|
|
372
390
|
|
|
391
|
+
const AGENT_PREFIX_EMOJI = "🤖";
|
|
392
|
+
|
|
393
|
+
// Slack shows the account's own name on everything this server posts, so mark
|
|
394
|
+
// agent-written text with a robot emoji and name the model that wrote it when
|
|
395
|
+
// the caller says which one it is running. Text is trimmed and blank text has no
|
|
396
|
+
// prefix to carry, so an optional comment stays absent. Text that already
|
|
397
|
+
// carries the prefix is returned unchanged, so editing a prefixed message does
|
|
398
|
+
// not stack prefixes.
|
|
399
|
+
function withAgentPrefix(text, model) {
|
|
400
|
+
const value = typeof text === "string" ? text.trim() : "";
|
|
401
|
+
if (!value) {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
if (value.startsWith(AGENT_PREFIX_EMOJI)) {
|
|
405
|
+
return value;
|
|
406
|
+
}
|
|
407
|
+
const name = typeof model === "string" ? model.trim() : "";
|
|
408
|
+
return `${AGENT_PREFIX_EMOJI}${name ? ` ${name}` : ""}: ${value}`;
|
|
409
|
+
}
|
|
410
|
+
|
|
373
411
|
// ── Schemas ─────────────────────────────────────────────────────────────────
|
|
374
412
|
|
|
413
|
+
const AgentModelField = z
|
|
414
|
+
.string()
|
|
415
|
+
.optional()
|
|
416
|
+
.describe("Model you are running, e.g. gpt-5.1, shown in the 🤖 prefix. Omit when unknown.");
|
|
417
|
+
|
|
375
418
|
const ReadPermalinkSchema = z.object({
|
|
376
419
|
url: z.string().describe("Slack permalink, e.g. https://<workspace>.slack.com/archives/C.../p..."),
|
|
377
420
|
});
|
|
@@ -391,6 +434,7 @@ const ReplyToThreadSchema = z.object({
|
|
|
391
434
|
channel_id: z.string(),
|
|
392
435
|
thread_ts: z.string(),
|
|
393
436
|
text: z.string(),
|
|
437
|
+
model: AgentModelField,
|
|
394
438
|
});
|
|
395
439
|
|
|
396
440
|
const AddReactionSchema = z.object({
|
|
@@ -404,12 +448,19 @@ const UploadFilesSchema = z.object({
|
|
|
404
448
|
file_paths: z.array(z.string().min(1)).min(1).max(10),
|
|
405
449
|
thread_ts: z.string().optional(),
|
|
406
450
|
initial_comment: z.string().optional(),
|
|
451
|
+
model: AgentModelField,
|
|
407
452
|
});
|
|
408
453
|
|
|
409
454
|
const EditMessageSchema = z.object({
|
|
410
455
|
channel_id: z.string(),
|
|
411
456
|
timestamp: z.string().describe("Timestamp of the message or thread reply to edit."),
|
|
412
457
|
text: z.string(),
|
|
458
|
+
agent_authored: z
|
|
459
|
+
.boolean()
|
|
460
|
+
.optional()
|
|
461
|
+
.default(true)
|
|
462
|
+
.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."),
|
|
463
|
+
model: AgentModelField,
|
|
413
464
|
});
|
|
414
465
|
|
|
415
466
|
// ── Tool definitions ─────────────────────────────────────────────────────────
|
|
@@ -432,7 +483,7 @@ const TOOLS = [
|
|
|
432
483
|
},
|
|
433
484
|
{
|
|
434
485
|
name: "slack_reply_to_thread",
|
|
435
|
-
description: "Reply to a Slack thread. Restricted to allowed channels.",
|
|
486
|
+
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
487
|
inputSchema: zodToJsonSchema(ReplyToThreadSchema),
|
|
437
488
|
},
|
|
438
489
|
{
|
|
@@ -447,12 +498,12 @@ const TOOLS = [
|
|
|
447
498
|
},
|
|
448
499
|
{
|
|
449
500
|
name: "slack_upload_files",
|
|
450
|
-
description: "Upload one or more local files to a channel or thread. Restricted to allowed channels.",
|
|
501
|
+
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
502
|
inputSchema: zodToJsonSchema(UploadFilesSchema),
|
|
452
503
|
},
|
|
453
504
|
{
|
|
454
505
|
name: "slack_edit_message",
|
|
455
|
-
description: "Edit an authored Slack message or thread reply by channel id and timestamp. Replaces the message text;
|
|
506
|
+
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
507
|
inputSchema: zodToJsonSchema(EditMessageSchema),
|
|
457
508
|
},
|
|
458
509
|
];
|
|
@@ -500,7 +551,7 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
|
|
|
500
551
|
const response = await slackApi("chat.postMessage", {
|
|
501
552
|
channel: channelId,
|
|
502
553
|
thread_ts: requireString(a.thread_ts, "thread_ts"),
|
|
503
|
-
text: requireString(a.text, "text"),
|
|
554
|
+
text: withAgentPrefix(requireString(a.text, "text"), a.model),
|
|
504
555
|
}, "POST");
|
|
505
556
|
return { content: [{ type: "text", text: `posted reply at ${response.ts}` }] };
|
|
506
557
|
}
|
|
@@ -537,7 +588,7 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
|
|
|
537
588
|
filePaths: a.file_paths,
|
|
538
589
|
channelId,
|
|
539
590
|
threadTs: a.thread_ts && requireString(a.thread_ts, "thread_ts"),
|
|
540
|
-
initialComment: a.initial_comment,
|
|
591
|
+
initialComment: withAgentPrefix(a.initial_comment, a.model),
|
|
541
592
|
});
|
|
542
593
|
return {
|
|
543
594
|
content: files.map((file) => ({
|
|
@@ -558,7 +609,7 @@ function createExecuteToolCall({ slackApi, formatMessages, uploadSlackFiles, all
|
|
|
558
609
|
updated = await slackApi("chat.update", {
|
|
559
610
|
channel: channelId,
|
|
560
611
|
ts: timestamp,
|
|
561
|
-
text,
|
|
612
|
+
text: a.agent_authored ? withAgentPrefix(text, a.model) : text,
|
|
562
613
|
}, "POST");
|
|
563
614
|
} catch (error) {
|
|
564
615
|
const reason = String(error?.message ?? error).replace(/^slack chat\.update failed: /, "");
|
|
@@ -612,11 +663,11 @@ async function main() {
|
|
|
612
663
|
const { slackApi, formatMessages, uploadSlackFiles } = createSlackClient({ xoxcToken, xoxdToken, userAgent: config.userAgent });
|
|
613
664
|
|
|
614
665
|
const server = new Server(
|
|
615
|
-
|
|
666
|
+
SERVER_INFO,
|
|
616
667
|
{
|
|
617
668
|
capabilities: { tools: {} },
|
|
618
669
|
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).",
|
|
670
|
+
"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
671
|
}
|
|
621
672
|
);
|
|
622
673
|
|
|
@@ -658,14 +709,16 @@ if (isMain) {
|
|
|
658
709
|
|
|
659
710
|
export {
|
|
660
711
|
TOOLS,
|
|
712
|
+
SERVER_INFO,
|
|
713
|
+
parseChannelList,
|
|
661
714
|
parsePermalink,
|
|
662
715
|
requireString,
|
|
663
716
|
assertAllowedWriteChannel,
|
|
717
|
+
withAgentPrefix,
|
|
664
718
|
createExecuteToolCall,
|
|
665
719
|
createSlackClient,
|
|
666
720
|
loadConfig,
|
|
667
721
|
expandHome,
|
|
668
|
-
parseChannelList,
|
|
669
722
|
ReadPermalinkSchema,
|
|
670
723
|
GetRepliesSchema,
|
|
671
724
|
GetHistorySchema,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yt-slack-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "node --test --experimental-test-coverage"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
-
"index.js"
|
|
14
|
+
"index.js",
|
|
15
|
+
"skills/*/SKILL.md"
|
|
15
16
|
],
|
|
16
17
|
"keywords": [
|
|
17
18
|
"mcp",
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slack-mcp
|
|
3
|
+
description: Read and write Slack through yt-slack-mcp — read permalinks, threads, channel history, and attachments; reply in threads, react, upload files, and edit messages in allowed channels. Use when asked to inspect a Slack link, summarize a channel or thread, find recent channel messages, or reply, react, upload, or edit in Slack.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Slack MCP
|
|
7
|
+
|
|
8
|
+
Use the `yt-slack-mcp` MCP server through this skill. Read before writing, and
|
|
9
|
+
write only where the channel allow-list permits.
|
|
10
|
+
|
|
11
|
+
Server: `yt-slack-mcp` 0.6.0, launched as `npx yt-slack-mcp` over stdio.
|
|
12
|
+
|
|
13
|
+
## Setup and auth
|
|
14
|
+
|
|
15
|
+
The server talks to Slack through your logged-in browser session, not a bot
|
|
16
|
+
token.
|
|
17
|
+
|
|
18
|
+
1. Save the `xoxc` token (Slack web localStorage) as a single line in
|
|
19
|
+
`~/.config/yt-slack-mcp/secrets/slack-xoxc.txt`.
|
|
20
|
+
2. Save the `xoxd` token (the cookie named `d`) as a single line in
|
|
21
|
+
`~/.config/yt-slack-mcp/secrets/slack-xoxd.txt`.
|
|
22
|
+
3. Allow writes by listing channel IDs in `allowedWriteChannels` in
|
|
23
|
+
`~/.config/yt-slack-mcp/config.json`. With an empty or missing list, every
|
|
24
|
+
write is refused.
|
|
25
|
+
|
|
26
|
+
Configuration is file-based: `config.json` also accepts `secretsDir`, `xoxcPath`,
|
|
27
|
+
`xoxdPath`, and `userAgent`, and the defaults apply when a key is absent. Never
|
|
28
|
+
echo a token or a file's contents back to the user.
|
|
29
|
+
|
|
30
|
+
### What each failure means
|
|
31
|
+
|
|
32
|
+
| Message | Cause | Fix |
|
|
33
|
+
|---------|-------|-----|
|
|
34
|
+
| `Error: missing slack-xoxc.txt (looked at <path>)` | The `xoxc` file is absent at startup; the server exits before listing tools. | Write the token file at the reported path, or set `xoxcPath` in `config.json` to point at it. |
|
|
35
|
+
| `Error: missing slack-xoxd.txt (looked at <path>)` | Same, for the `xoxd` file. | Write the token file at the reported path, or set `xoxdPath` in `config.json` to point at it. |
|
|
36
|
+
| `Error: invalid Slack MCP config <path>: <detail>` | `config.json` is not valid JSON, or a value has the wrong type; the detail names the problem. | Fix the JSON, correct the value, or remove the file. |
|
|
37
|
+
| `Error: writes are not allowed for channel <id>` | The channel is not in the allow-list. | Add the channel ID to the allow-list if the user wants writes there; never work around the refusal. |
|
|
38
|
+
| `slack <method> failed: invalid_auth` (or `not_authed`) | The browser session expired or the token was copied incorrectly. | Refresh the `xoxc`/`xoxd` pair, then retry once. |
|
|
39
|
+
| `Error: missing <name>` | A required write-tool argument (`channel_id`, `thread_ts`, `timestamp`, `text`, `reaction`) was blank. | Supply the actual value; do not invent IDs. |
|
|
40
|
+
|
|
41
|
+
Tool errors arrive as text starting with `Error:`. Report the text instead of
|
|
42
|
+
retrying the same call unchanged.
|
|
43
|
+
|
|
44
|
+
## Troubleshooting
|
|
45
|
+
|
|
46
|
+
- **Writes refused or tokens missing after an upgrade.** Configuration is
|
|
47
|
+
file-based only: values supplied through environment variables are ignored.
|
|
48
|
+
Move `secretsDir`, `xoxcPath`, `xoxdPath`, `allowedWriteChannels`, and
|
|
49
|
+
`userAgent` into `~/.config/yt-slack-mcp/config.json`.
|
|
50
|
+
- **Only some expected channels are writable.** `allowedWriteChannels` is the
|
|
51
|
+
complete list with no additive default; compare it with the channel IDs the
|
|
52
|
+
user expects.
|
|
53
|
+
- **The server exits at startup.** A reported token path is missing; create the
|
|
54
|
+
file at that path. An empty or stale token does not stop startup; the first
|
|
55
|
+
Slack call fails later as `invalid_auth`.
|
|
56
|
+
- **A write still fails inside an allowed channel.** The browser session may have
|
|
57
|
+
lost write access or the message may be outside the edit window; report the
|
|
58
|
+
exact `Error:` text instead of retrying.
|
|
59
|
+
|
|
60
|
+
## Tool map
|
|
61
|
+
|
|
62
|
+
Choose the read tool from what the user gave you, and the write tool from the
|
|
63
|
+
action they asked for.
|
|
64
|
+
|
|
65
|
+
| Tool | Use it when |
|
|
66
|
+
|------|-------------|
|
|
67
|
+
| `slack_read_permalink` | A Slack link is the input. Reads the linked message's thread, including inline images, PDF text, and text attachments. |
|
|
68
|
+
| `slack_get_replies` | You have a channel ID and thread timestamp and need that thread again or its latest replies. |
|
|
69
|
+
| `slack_get_history` | There is no link or thread: read recent messages when you have a channel ID. The tool does not resolve channel names. |
|
|
70
|
+
| `slack_reply_to_thread` | Post a reply into an existing thread. Requires `channel_id`, `thread_ts`, `text`, and preferably `model`. |
|
|
71
|
+
| `slack_add_reaction` | Mark or acknowledge a message without posting text. |
|
|
72
|
+
| `slack_remove_reaction` | Remove a reaction this account added. |
|
|
73
|
+
| `slack_upload_files` | Post up to 10 local files (10 MB each) to a channel or thread, with an optional `initial_comment`. |
|
|
74
|
+
| `slack_edit_message` | Correct a message this account already posted, by channel ID and timestamp. |
|
|
75
|
+
|
|
76
|
+
The server has no plain top-level text post; a reply needs an existing thread.
|
|
77
|
+
A file upload with an `initial_comment` is the only way to start a new top-level
|
|
78
|
+
message. Say so instead of promising a text post.
|
|
79
|
+
|
|
80
|
+
## Recipes
|
|
81
|
+
|
|
82
|
+
### Read a permalink and reply in its thread
|
|
83
|
+
|
|
84
|
+
1. Call `slack_read_permalink` with the `url`.
|
|
85
|
+
2. Take the channel ID from the permalink and the root `[ts]` from the first
|
|
86
|
+
returned message.
|
|
87
|
+
3. Read the whole thread before answering; do not repost what is already there.
|
|
88
|
+
4. Call `slack_reply_to_thread` with `channel_id`, `thread_ts`, `text`, and
|
|
89
|
+
`model` set to the model you are running.
|
|
90
|
+
|
|
91
|
+
### Summarize a channel
|
|
92
|
+
|
|
93
|
+
1. Resolve the channel ID first. The tool does not accept names like `#ops`; take
|
|
94
|
+
the ID from a permalink or ask the user.
|
|
95
|
+
2. Call `slack_get_history` with `channel_id` and `limit` up to 200.
|
|
96
|
+
3. Summarize in the conversation, quoting `[ts]` values when the user needs to
|
|
97
|
+
locate a message.
|
|
98
|
+
4. Post the summary only when asked, and only as a reply in a relevant thread or
|
|
99
|
+
as a file upload's `initial_comment`; there is no plain top-level text post.
|
|
100
|
+
|
|
101
|
+
### Upload a file with a comment
|
|
102
|
+
|
|
103
|
+
1. Resolve absolute local paths; at most 10 files, 10 MB each.
|
|
104
|
+
2. Call `slack_upload_files` with `file_paths`, `channel_id`, an optional
|
|
105
|
+
`thread_ts`, and `initial_comment` when a message should accompany the files.
|
|
106
|
+
3. Pass `model`, which is used only when `initial_comment` is present; without a
|
|
107
|
+
comment the upload stays comment-free.
|
|
108
|
+
4. Report the returned file and message permalinks.
|
|
109
|
+
|
|
110
|
+
### Correct a posted message
|
|
111
|
+
|
|
112
|
+
1. Call `slack_edit_message` with `channel_id`, the message `timestamp`, and the
|
|
113
|
+
replacement `text`.
|
|
114
|
+
2. Leave `agent_authored` at its default for agent-written text; pass `model` so
|
|
115
|
+
the prefix names the model.
|
|
116
|
+
3. Pass `agent_authored: false` only when replacing agent text with the user's own
|
|
117
|
+
words, such as correcting their message; `model` is then ignored.
|
|
118
|
+
4. Slack removes existing Block Kit blocks on edit and only allows edits to this
|
|
119
|
+
account's messages within its edit window.
|
|
120
|
+
|
|
121
|
+
## Guardrails
|
|
122
|
+
|
|
123
|
+
- Read before writing. Fetch the thread or channel context first, even when the
|
|
124
|
+
requested text seems obvious.
|
|
125
|
+
- Stay inside the allow-list. Never attempt a write to a channel that is not
|
|
126
|
+
listed, and never suggest disabling the check to "make it work".
|
|
127
|
+
- Pass `model` on every text-writing call — reply, upload with a comment, edit —
|
|
128
|
+
so posts read `🤖 <model>:`. Never add the prefix yourself; the server adds it
|
|
129
|
+
once.
|
|
130
|
+
- `agent_authored: false` is an explicit correction of non-agent text, not a
|
|
131
|
+
default and not a way to hide that an agent posted.
|
|
132
|
+
- Channel names are not resolved. Never pass `#name` as `channel_id`; use a
|
|
133
|
+
`C...`, `G...`, or `D...` ID from a permalink or ask the user.
|
|
134
|
+
- Prefer edits over reposting. Do not "fix" a message by posting it again.
|