Skip to main content
Glama

Slack MCP Server

by lbeatu
server.ts14.1 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { Dependencies } from "./config/dependencies.js"; export const dependencies = new Dependencies(); // Helper function to initialize with token parameter async function initializeWithTokenParam(token?: string) { if (!token) { return { content: [ { type: "text" as const, text: "❌ SLACK_TOKEN parameter is required. Please provide your Slack bot token.", }, ], }; } const success = await dependencies.initializeWithToken(token); if (!success) { return { content: [ { type: "text" as const, text: "❌ Failed to initialize with provided token. Please check your Slack token.", }, ], }; } return null; // Success } function createServer(): McpServer { const server = new McpServer({ name: "SlackMCPServer", version: "1.0.0", capabilities: { resources: {}, tools: {}, }, }); // List channels tool server.tool( "slack_list_channels", "List public channels in the workspace with pagination", { limit: z .number() .optional() .describe( "Maximum number of channels to return (default 100, max 200)" ), cursor: z .string() .optional() .describe("Pagination cursor for next page of results"), token: z.string().describe("Slack bot token (e.g., xoxb-...)"), }, async ({ limit, cursor, token }) => { try { const initCheck = await initializeWithTokenParam(token); if (initCheck) return initCheck; const result = await dependencies.listChannelsUseCase.execute({ limit, cursor, }); return result; } catch (error) { return { content: [ { type: "text" as const, text: `❌ Error listing channels: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } ); // Post message tool server.tool( "slack_post_message", "Post a new message to a Slack channel", { channel_id: z.string().describe("The ID of the channel to post to"), text: z.string().describe("The message text to post"), token: z.string().describe("Slack bot token (e.g., xoxb-...)"), }, async ({ channel_id, text, token }) => { try { const initCheck = await initializeWithTokenParam(token); if (initCheck) return initCheck; const result = await dependencies.postMessageUseCase.execute({ channel_id, text, }); return result; } catch (error) { return { content: [ { type: "text" as const, text: `❌ Error posting message: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } ); // Reply to thread tool server.tool( "slack_reply_to_thread", "Reply to a specific message thread in Slack", { channel_id: z .string() .describe("The ID of the channel containing the thread"), thread_ts: z .string() .describe( "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: z.string().describe("The reply text"), token: z.string().describe("Slack bot token (e.g., xoxb-...)"), }, async ({ channel_id, thread_ts, text, token }) => { try { const initCheck = await initializeWithTokenParam(token); if (initCheck) return initCheck; const result = await dependencies.slackService.replyToThread({ channel_id, thread_ts, text, }); if (!result.success) { return { content: [ { type: "text" as const, text: `❌ Failed to reply to thread: ${result.error}`, }, ], }; } return { content: [ { type: "text" as const, text: `✅ **Reply posted successfully!** 🧵\n\n**Channel**: <#${channel_id}>\n**Thread**: ${thread_ts}\n**Content**: ${text}`, }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `❌ Error replying to thread: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } ); // Add reaction tool server.tool( "slack_add_reaction", "Add a reaction emoji to a message", { channel_id: z .string() .describe("The ID of the channel containing the message"), timestamp: z .string() .describe("The timestamp of the message to react to"), reaction: z .string() .describe("The name of the emoji reaction (without ::)"), token: z.string().describe("Slack bot token (e.g., xoxb-...)"), }, async ({ channel_id, timestamp, reaction, token }) => { try { const initCheck = await initializeWithTokenParam(token); if (initCheck) return initCheck; const result = await dependencies.slackService.addReaction({ channel_id, timestamp, reaction, }); if (!result.success) { return { content: [ { type: "text" as const, text: `❌ Failed to add reaction: ${result.error}`, }, ], }; } return { content: [ { type: "text" as const, text: `✅ **Reaction added successfully!** :${reaction}:\n\n**Channel**: <#${channel_id}>\n**Message**: ${timestamp}`, }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `❌ Error adding reaction: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } ); // Get channel history tool server.tool( "slack_get_channel_history", "Get recent messages from a channel", { channel_id: z.string().describe("The ID of the channel"), limit: z .number() .optional() .describe("Number of messages to retrieve (default 10)"), token: z.string().describe("Slack bot token (e.g., xoxb-...)"), }, async ({ channel_id, limit, token }) => { try { const initCheck = await initializeWithTokenParam(token); if (initCheck) return initCheck; const result = await dependencies.getChannelHistoryUseCase.execute({ channel_id, limit, }); return result; } catch (error) { return { content: [ { type: "text" as const, text: `❌ Error getting channel history: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } ); // Get thread replies tool server.tool( "slack_get_thread_replies", "Get all replies in a message thread", { channel_id: z .string() .describe("The ID of the channel containing the thread"), thread_ts: z .string() .describe( "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." ), token: z.string().describe("Slack bot token (e.g., xoxb-...)"), }, async ({ channel_id, thread_ts, token }) => { try { const initCheck = await initializeWithTokenParam(token); if (initCheck) return initCheck; const result = await dependencies.slackService.getThreadReplies({ channel_id, thread_ts, }); if (!result.success) { return { content: [ { type: "text" as const, text: `❌ Failed to get thread replies: ${result.error}`, }, ], }; } const { messages } = result.data!; if (messages.length === 0) { return { content: [ { type: "text" as const, text: `📭 No replies found in thread \`${thread_ts}\`.`, }, ], }; } const replyList = messages .map((message, index) => { const timestamp = new Date( parseFloat(message.ts) * 1000 ).toLocaleString(); return `**${index + 1}.** <@${message.user}> - ${timestamp}\n ${ message.text }`; }) .join("\n\n"); return { content: [ { type: "text" as const, text: `🧵 **Thread Replies** (${messages.length} messages)\n**Channel**: <#${channel_id}>\n**Thread**: ${thread_ts}\n\n${replyList}`, }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `❌ Error getting thread replies: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } ); // Parse Slack URL and get thread details server.tool( "slack_parse_url", "AUTOMATICALLY parse Slack URLs to get thread/message content. Use this IMMEDIATELY when user shares a Slack URL instead of asking them to copy-paste content.", { url: z .string() .describe( "Slack URL (e.g., https://workspace.slack.com/archives/CHANNEL_ID/pTIMESTAMP)" ), token: z.string().describe("Slack bot token (e.g., xoxb-...)"), }, async ({ url, token }) => { try { const initCheck = await initializeWithTokenParam(token); if (initCheck) return initCheck; // Parse Slack URL - supports various formats const urlPattern = /https:\/\/([^.]+)\.slack\.com\/archives\/([^\/]+)\/p(\d+)(\d{6})?/; const match = url.match(urlPattern); if (!match) { return { content: [ { type: "text" as const, text: "❌ Invalid Slack URL format. Expected format: https://workspace.slack.com/archives/CHANNEL_ID/pTIMESTAMP", }, ], }; } const [, workspace, channelId, timestamp, microseconds] = match; const messageTs = microseconds ? `${timestamp}.${microseconds}` : `${timestamp}.000000`; // First, get channel info const channelsResult = await dependencies.listChannelsUseCase.execute( {} ); if (!channelsResult.content || channelsResult.content.length === 0) { return { content: [ { type: "text" as const, text: "❌ Could not fetch channels to find channel info", }, ], }; } const channelInfo = channelsResult.content[0].text.includes(channelId) ? channelsResult.content[0].text : `Channel: ${channelId}`; // Get channel history to find the specific message const historyResult = await dependencies.getChannelHistoryUseCase.execute({ channel_id: channelId, limit: 100, }); if (!historyResult.content || historyResult.content.length === 0) { return { content: [ { type: "text" as const, text: `❌ Could not fetch messages from channel ${channelId}`, }, ], }; } // Try to get thread replies if this is a threaded message let threadInfo = ""; try { const threadResult = await dependencies.slackService.getThreadReplies( { channel_id: channelId, thread_ts: messageTs, } ); if ( threadResult.success && threadResult.data && threadResult.data.messages && threadResult.data.messages.length > 0 ) { threadInfo = `\n\n🧵 **Thread with ${threadResult.data.messages.length} replies:**\n`; threadResult.data.messages.forEach((reply: any, index: number) => { threadInfo += `${index + 1}. **${reply.user}**: ${reply.text}\n`; }); } } catch (error) { // Thread might not exist, that's okay } return { content: [ { type: "text" as const, text: `🔗 **Slack Message Details** **Workspace**: ${workspace} **Channel**: ${channelId} **Message Timestamp**: ${messageTs} **URL**: ${url} 📋 **Channel History:** ${historyResult.content[0].text}${threadInfo} 💡 *Use slack_get_thread_replies with channel_id="${channelId}" and thread_ts="${messageTs}" to get more thread details*`, }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `❌ Error parsing Slack URL: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } ); return server; } export default createServer;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lbeatu/slack-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server