Skip to main content
Glama

Github Project Manager

index.ts15.7 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { WebClient, Block, KnownBlock } from '@slack/web-api'; import { z } from 'zod'; import { ChannelOperationsTool } from './channel-operations.js'; import { MessageOperationsTool } from './message-operations.js'; import { UserOperationsTool } from './user-operations.js'; import { FileOperationsTool } from './file-operations.js'; import { FileType, FileTypeEnum } from '../types/file-type.js'; /** * Register Slack tools with the MCP server * * @param server - The MCP server instance * @param slackClient - The authenticated Slack WebClient * @param teamId - The Slack team ID */ export function registerTools(server: McpServer, slackClient: WebClient, teamId: string): void { const channelOperations = new ChannelOperationsTool(slackClient, teamId); const messageOperations = new MessageOperationsTool(slackClient, teamId); const userOperations = new UserOperationsTool(slackClient, teamId); const fileOperations = new FileOperationsTool(slackClient, teamId); // Channel Operations // List Channels server.tool( 'slack_list_channels', { 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'), }, async (params) => { return await channelOperations.listChannels(params.limit, params.cursor); }, ); // Get Channel History server.tool( 'slack_get_channel_history', { channel_id: z.string().describe('The channel ID'), limit: z.number().optional().describe('Number of messages to retrieve (default: 10)'), }, async (params) => { return await channelOperations.getChannelHistory(params.channel_id, params.limit); }, ); // Create Channel server.tool( 'slack_create_channel', { name: z .string() .describe('Channel name (lowercase, no spaces/special chars except hyphens and underscores)'), is_private: z.boolean().describe('Whether to create a private channel'), description: z.string().optional().describe('Optional initial description/purpose for the channel'), }, async (params) => { return await channelOperations.createChannel(params.name, params.is_private, params.description); }, ); // Archive Channel server.tool( 'slack_archive_channel', { channel_id: z.string().describe('The channel ID to archive'), }, async (params) => { return await channelOperations.archiveChannel(params.channel_id); }, ); // Unarchive Channel server.tool( 'slack_unarchive_channel', { channel_id: z.string().describe('The channel ID to unarchive'), }, async (params) => { return await channelOperations.unarchiveChannel(params.channel_id); }, ); // Invite to Channel server.tool( 'slack_invite_to_channel', { channel_id: z.string().describe('The channel ID'), user_ids: z.array(z.string()).describe('Array of user IDs to invite'), }, async (params) => { return await channelOperations.inviteToChannel(params.channel_id, params.user_ids); }, ); // Get Channel Info server.tool( 'slack_get_channel_info', { channel_id: z.string().describe('The channel ID'), }, async (params) => { return await channelOperations.getChannelInfo(params.channel_id); }, ); // Message Operations // Post Message server.tool( 'slack_post_message', { channel_id: z.string().describe('The ID of the channel to post to'), text: z.string().describe('The message text to post'), }, async (params) => { return await messageOperations.postMessage(params.channel_id, params.text); }, ); // Post Rich Message with Blocks and/or Attachments server.tool( 'slack_post_rich_message', { channel_id: z.string().describe('The ID of the channel to post to'), text: z.string().describe('The fallback text for the message'), blocks: z .array(z.record(z.any())) .optional() .describe( 'Layout blocks for rich formatting (Section, Header, Divider, Image, Context blocks). Example: [{"type":"header","text":{"type":"plain_text","text":"Title"}},{"type":"section","text":{"type":"mrkdwn","text":"Hello *world*"}}]', ), attachments: z .array(z.record(z.any())) .optional() .describe( 'Attachments for the message with colored sidebars, fields, etc. Example: [{"color":"#36a64f","title":"Title","fields":[{"title":"Priority","value":"High","short":true}]}]', ), }, async (params) => { return await messageOperations.postRichMessage( params.channel_id, params.text, params.blocks as Array<Block | KnownBlock> | undefined, params.attachments, ); }, ); // Update Message server.tool( 'slack_update_message', { channel_id: z.string().describe('The channel containing the message'), timestamp: z.string().describe('Timestamp of the message to update'), text: z.string().describe('New text for the message'), blocks: z .array(z.record(z.any())) .optional() .describe( 'New blocks for the message, replacing any existing ones. Example: [{"type":"header","text":{"type":"plain_text","text":"Updated Title"}},{"type":"section","text":{"type":"mrkdwn","text":"Updated content with *formatting*"}}]', ), attachments: z .array(z.record(z.any())) .optional() .describe( 'New attachments for the message, replacing any existing ones. Example: [{"color":"#36a64f","title":"Updated Status","fields":[{"title":"Status","value":"Completed","short":true}]}]', ), }, async (params) => { return await messageOperations.updateMessage( params.channel_id, params.timestamp, params.text, params.blocks as Array<Block | KnownBlock> | undefined, params.attachments, ); }, ); // Delete Message server.tool( 'slack_delete_message', { channel_id: z.string().describe('The channel containing the message'), timestamp: z.string().describe('Timestamp of the message to delete'), }, async (params) => { return await messageOperations.deleteMessage(params.channel_id, params.timestamp); }, ); // Schedule Message server.tool( 'slack_schedule_message', { channel_id: z.string().describe('The ID of the channel to post to'), text: z.string().describe('The message text to post'), post_at: z.number().describe('Unix timestamp for when message should be sent (seconds since epoch)'), blocks: z .array(z.record(z.any())) .optional() .describe( 'Layout blocks for rich formatting. Example: [{"type":"header","text":{"type":"plain_text","text":"Scheduled Announcement"}},{"type":"section","text":{"type":"mrkdwn","text":"This message was scheduled to appear at a specific time"}}]', ), attachments: z .array(z.record(z.any())) .optional() .describe( 'Attachments for the message. Example: [{"color":"#36a64f","title":"Event Details","fields":[{"title":"Event","value":"Team Meeting","short":true},{"title":"Location","value":"Conference Room","short":true}]}]', ), }, async (params) => { return await messageOperations.scheduleMessage( params.channel_id, params.text, params.post_at, params.blocks as Array<Block | KnownBlock> | undefined, params.attachments, ); }, ); // Reply to Thread server.tool( 'slack_reply_to_thread', { channel_id: z.string().describe('The channel containing the thread'), thread_ts: z.string().describe('Timestamp of the parent message'), text: z.string().describe('The reply text'), }, async (params) => { return await messageOperations.replyToThread(params.channel_id, params.thread_ts, params.text); }, ); // Add Reaction server.tool( 'slack_add_reaction', { channel_id: z.string().describe('The channel containing the message'), timestamp: z.string().describe('Message timestamp to react to'), reaction: z.string().describe('Emoji name without colons'), }, async (params) => { return await messageOperations.addReaction(params.channel_id, params.timestamp, params.reaction); }, ); // Get Thread Replies server.tool( 'slack_get_thread_replies', { channel_id: z.string().describe('The channel containing the thread'), thread_ts: z.string().describe('Timestamp of the parent message'), }, async (params) => { return await messageOperations.getThreadReplies(params.channel_id, params.thread_ts); }, ); // User Operations // Get Users server.tool( 'slack_get_users', { cursor: z.string().optional().describe('Pagination cursor for next page'), limit: z.number().optional().describe('Maximum users to return (default: 100, max: 200)'), }, async (params) => { return await userOperations.getUsers(params.cursor, params.limit); }, ); // Get User Profile server.tool( 'slack_get_user_profile', { user_id: z.string().describe("The user's ID"), }, async (params) => { return await userOperations.getUserProfile(params.user_id); }, ); // File Operations // Upload File server.tool( 'slack_upload_file', { file_path: z.string().describe('Local file path to upload'), file_name: z .string() .optional() .describe('Name to use for the file in Slack (defaults to filename from path if not provided)'), channel_id: z.string().optional().describe('Channel ID to share the file with'), file_type: FileTypeEnum.optional().describe( 'File type identifier (e.g., pdf, jpg, docx). Use "auto" to let Slack detect the file type automatically.', ), title: z.string().optional().describe('Title for the file (defaults to filename if not provided)'), initial_comment: z.string().optional().describe('Initial comment to include with the file when sharing'), }, async (params) => { return await fileOperations.uploadFile( params.file_path, params.file_name, params.channel_id, params.file_type as FileType, params.title, params.initial_comment, ); }, ); // Upload File Content server.tool( 'slack_upload_file_content', { content: z.string().describe('Content to upload as a file (text content)'), file_name: z.string().describe('Name for the file including extension (e.g., "report.txt")'), channel_id: z.string().optional().describe('Channel ID to share the file with'), file_type: FileTypeEnum.optional().describe( 'File type identifier (e.g., "text", "markdown", "html"). Common types include: "text", "pdf", "csv", "markdown".', ), title: z.string().optional().describe('Title for the file (defaults to file_name if not provided)'), initial_comment: z.string().optional().describe('Initial comment to include with the file when sharing'), }, async (params) => { return await fileOperations.uploadFileContent( params.content, params.file_name, params.channel_id, params.file_type as FileType, params.title, params.initial_comment, ); }, ); // Get File Info server.tool( 'slack_get_file_info', { file_id: z.string().describe('The ID of the file to get information about (starts with "F")'), }, async (params) => { return await fileOperations.getFileInfo(params.file_id); }, ); // Share File server.tool( 'slack_share_file', { file_id: z.string().describe('The ID of the file to share (starts with "F")'), channel_id: z.string().describe('The channel ID to share the file in (starts with "C")'), }, async (params) => { return await fileOperations.shareFile(params.file_id, params.channel_id); }, ); // Enable Public URL server.tool( 'slack_enable_public_url', { file_id: z .string() .describe('The ID of the file to make public (starts with "F") - creates a publicly accessible URL'), }, async (params) => { return await fileOperations.enablePublicURL(params.file_id); }, ); // Disable Public URL server.tool( 'slack_disable_public_url', { file_id: z .string() .describe('The ID of the file to make private (starts with "F") - revokes the public URL'), }, async (params) => { return await fileOperations.disablePublicURL(params.file_id); }, ); // List Files server.tool( 'slack_list_files', { channel_id: z .string() .optional() .describe('Channel ID to filter files by (only returns files shared in this channel)'), user_id: z .string() .optional() .describe('User ID to filter files by (only returns files uploaded by this user)'), limit: z.number().optional().describe('Maximum number of files to return (default: 10, max: 100)'), }, async (params) => { return await fileOperations.listFiles(params.channel_id, params.user_id, params.limit); }, ); // Delete File server.tool( 'slack_delete_file', { file_id: z.string().describe('The ID of the file to delete (starts with "F") - this action is permanent'), }, async (params) => { return await fileOperations.deleteFile(params.file_id); }, ); }

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/Monsoft-Solutions/model-context-protocols'

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