From f7aa430300f9e02f131f349cb3c7e3264772ff59 Mon Sep 17 00:00:00 2001 From: Floren Munteanu <19806136+fmunteanu@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:38:32 -0400 Subject: [PATCH] fix: code improvements --- src/index.ts | 8 +- src/server/client.ts | 6 +- src/server/mcp.ts | 252 +++++------------------------------------ src/server/tool.ts | 263 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 299 insertions(+), 230 deletions(-) create mode 100644 src/server/tool.ts diff --git a/src/index.ts b/src/index.ts index b195994..d223e9a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,12 +11,12 @@ */ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { SlackMcpServer } from './server/mcp.js'; +import { McpServer } from './server/mcp.js'; /** * Main entry point for the Slack MCP Server * - * Validates environment variables, initializes the SlackMcpServer, + * Validates environment variables, initializes the McpServer, * and establishes stdio transport for communication with Claude agents. * * @async @@ -47,10 +47,10 @@ async function main(): Promise { console.error('Please set SLACK_BOT_TOKEN and SLACK_TEAM_ID environment variables'); process.exit(1); } - const slackServer = new SlackMcpServer(botToken); + const mcpServer = new McpServer(botToken); const transport = new StdioServerTransport(); try { - await slackServer.connect(transport); + await mcpServer.connect(transport); } catch (error) { console.error('Failed to connect MCP transport:', error); } diff --git a/src/server/client.ts b/src/server/client.ts index ff1cf9b..6f5dcb6 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -16,9 +16,9 @@ import { fileURLToPath } from 'url'; * Provides secure Slack API operations with rate limiting, content sanitization, * and URL validation for safe workspace interactions. * - * @class SlackClient + * @class Client */ -export class SlackClient { +export class Client { private botHeaders: { Authorization: string; 'Content-Type': string }; private rateLimiter: Map = new Map(); private readonly API = 'https://slack.com/api'; @@ -29,7 +29,7 @@ export class SlackClient { private readonly RATE_LIMIT_WINDOW = 60000; /** - * Creates a new SlackClient instance + * Creates a new Client instance * * @param {string} botToken - Slack bot token for API authentication */ diff --git a/src/server/mcp.ts b/src/server/mcp.ts index 56e5d2b..b9af82b 100644 --- a/src/server/mcp.ts +++ b/src/server/mcp.ts @@ -15,7 +15,8 @@ import { Tool, } from '@modelcontextprotocol/sdk/types.js'; import slackifyMarkdown from 'slackify-markdown'; -import { SlackClient } from './client.js'; +import { Client } from './client.js'; +import { McpTool } from './tool.js'; interface AddReactionArgs { channel_id: string; @@ -68,177 +69,41 @@ interface ReplyToThreadArgs { type ToolHandler = (args: any) => Promise; /** - * Slack MCP Server implementation + * Slack MCP Server implementation bridging Slack API with Model Context Protocol + * + * Provides comprehensive interface for Slack workspace operations through MCP tools, + * managing API communication, request routing, and response formatting. * - * @class SlackMcpServer + * @class McpServer */ -export class SlackMcpServer { - private client: SlackClient; +export class McpServer { + private client: Client; private server: Server; + private tool: McpTool; private toolHandlers: Map; - private transport?: StdioServerTransport; + private users: number; /** - * Creates a new SlackMcpServer instance + * Creates a new McpServer instance with tool setup + * + * Initializes client, MCP server, and tool registry. + * Sets up handler mappings and prepares for transport connection. * * @param {string} botToken - Slack bot token for API authentication */ constructor(botToken: string) { - this.client = new SlackClient(botToken); + this.client = new Client(botToken); + this.users = 100; this.server = new Server( { name: 'slack', version: this.client.version() }, { capabilities: { tools: {} } } ); + this.tool = new McpTool(this.users); this.toolHandlers = new Map(); this.setupToolHandlers(); this.setupHandlers(); } - /** - * Tool definition for adding reaction emojis to messages - * - * @private - * @returns {Tool} Add reaction tool definition - */ - private addReactionTool(): Tool { - return { - name: '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'] - } - }; - } - - /** - * Tool definition for editing existing messages - * - * @private - * @returns {Tool} Edit message tool definition - */ - private editMessageTool(): Tool { - return { - name: 'edit_message', - description: 'Edit an existing message in a Slack channel', - 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 edit' }, - text: { type: 'string', description: 'The message text to edit' } - }, - required: ['channel_id', 'timestamp', 'text'] - } - }; - } - - /** - * Tool definition for retrieving channel message history - * - * @private - * @returns {Tool} Channel history tool definition - */ - private getChannelHistoryTool(): Tool { - return { - name: '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'] - } - }; - } - - /** - * Tool definition for getting thread replies - * - * @private - * @returns {Tool} Thread replies tool definition - */ - private getThreadRepliesTool(): Tool { - return { - name: '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 (format: 1234567890.123456)' } - }, - required: ['channel_id', 'thread_ts'] - } - }; - } - - /** - * Returns all available MCP tools - * - * @private - * @returns {Tool[]} Array of MCP tool definitions - */ - private getTools(): Tool[] { - return [ - this.addReactionTool(), - this.editMessageTool(), - this.getChannelHistoryTool(), - this.getThreadRepliesTool(), - this.getUserProfileTool(), - this.getUsersTool(), - this.listChannelsTool(), - this.postMessageTool(), - this.replyToThreadTool() - ]; - } - - /** - * Tool definition for getting detailed user profile information - * - * @private - * @returns {Tool} User profile tool definition - */ - private getUserProfileTool(): Tool { - return { - name: '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'] - } - }; - } - - /** - * Tool definition for listing workspace users - * - * @private - * @returns {Tool} Users list tool definition - */ - private getUsersTool(): Tool { - return { - name: '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 } - } - } - }; - } /** * Handles add reaction tool requests @@ -400,75 +265,14 @@ export class SlackMcpServer { /** * Handles tool listing requests from MCP clients * - * @private - * @returns {Promise} Response containing available tools - */ - private async handleTools(): Promise { - return { tools: this.getTools() }; - } - - /** - * Tool definition for listing Slack channels - * - * @private - * @returns {Tool} List channels tool definition - */ - private listChannelsTool(): Tool { - return { - name: 'list_channels', - description: 'List public or pre-defined 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' } - } - } - }; - } - - /** - * Tool definition for posting messages to Slack channels + * Returns complete list of available MCP tools with their schemas + * and descriptions for client capability discovery. * * @private - * @returns {Tool} Post message tool definition + * @returns {Promise<{tools: Tool[]}>} Complete tool registry for MCP protocol */ - private postMessageTool(): Tool { - return { - name: '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'] - } - }; - } - - /** - * Tool definition for replying to message threads in Slack - * - * @private - * @returns {Tool} Reply to thread tool definition - */ - private replyToThreadTool(): Tool { - return { - name: '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 (format: 1234567890.123456)' }, - text: { type: 'string', description: 'The reply text to post' }, - broadcast: { type: 'boolean', description: 'Whether to also send the reply to the main channel (default: false)', default: false } - }, - required: ['channel_id', 'thread_ts', 'text'] - } - }; + private async handleTools(): Promise<{ tools: Tool[] }> { + return { tools: this.tool.getTools() }; } /** @@ -499,13 +303,15 @@ export class SlackMcpServer { } /** - * Connects the MCP server to the specified transport with proper error handling + * Connects the MCP server to stdio transport with error handling + * + * Establishes MCP communication channel using standard input/output streams, + * configures error handling, and starts message processing. * - * @param {StdioServerTransport} transport - Transport for MCP communication - * @returns {Promise} Promise that resolves when connection is established + * @param {StdioServerTransport} transport - Stdio transport for MCP communication + * @returns {Promise} Promise that resolves when connection is established and listening */ async connect(transport: StdioServerTransport): Promise { - this.transport = transport; transport.onerror = () => { }; await this.server.connect(transport); } diff --git a/src/server/tool.ts b/src/server/tool.ts new file mode 100644 index 0000000..2c60091 --- /dev/null +++ b/src/server/tool.ts @@ -0,0 +1,263 @@ +/** + * MCP Tool Definitions for Slack Integration + * + * @module server/tool + * @author AXIVO + * @license BSD-3-Clause + */ + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +/** + * MCP Tool Definitions for Slack API Integration + * + * Provides comprehensive MCP tool definitions that bridge Slack API capabilities + * with Model Context Protocol, enabling Claude agents to interact with Slack workspaces. + * + * @class McpTool + */ +export class McpTool { + private users: number; + + /** + * Creates a new McpTool instance with pagination configuration + * + * Initializes tool definitions with consistent pagination limits + * for all tools that support result pagination. + * + * @param {number} users - Default pagination limit for user-related results + */ + constructor(users: number) { + this.users = users; + } + + /** + * Creates MCP tool for adding reaction emojis to messages + * + * Enables adding emoji reactions to Slack messages for engagement + * and quick responses without full message replies. + * + * @returns {Tool} MCP tool definition for adding reactions + */ + addReaction(): Tool { + return { + name: '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'] + } + }; + } + + /** + * Creates MCP tool for editing existing messages + * + * Allows modification of previously posted messages in Slack channels, + * supporting content updates and corrections. + * + * @returns {Tool} MCP tool definition for message editing + */ + editMessage(): Tool { + return { + name: 'edit_message', + description: 'Edit an existing message in a Slack channel', + 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 edit' }, + text: { type: 'string', description: 'The message text to edit' } + }, + required: ['channel_id', 'timestamp', 'text'] + } + }; + } + + /** + * Creates MCP tool for retrieving channel message history + * + * Provides access to recent messages in Slack channels with pagination + * support for browsing conversation history and context. + * + * @returns {Tool} MCP tool definition for channel history retrieval + */ + getChannelHistory(): Tool { + return { + name: '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'] + } + }; + } + + /** + * Creates MCP tool for getting thread replies + * + * Retrieves all replies within a specific message thread for + * comprehensive conversation context and thread management. + * + * @returns {Tool} MCP tool definition for thread reply retrieval + */ + getThreadReplies(): Tool { + return { + name: '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 (format: 1234567890.123456)' } + }, + required: ['channel_id', 'thread_ts'] + } + }; + } + + /** + * Aggregates all available MCP tools into comprehensive registry + * + * Returns complete collection of Slack-to-MCP tool definitions including + * messaging, user management, and workspace operations. + * + * @returns {Tool[]} Complete array of all available MCP tool definitions + */ + getTools(): Tool[] { + return [ + this.addReaction(), + this.editMessage(), + this.getChannelHistory(), + this.getThreadReplies(), + this.getUserProfile(), + this.getUsers(), + this.listChannels(), + this.postMessage(), + this.replyToThread() + ]; + } + + /** + * Creates MCP tool for getting detailed user profile information + * + * Provides comprehensive user profile data including display names, + * status, timezone, and custom fields for user identification. + * + * @returns {Tool} MCP tool definition for user profile retrieval + */ + getUserProfile(): Tool { + return { + name: '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'] + } + }; + } + + /** + * Creates MCP tool for listing workspace users with pagination + * + * Enumerates all users in the Slack workspace with pagination support + * for large teams and comprehensive user directory access. + * + * @returns {Tool} MCP tool definition for user listing + */ + getUsers(): Tool { + return { + name: '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: this.users } + } + } + }; + } + + /** + * Creates MCP tool for listing Slack channels with pagination + * + * Provides access to public channels and predefined channel lists + * with pagination support for workspace navigation. + * + * @returns {Tool} MCP tool definition for channel listing + */ + listChannels(): Tool { + return { + name: 'list_channels', + description: 'List public or pre-defined 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: this.users }, + cursor: { type: 'string', description: 'Pagination cursor for next page of results' } + } + } + }; + } + + /** + * Creates MCP tool for posting messages to Slack channels + * + * Enables sending new messages to Slack channels with markdown support + * and proper formatting for effective communication. + * + * @returns {Tool} MCP tool definition for message posting + */ + postMessage(): Tool { + return { + name: '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'] + } + }; + } + + /** + * Creates MCP tool for replying to message threads + * + * Allows posting replies within existing message threads with optional + * broadcasting to maintain organized conversations. + * + * @returns {Tool} MCP tool definition for thread replies + */ + replyToThread(): Tool { + return { + name: '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 (format: 1234567890.123456)' }, + text: { type: 'string', description: 'The reply text to post' }, + broadcast: { type: 'boolean', description: 'Whether to also send the reply to the main channel (default: false)', default: false } + }, + required: ['channel_id', 'thread_ts', 'text'] + } + }; + } +}