yt-slack-mcp 0.5.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/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
+ - 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.
12
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`.
13
14
 
14
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.
@@ -19,11 +20,11 @@ Attachment limits: images and text files are limited to 10 MB; text attachments
19
20
  |------|-------------|
20
21
  | `slack_read_permalink` | Read a message or thread from a channel, DM, or group-DM permalink, including supported attachments. |
21
22
  | `slack_get_replies` | Read replies for a thread by channel ID and timestamp. |
22
- | `slack_get_history` | Read recent channel history. |
23
+ | `slack_get_history` | Read recent channel history by channel ID (up to 200 messages). |
23
24
  | `slack_reply_to_thread` | Reply to a thread, with optional `model`, in a `🤖:`-prefixed message; restricted to allowed channels. |
24
25
  | `slack_add_reaction` | Add a reaction; restricted to allowed channels. |
25
26
  | `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_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. |
27
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. |
28
29
 
29
30
  ## Agent message prefix
@@ -71,12 +72,12 @@ Save each token as a single-line file:
71
72
  ~/.config/yt-slack-mcp/secrets/slack-xoxd.txt
72
73
  ```
73
74
 
74
- The server reads these paths by default. Override them with the config file or environment variables if your MCP host manages secrets elsewhere.
75
+ The server reads these paths by default. Override them in the config file described below if your MCP host manages secrets elsewhere.
75
76
 
76
77
  ### 3. (Optional) Configure allowed write channels and paths
77
78
 
78
- All configuration can live in a JSON config file at
79
- `~/.config/yt-slack-mcp/config.json` (override with `SLACK_MCP_CONFIG`):
79
+ All configuration lives in a JSON config file at
80
+ `~/.config/yt-slack-mcp/config.json`:
80
81
 
81
82
  ```json
82
83
  {
@@ -90,22 +91,27 @@ All configuration can live in a JSON config file at
90
91
 
91
92
  By default the write tools (`slack_reply_to_thread`, `slack_add_reaction`,
92
93
  `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.
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.
96
97
 
97
98
  ## Configuration
98
99
 
99
- Config resolution order: built-in defaults → config file → env vars.
100
+ Configuration comes from the JSON config file, with built-in defaults for
101
+ absent keys.
100
102
 
101
- | Config file key | Env var | Default | Description |
102
- |-----------------|---------|---------|-------------|
103
- | `secretsDir` | `SLACK_MCP_SECRETS_DIR` | `~/.config/yt-slack-mcp/secrets` | Directory holding the token files |
104
- | `xoxcPath` | `SLACK_MCP_XOXC_PATH` | `$SECRETS_DIR/slack-xoxc.txt` | Path to the `xoxc` token |
105
- | `xoxdPath` | `SLACK_MCP_XOXD_PATH` | `$SECRETS_DIR/slack-xoxd.txt` | Path to the `xoxd` token |
106
- | `allowedWriteChannels` | `SLACK_MCP_ALLOWED_WRITE_CHANNELS` | *(none)* | Channel IDs allowed for write tools (comma-separated in env) |
107
- | `userAgent` | `SLACK_MCP_USER_AGENT` | `Mozilla/5.0` | HTTP `User-Agent` header |
108
- | *(config file path)* | `SLACK_MCP_CONFIG` | `~/.config/yt-slack-mcp/config.json` | Path to the JSON config file |
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.
109
115
 
110
116
  ## MCP host configuration
111
117
 
@@ -128,6 +134,14 @@ For example, Claude Desktop uses:
128
134
  }
129
135
  ```
130
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
+
131
145
  ## License
132
146
 
133
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
- return value
50
- .split(",")
51
- .map((c) => c.trim())
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(env = process.env) {
56
- const configPath = env.SLACK_MCP_CONFIG || DEFAULT_CONFIG_PATH;
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
- env.SLACK_MCP_SECRETS_DIR || expandHome(fileConfig.secretsDir) || DEFAULT_SECRETS_DIR;
68
- const xoxcPath =
69
- env.SLACK_MCP_XOXC_PATH || expandHome(fileConfig.xoxcPath) || path.join(secretsDir, "slack-xoxc.txt");
70
- const xoxdPath =
71
- env.SLACK_MCP_XOXD_PATH || expandHome(fileConfig.xoxdPath) || path.join(secretsDir, "slack-xoxd.txt");
72
- const allowedWriteChannels =
73
- parseChannelList(env.SLACK_MCP_ALLOWED_WRITE_CHANNELS) ?? fileConfig.allowedWriteChannels ?? [];
74
- const userAgent = env.SLACK_MCP_USER_AGENT || fileConfig.userAgent || "Mozilla/5.0";
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
  }
@@ -645,7 +663,7 @@ async function main() {
645
663
  const { slackApi, formatMessages, uploadSlackFiles } = createSlackClient({ xoxcToken, xoxdToken, userAgent: config.userAgent });
646
664
 
647
665
  const server = new Server(
648
- { name: "yt-slack-mcp", version: "0.1.0" },
666
+ SERVER_INFO,
649
667
  {
650
668
  capabilities: { tools: {} },
651
669
  instructions:
@@ -691,6 +709,8 @@ if (isMain) {
691
709
 
692
710
  export {
693
711
  TOOLS,
712
+ SERVER_INFO,
713
+ parseChannelList,
694
714
  parsePermalink,
695
715
  requireString,
696
716
  assertAllowedWriteChannel,
@@ -699,7 +719,6 @@ export {
699
719
  createSlackClient,
700
720
  loadConfig,
701
721
  expandHome,
702
- parseChannelList,
703
722
  ReadPermalinkSchema,
704
723
  GetRepliesSchema,
705
724
  GetHistorySchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-slack-mcp",
3
- "version": "0.5.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.