@microagents/server-slack
Version:
MCP server for Slack API integration
396 lines • 14.8 kB
JavaScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
import { setMicroagentsCredentials } from "@microagents/common";
await setMicroagentsCredentials();
// Tool definitions
const listChannelsTool = {
name: "slack_list_channels",
description: "List public channels in the workspace with pagination",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description: "Maximum number of channels to return (default 100, max 200)",
default: 100,
},
cursor: {
type: "string",
description: "Pagination cursor for next page of results",
},
},
},
};
const postMessageTool = {
name: "slack_post_message",
description: "Post a new message to a Slack channel",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel to post to",
},
text: {
type: "string",
description: "The message text to post",
},
},
required: ["channel_id", "text"],
},
};
const replyToThreadTool = {
name: "slack_reply_to_thread",
description: "Reply to a specific message thread in Slack",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel containing the thread",
},
thread_ts: {
type: "string",
description: "The timestamp of the parent message in the format '1234567890.123456'. Timestamps in the format without the period can be converted by adding the period such that 6 numbers come after it.",
},
text: {
type: "string",
description: "The reply text",
},
},
required: ["channel_id", "thread_ts", "text"],
},
};
const addReactionTool = {
name: "slack_add_reaction",
description: "Add a reaction emoji to a message",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel containing the message",
},
timestamp: {
type: "string",
description: "The timestamp of the message to react to",
},
reaction: {
type: "string",
description: "The name of the emoji reaction (without ::)",
},
},
required: ["channel_id", "timestamp", "reaction"],
},
};
const getChannelHistoryTool = {
name: "slack_get_channel_history",
description: "Get recent messages from a channel",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel",
},
limit: {
type: "number",
description: "Number of messages to retrieve (default 10)",
default: 10,
},
},
required: ["channel_id"],
},
};
const getThreadRepliesTool = {
name: "slack_get_thread_replies",
description: "Get all replies in a message thread",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel containing the thread",
},
thread_ts: {
type: "string",
description: "The timestamp of the parent message in the format '1234567890.123456'. Timestamps in the format without the period can be converted by adding the period such that 6 numbers come after it.",
},
},
required: ["channel_id", "thread_ts"],
},
};
const getUsersTool = {
name: "slack_get_users",
description: "Get a list of all users in the workspace with their basic profile information",
inputSchema: {
type: "object",
properties: {
cursor: {
type: "string",
description: "Pagination cursor for next page of results",
},
limit: {
type: "number",
description: "Maximum number of users to return (default 100, max 200)",
default: 100,
},
},
},
};
const getUserProfileTool = {
name: "slack_get_user_profile",
description: "Get detailed profile information for a specific user",
inputSchema: {
type: "object",
properties: {
user_id: {
type: "string",
description: "The ID of the user",
},
},
required: ["user_id"],
},
};
class SlackClient {
botHeaders;
constructor(botToken) {
this.botHeaders = {
Authorization: `Bearer ${botToken}`,
"Content-Type": "application/json",
};
}
async getChannels(limit = 100, cursor) {
const params = new URLSearchParams({
types: "public_channel",
exclude_archived: "true",
limit: Math.min(limit, 200).toString(),
team_id: process.env.SLACK_TEAM_ID,
});
if (cursor) {
params.append("cursor", cursor);
}
const response = await fetch(`https://slack.com/api/conversations.list?${params}`, { headers: this.botHeaders });
return response.json();
}
async postMessage(channel_id, text) {
const response = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: this.botHeaders,
body: JSON.stringify({
channel: channel_id,
text: text,
}),
});
return response.json();
}
async postReply(channel_id, thread_ts, text) {
const response = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: this.botHeaders,
body: JSON.stringify({
channel: channel_id,
thread_ts: thread_ts,
text: text,
}),
});
return response.json();
}
async addReaction(channel_id, timestamp, reaction) {
const response = await fetch("https://slack.com/api/reactions.add", {
method: "POST",
headers: this.botHeaders,
body: JSON.stringify({
channel: channel_id,
timestamp: timestamp,
name: reaction,
}),
});
return response.json();
}
async getChannelHistory(channel_id, limit = 10) {
const params = new URLSearchParams({
channel: channel_id,
limit: limit.toString(),
});
const response = await fetch(`https://slack.com/api/conversations.history?${params}`, { headers: this.botHeaders });
return response.json();
}
async getThreadReplies(channel_id, thread_ts) {
const params = new URLSearchParams({
channel: channel_id,
ts: thread_ts,
});
const response = await fetch(`https://slack.com/api/conversations.replies?${params}`, { headers: this.botHeaders });
return response.json();
}
async getUsers(limit = 100, cursor) {
const params = new URLSearchParams({
limit: Math.min(limit, 200).toString(),
team_id: process.env.SLACK_TEAM_ID,
});
if (cursor) {
params.append("cursor", cursor);
}
const response = await fetch(`https://slack.com/api/users.list?${params}`, {
headers: this.botHeaders,
});
return response.json();
}
async getUserProfile(user_id) {
const params = new URLSearchParams({
user: user_id,
include_labels: "true",
});
const response = await fetch(`https://slack.com/api/users.profile.get?${params}`, { headers: this.botHeaders });
return response.json();
}
}
async function main() {
const botToken = process.env.SLACK_BOT_TOKEN;
const teamId = process.env.SLACK_TEAM_ID;
if (!botToken || !teamId) {
console.error("Please set SLACK_BOT_TOKEN and SLACK_TEAM_ID environment variables");
process.exit(1);
}
console.error("Starting Slack MCP Server...");
const server = new Server({
name: "Slack MCP Server",
version: "1.0.0",
}, {
capabilities: {
tools: {},
},
});
const slackClient = new SlackClient(botToken);
server.setRequestHandler(CallToolRequestSchema, async (request) => {
console.error("Received CallToolRequest:", request);
try {
if (!request.params.arguments) {
throw new Error("No arguments provided");
}
switch (request.params.name) {
case "slack_list_channels": {
const args = request.params
.arguments;
const response = await slackClient.getChannels(args.limit, args.cursor);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_post_message": {
const args = request.params.arguments;
if (!args.channel_id || !args.text) {
throw new Error("Missing required arguments: channel_id and text");
}
const response = await slackClient.postMessage(args.channel_id, args.text);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_reply_to_thread": {
const args = request.params
.arguments;
if (!args.channel_id || !args.thread_ts || !args.text) {
throw new Error("Missing required arguments: channel_id, thread_ts, and text");
}
const response = await slackClient.postReply(args.channel_id, args.thread_ts, args.text);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_add_reaction": {
const args = request.params.arguments;
if (!args.channel_id || !args.timestamp || !args.reaction) {
throw new Error("Missing required arguments: channel_id, timestamp, and reaction");
}
const response = await slackClient.addReaction(args.channel_id, args.timestamp, args.reaction);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_get_channel_history": {
const args = request.params
.arguments;
if (!args.channel_id) {
throw new Error("Missing required argument: channel_id");
}
const response = await slackClient.getChannelHistory(args.channel_id, args.limit);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_get_thread_replies": {
const args = request.params
.arguments;
if (!args.channel_id || !args.thread_ts) {
throw new Error("Missing required arguments: channel_id and thread_ts");
}
const response = await slackClient.getThreadReplies(args.channel_id, args.thread_ts);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_get_users": {
const args = request.params.arguments;
const response = await slackClient.getUsers(args.limit, args.cursor);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_get_user_profile": {
const args = request.params
.arguments;
if (!args.user_id) {
throw new Error("Missing required argument: user_id");
}
const response = await slackClient.getUserProfile(args.user_id);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
}
catch (error) {
console.error("Error executing tool:", error);
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
}),
},
],
};
}
});
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error("Received ListToolsRequest");
return {
tools: [
listChannelsTool,
postMessageTool,
replyToThreadTool,
addReactionTool,
getChannelHistoryTool,
getThreadRepliesTool,
getUsersTool,
getUserProfileTool,
],
};
});
const transport = new StdioServerTransport();
console.error("Connecting server to transport...");
await server.connect(transport);
console.error("Slack MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
//# sourceMappingURL=index.js.map