Skip to content

Commit 4c2ef7a

Browse files
authored
Merge pull request #40 from arvoreeducacao/joao-barros-/-add-send-channel-message
feat(slack-advanced): add send_channel_message tool
2 parents 07ab848 + b75e302 commit 4c2ef7a

5 files changed

Lines changed: 101 additions & 1 deletion

File tree

packages/slack-advanced/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@arvoretech/slack-advanced-mcp",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "Advanced Slack MCP Server with semantic user search, smart DMs, style analysis, thread extraction, audio transcription, and image analysis",
55
"main": "dist/index.js",
66
"type": "module",

packages/slack-advanced/src/server.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
TranscribeAudioParamsSchema,
1919
AnalyzeImageParamsSchema,
2020
GetFileInfoParamsSchema,
21+
SendChannelMessageParamsSchema,
2122
} from "./types.js";
2223

2324
export class SlackAdvancedMCPServer {
@@ -91,6 +92,15 @@ export class SlackAdvancedMCPServer {
9192
return this.messagingTools.sendDm(SendDmParamsSchema.parse(params));
9293
});
9394

95+
this.server.registerTool("send_channel_message", {
96+
title: "Send Channel Message",
97+
description:
98+
"Send a message to a Slack channel. Accepts channel ID or #channel-name. Supports thread replies and markdown link conversion.",
99+
inputSchema: SendChannelMessageParamsSchema.shape,
100+
}, async (params) => {
101+
return this.messagingTools.sendChannelMessage(SendChannelMessageParamsSchema.parse(params));
102+
});
103+
94104
this.server.registerTool("get_dm_history", {
95105
title: "Get DM History",
96106
description:

packages/slack-advanced/src/slack-client.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,36 @@ export class SlackClient {
205205
);
206206
}
207207

208+
async resolveChannelId(identifier: string): Promise<string> {
209+
if (/^[A-Z0-9]+$/.test(identifier)) {
210+
return identifier;
211+
}
212+
213+
const name = identifier.replace(/^#/, "");
214+
215+
let cursor: string | undefined;
216+
do {
217+
const params: Record<string, unknown> = { limit: 200, types: "public_channel,private_channel" };
218+
if (cursor) params.cursor = cursor;
219+
220+
const res = await this.request<{
221+
ok: boolean;
222+
channels: Array<{ id: string; name: string }>;
223+
response_metadata?: { next_cursor?: string };
224+
}>("conversations.list", params);
225+
226+
const match = res.channels.find((c) => c.name === name);
227+
if (match) return match.id;
228+
229+
cursor = res.response_metadata?.next_cursor || undefined;
230+
} while (cursor);
231+
232+
throw new SlackAdvancedMCPError(
233+
`Could not resolve channel: ${identifier}`,
234+
"CHANNEL_NOT_FOUND"
235+
);
236+
}
237+
208238
async openDm(userId: string): Promise<string> {
209239
const res = await this.request<{
210240
ok: boolean;

packages/slack-advanced/src/tools/messaging.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { SlackClient } from "../slack-client.js";
22
import type {
33
SendDmParams,
44
GetDmHistoryParams,
5+
SendChannelMessageParams,
56
McpToolResult,
67
SlackMessage,
78
} from "../types.js";
@@ -82,6 +83,45 @@ export class MessagingTools {
8283
}
8384
}
8485

86+
async sendChannelMessage(params: SendChannelMessageParams): Promise<McpToolResult> {
87+
try {
88+
const channelId = await this.slack.resolveChannelId(params.channel);
89+
90+
let text = params.text;
91+
if (params.content_type === "text/markdown") {
92+
text = this.convertMarkdownLinksToMrkdwn(text);
93+
}
94+
95+
const msgParams: Record<string, unknown> = {
96+
channel: channelId,
97+
text,
98+
};
99+
100+
if (params.thread_ts) {
101+
msgParams.thread_ts = params.thread_ts;
102+
}
103+
104+
const res = await this.slack.request<{
105+
ok: boolean;
106+
channel: string;
107+
ts: string;
108+
message: { text: string; ts: string };
109+
}>("chat.postMessage", msgParams);
110+
111+
return this.ok({
112+
sent: true,
113+
channel: res.channel,
114+
ts: res.ts,
115+
});
116+
} catch (error) {
117+
return this.formatError(error);
118+
}
119+
}
120+
121+
private convertMarkdownLinksToMrkdwn(text: string): string {
122+
return text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>");
123+
}
124+
85125
private ok(data: unknown): McpToolResult {
86126
return {
87127
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],

packages/slack-advanced/src/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,25 @@ export const GetFileInfoParamsSchema = z.object({
128128
.describe("Slack file ID"),
129129
});
130130

131+
export const SendChannelMessageParamsSchema = z.object({
132+
channel: z
133+
.string()
134+
.min(1, "Channel is required")
135+
.describe("Channel ID (e.g. C01EXAMPLE) or channel name (e.g. #general)"),
136+
text: z
137+
.string()
138+
.min(1, "Message text is required")
139+
.describe("Message content (supports Slack mrkdwn)"),
140+
thread_ts: z
141+
.string()
142+
.optional()
143+
.describe("Thread timestamp to reply in a thread"),
144+
content_type: z
145+
.enum(["text/plain", "text/markdown"])
146+
.optional()
147+
.describe("Content type for link formatting. Use text/markdown for [text](url) links"),
148+
});
149+
131150
export type SearchUsersParams = z.infer<typeof SearchUsersParamsSchema>;
132151
export type GetUserProfileParams = z.infer<typeof GetUserProfileParamsSchema>;
133152
export type SendDmParams = z.infer<typeof SendDmParamsSchema>;
@@ -137,6 +156,7 @@ export type GetThreadFromLinkParams = z.infer<typeof GetThreadFromLinkParamsSche
137156
export type TranscribeAudioParams = z.infer<typeof TranscribeAudioParamsSchema>;
138157
export type AnalyzeImageParams = z.infer<typeof AnalyzeImageParamsSchema>;
139158
export type GetFileInfoParams = z.infer<typeof GetFileInfoParamsSchema>;
159+
export type SendChannelMessageParams = z.infer<typeof SendChannelMessageParamsSchema>;
140160

141161
export type McpTextContent = {
142162
type: "text";

0 commit comments

Comments
 (0)