Skip to main content
Glama
index.ts41.8 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { WebClient } from '@slack/web-api'; import { z } from 'zod'; import * as dotenv from 'dotenv'; import * as cron from 'node-cron'; import axios from 'axios'; // Carregar variáveis de ambiente dotenv.config(); // Validação do schema para argumentos const SlackMessageSchema = z.object({ channel: z.string().describe('ID ou nome do canal Slack'), text: z.string().describe('Texto da mensagem'), thread_ts: z.string().optional().describe('Timestamp do thread (opcional)'), }); const SlackChannelSchema = z.object({ types: z.string().optional().describe('Tipos de canais: public_channel,private_channel,mpim,im'), limit: z.number().optional().describe('Número máximo de canais (padrão: 100)'), }); const SlackUserSchema = z.object({ limit: z.number().optional().describe('Número máximo de usuários (padrão: 100)'), }); const SlackFileUploadSchema = z.object({ channels: z.string().describe('Canal para upload (ID ou nome)'), file_path: z.string().describe('Caminho do arquivo local'), title: z.string().optional().describe('Título do arquivo'), initial_comment: z.string().optional().describe('Comentário inicial'), }); const SlackReactionSchema = z.object({ channel: z.string().describe('ID do canal'), timestamp: z.string().describe('Timestamp da mensagem'), name: z.string().describe('Nome do emoji (sem :)'), }); const SlackScheduleMessageSchema = z.object({ channel: z.string().describe('Canal de destino'), text: z.string().describe('Texto da mensagem'), post_at: z.number().describe('Timestamp Unix para envio'), }); const SlackThreadReplySchema = z.object({ channel: z.string().describe('ID do canal'), thread_ts: z.string().describe('Timestamp do thread principal'), text: z.string().describe('Texto da resposta'), }); const SlackUserStatusSchema = z.object({ status_text: z.string().describe('Texto do status'), status_emoji: z.string().describe('Emoji do status (ex: :house:)'), status_expiration: z.number().optional().describe('Timestamp de expiração'), }); const SlackPollSchema = z.object({ channel: z.string().describe('Canal para a enquete'), question: z.string().describe('Pergunta da enquete'), options: z.array(z.string()).describe('Opções da enquete'), anonymous: z.boolean().optional().describe('Enquete anônima'), }); const SlackWebhookSchema = z.object({ url: z.string().describe('URL do webhook'), text: z.string().describe('Texto da mensagem'), username: z.string().optional().describe('Nome do bot'), icon_emoji: z.string().optional().describe('Emoji do ícone'), }); const SlackAnalyticsSchema = z.object({ channel: z.string().describe('Canal para análise'), days: z.number().optional().describe('Dias para análise (padrão: 7)'), }); const SlackMentionSchema = z.object({ channel: z.string().describe('Canal da mensagem'), text: z.string().describe('Texto da mensagem'), users: z.array(z.string()).describe('IDs dos usuários para mencionar'), }); class SlackMCPServer { private server: Server; private slackClient: WebClient; constructor() { this.server = new Server( { name: 'slack-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Configurar cliente Slack const slackToken = process.env.SLACK_BOT_TOKEN; if (!slackToken) { throw new Error('SLACK_BOT_TOKEN não encontrado nas variáveis de ambiente'); } this.slackClient = new WebClient(slackToken); this.setupToolHandlers(); } private setupToolHandlers() { // Listar ferramentas disponíveis this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'send_slack_message', description: 'Enviar uma mensagem para um canal do Slack', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'ID ou nome do canal Slack (ex: #geral, C1234567890)', }, text: { type: 'string', description: 'Texto da mensagem a ser enviada', }, thread_ts: { type: 'string', description: 'Timestamp do thread para responder (opcional)', }, }, required: ['channel', 'text'], }, }, { name: 'list_slack_channels', description: 'Listar canais do Slack', inputSchema: { type: 'object', properties: { types: { type: 'string', description: 'Tipos de canais: public_channel,private_channel,mpim,im', default: 'public_channel,private_channel', }, limit: { type: 'number', description: 'Número máximo de canais', default: 100, }, }, required: [], }, }, { name: 'list_slack_users', description: 'Listar usuários do workspace Slack', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Número máximo de usuários', default: 100, }, }, required: [], }, }, { name: 'get_slack_channel_history', description: 'Obter histórico de mensagens de um canal', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'ID do canal Slack', }, limit: { type: 'number', description: 'Número máximo de mensagens', default: 10, }, }, required: ['channel'], }, }, { name: 'upload_file_to_slack', description: 'Fazer upload de arquivo para canal Slack', inputSchema: { type: 'object', properties: { channels: { type: 'string', description: 'Canal para upload (ID ou nome)', }, file_path: { type: 'string', description: 'Caminho do arquivo local', }, title: { type: 'string', description: 'Título do arquivo', }, initial_comment: { type: 'string', description: 'Comentário inicial', }, }, required: ['channels', 'file_path'], }, }, { name: 'add_slack_reaction', description: 'Adicionar reação emoji a uma mensagem', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'ID do canal', }, timestamp: { type: 'string', description: 'Timestamp da mensagem', }, name: { type: 'string', description: 'Nome do emoji (sem os :)', }, }, required: ['channel', 'timestamp', 'name'], }, }, { name: 'schedule_slack_message', description: 'Agendar mensagem para envio futuro', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'Canal de destino', }, text: { type: 'string', description: 'Texto da mensagem', }, post_at: { type: 'number', description: 'Timestamp Unix para envio', }, }, required: ['channel', 'text', 'post_at'], }, }, { name: 'reply_in_thread', description: 'Responder em thread específico', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'ID do canal', }, thread_ts: { type: 'string', description: 'Timestamp do thread principal', }, text: { type: 'string', description: 'Texto da resposta', }, }, required: ['channel', 'thread_ts', 'text'], }, }, { name: 'set_user_status', description: 'Definir status do usuário (presença)', inputSchema: { type: 'object', properties: { status_text: { type: 'string', description: 'Texto do status', }, status_emoji: { type: 'string', description: 'Emoji do status (ex: :house:)', }, status_expiration: { type: 'number', description: 'Timestamp de expiração', }, }, required: ['status_text', 'status_emoji'], }, }, { name: 'search_slack_messages', description: 'Buscar mensagens no workspace', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Termo de busca', }, count: { type: 'number', description: 'Número de resultados', default: 20, }, sort: { type: 'string', description: 'Ordenação: timestamp ou score', default: 'timestamp', }, }, required: ['query'], }, }, { name: 'get_workspace_info', description: 'Obter informações do workspace Slack', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'create_slack_reminder', description: 'Criar lembrete no Slack', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'Texto do lembrete', }, time: { type: 'string', description: 'Quando lembrar (ex: "in 5 minutes", "tomorrow at 9am")', }, user: { type: 'string', description: 'ID do usuário (opcional, padrão: próprio usuário)', }, }, required: ['text', 'time'], }, }, { name: 'create_slack_poll', description: 'Criar enquete interativa no Slack', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'Canal para a enquete', }, question: { type: 'string', description: 'Pergunta da enquete', }, options: { type: 'array', items: { type: 'string' }, description: 'Opções da enquete (máximo 10)', }, anonymous: { type: 'boolean', description: 'Enquete anônima', default: false, }, }, required: ['channel', 'question', 'options'], }, }, { name: 'send_webhook_message', description: 'Enviar mensagem via webhook (para integrações)', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL do webhook Slack', }, text: { type: 'string', description: 'Texto da mensagem', }, username: { type: 'string', description: 'Nome do bot', }, icon_emoji: { type: 'string', description: 'Emoji do ícone (ex: :robot_face:)', }, }, required: ['url', 'text'], }, }, { name: 'get_channel_analytics', description: 'Obter analytics de um canal (atividade, engajamento)', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'ID do canal', }, days: { type: 'number', description: 'Dias para análise', default: 7, }, }, required: ['channel'], }, }, { name: 'mention_users', description: 'Enviar mensagem mencionando usuários específicos', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'Canal da mensagem', }, text: { type: 'string', description: 'Texto da mensagem', }, users: { type: 'array', items: { type: 'string' }, description: 'IDs dos usuários para mencionar', }, }, required: ['channel', 'text', 'users'], }, }, { name: 'archive_old_channels', description: 'Arquivar canais inativos automaticamente', inputSchema: { type: 'object', properties: { days_inactive: { type: 'number', description: 'Dias sem atividade para arquivar', default: 30, }, dry_run: { type: 'boolean', description: 'Apenas simular (não arquivar)', default: true, }, }, required: [], }, }, { name: 'bulk_invite_users', description: 'Convidar múltiplos usuários para canal', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'ID do canal', }, users: { type: 'array', items: { type: 'string' }, description: 'IDs dos usuários', }, }, required: ['channel', 'users'], }, }, { name: 'get_private_channels_with_user', description: 'Listar grupos privados que o usuário participa', inputSchema: { type: 'object', properties: { user_id: { type: 'string', description: 'ID do usuário (opcional, padrão: bot atual)', }, include_history: { type: 'boolean', description: 'Incluir última mensagem de cada grupo', default: false, }, }, required: [], }, }, { name: 'search_in_private_groups', description: 'Buscar mensagens especificamente em grupos privados', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Termo de busca', }, group_ids: { type: 'array', items: { type: 'string' }, description: 'IDs dos grupos específicos (opcional)', }, count: { type: 'number', description: 'Número de resultados', default: 20, }, }, required: ['query'], }, }, ], }; }); // Executar ferramentas this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'send_slack_message': return await this.sendSlackMessage(args); case 'list_slack_channels': return await this.listSlackChannels(args); case 'list_slack_users': return await this.listSlackUsers(args); case 'get_slack_channel_history': return await this.getChannelHistory(args); case 'upload_file_to_slack': return await this.uploadFile(args); case 'add_slack_reaction': return await this.addReaction(args); case 'schedule_slack_message': return await this.scheduleMessage(args); case 'reply_in_thread': return await this.replyInThread(args); case 'set_user_status': return await this.setUserStatus(args); case 'search_slack_messages': return await this.searchMessages(args); case 'get_workspace_info': return await this.getWorkspaceInfo(); case 'create_slack_reminder': return await this.createReminder(args); case 'create_slack_poll': return await this.createPoll(args); case 'send_webhook_message': return await this.sendWebhookMessage(args); case 'get_channel_analytics': return await this.getChannelAnalytics(args); case 'mention_users': return await this.mentionUsers(args); case 'archive_old_channels': return await this.archiveOldChannels(args); case 'bulk_invite_users': return await this.bulkInviteUsers(args); case 'get_private_channels_with_user': return await this.getPrivateChannelsWithUser(args); case 'search_in_private_groups': return await this.searchInPrivateGroups(args); default: throw new McpError( ErrorCode.MethodNotFound, `Ferramenta desconhecida: ${name}` ); } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Erro ao executar ${name}: ${error instanceof Error ? error.message : String(error)}` ); } }); } private async sendSlackMessage(args: any) { const { channel, text, thread_ts } = SlackMessageSchema.parse(args); const result = await this.slackClient.chat.postMessage({ channel, text, thread_ts, }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Mensagem enviada com sucesso', channel: result.channel, ts: result.ts, message_text: text, }, null, 2 ), }, ], }; } private async listSlackChannels(args: any) { const { types = 'public_channel,private_channel', limit = 100 } = SlackChannelSchema.parse(args); const result = await this.slackClient.conversations.list({ types, limit, }); const channels = result.channels?.map(channel => ({ id: channel.id, name: channel.name, is_private: channel.is_private, is_member: channel.is_member, topic: channel.topic?.value, purpose: channel.purpose?.value, num_members: channel.num_members, })) || []; return { content: [ { type: 'text', text: JSON.stringify( { success: true, channels_count: channels.length, channels, }, null, 2 ), }, ], }; } private async listSlackUsers(args: any) { const { limit = 100 } = SlackUserSchema.parse(args); const result = await this.slackClient.users.list({ limit, }); const users = result.members?.map(user => ({ id: user.id, name: user.name, real_name: user.real_name, display_name: user.profile?.display_name, email: user.profile?.email, is_bot: user.is_bot, is_admin: user.is_admin, is_owner: user.is_owner, })) || []; return { content: [ { type: 'text', text: JSON.stringify( { success: true, users_count: users.length, users, }, null, 2 ), }, ], }; } private async getChannelHistory(args: any) { const { channel, limit = 10 } = args; const result = await this.slackClient.conversations.history({ channel, limit, }); const messages = result.messages?.map(message => ({ ts: message.ts, user: message.user, text: message.text, thread_ts: message.thread_ts, reply_count: message.reply_count, })) || []; return { content: [ { type: 'text', text: JSON.stringify( { success: true, channel, messages_count: messages.length, messages, }, null, 2 ), }, ], }; } private async uploadFile(args: any) { const { channels, file_path, title, initial_comment } = SlackFileUploadSchema.parse(args); try { const fs = await import('fs'); const result = await this.slackClient.files.uploadV2({ channels, file: fs.createReadStream(file_path), title, initial_comment, }) as any; return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Arquivo enviado com sucesso', file: { id: result.file?.id, name: result.file?.name, url: result.file?.url_private, }, }, null, 2 ), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Erro ao fazer upload: ${error instanceof Error ? error.message : String(error)}` ); } } private async addReaction(args: any) { const { channel, timestamp, name } = SlackReactionSchema.parse(args); const result = await this.slackClient.reactions.add({ channel, timestamp, name, }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: `Reação :${name}: adicionada com sucesso`, reaction: name, timestamp, }, null, 2 ), }, ], }; } private async scheduleMessage(args: any) { const { channel, text, post_at } = SlackScheduleMessageSchema.parse(args); const result = await this.slackClient.chat.scheduleMessage({ channel, text, post_at, }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Mensagem agendada com sucesso', scheduled_message_id: result.scheduled_message_id, post_at: new Date(post_at * 1000).toISOString(), }, null, 2 ), }, ], }; } private async replyInThread(args: any) { const { channel, thread_ts, text } = SlackThreadReplySchema.parse(args); const result = await this.slackClient.chat.postMessage({ channel, text, thread_ts, }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Resposta enviada no thread', thread_ts, message_ts: result.ts, }, null, 2 ), }, ], }; } private async setUserStatus(args: any) { const { status_text, status_emoji, status_expiration } = SlackUserStatusSchema.parse(args); const result = await this.slackClient.users.profile.set({ profile: { status_text, status_emoji, status_expiration: status_expiration || 0, }, }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Status do usuário atualizado', status: { text: status_text, emoji: status_emoji, expiration: status_expiration ? new Date(status_expiration * 1000).toISOString() : null, }, }, null, 2 ), }, ], }; } private async searchMessages(args: any) { const { query, count = 20, sort = 'timestamp' } = args; const result = await this.slackClient.search.messages({ query, count, sort, }); const matches = result.messages?.matches?.map(match => ({ text: match.text, user: match.username, channel: match.channel?.name, ts: match.ts, permalink: match.permalink, })) || []; return { content: [ { type: 'text', text: JSON.stringify( { success: true, query, total_matches: result.messages?.total, matches_count: matches.length, matches, }, null, 2 ), }, ], }; } private async getWorkspaceInfo() { const teamInfo = await this.slackClient.team.info(); const authTest = await this.slackClient.auth.test(); return { content: [ { type: 'text', text: JSON.stringify( { success: true, workspace: { id: teamInfo.team?.id, name: teamInfo.team?.name, domain: teamInfo.team?.domain, email_domain: teamInfo.team?.email_domain, icon: teamInfo.team?.icon, }, bot: { user_id: authTest.user_id, bot_id: authTest.bot_id, team_id: authTest.team_id, }, }, null, 2 ), }, ], }; } private async createReminder(args: any) { const { text, time, user } = args; const result = await this.slackClient.reminders.add({ text, time, user, }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Lembrete criado com sucesso', reminder: { id: result.reminder?.id, text: result.reminder?.text, time: result.reminder?.time, user: result.reminder?.user, }, }, null, 2 ), }, ], }; } private async createPoll(args: any) { const { channel, question, options, anonymous = false } = SlackPollSchema.parse(args); // Criar enquete usando Block Kit const blocks: any[] = [ { type: 'section', text: { type: 'mrkdwn', text: `*${question}*${anonymous ? ' (Enquete Anônima)' : ''}`, }, }, { type: 'divider', }, ]; // Adicionar botões para cada opção const actions = options.slice(0, 10).map((option, index) => ({ type: 'button', text: { type: 'plain_text', text: option, }, action_id: `poll_option_${index}`, value: `${index}`, })); blocks.push({ type: 'actions', elements: actions, }); const result = await this.slackClient.chat.postMessage({ channel, text: question, blocks, }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Enquete criada com sucesso', poll: { question, options, message_ts: result.ts, anonymous, }, }, null, 2 ), }, ], }; } private async sendWebhookMessage(args: any) { const { url, text, username, icon_emoji } = SlackWebhookSchema.parse(args); try { const payload = { text, username, icon_emoji, }; const response = await axios.post(url, payload); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Mensagem webhook enviada', status: response.status, }, null, 2 ), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Erro ao enviar webhook: ${error instanceof Error ? error.message : String(error)}` ); } } private async getChannelAnalytics(args: any) { const { channel, days = 7 } = SlackAnalyticsSchema.parse(args); const oldest = Math.floor((Date.now() - days * 24 * 60 * 60 * 1000) / 1000); const result = await this.slackClient.conversations.history({ channel, oldest: oldest.toString(), limit: 1000, }); const messages = result.messages || []; const userStats: Record<string, number> = {}; const hourStats: Record<string, number> = {}; let totalReactions = 0; messages.forEach(message => { if (message.user) { userStats[message.user] = (userStats[message.user] || 0) + 1; } if (message.ts) { const hour = new Date(parseFloat(message.ts) * 1000).getHours(); hourStats[hour] = (hourStats[hour] || 0) + 1; } if (message.reactions) { totalReactions += message.reactions.reduce((sum, reaction) => sum + (reaction.count || 0), 0); } }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, analytics: { period_days: days, total_messages: messages.length, total_reactions: totalReactions, most_active_users: Object.entries(userStats) .sort(([,a], [,b]) => b - a) .slice(0, 5), activity_by_hour: hourStats, engagement_rate: messages.length > 0 ? totalReactions / messages.length : 0, }, }, null, 2 ), }, ], }; } private async mentionUsers(args: any) { const { channel, text, users } = SlackMentionSchema.parse(args); const mentions = users.map(user => `<@${user}>`).join(' '); const messageText = `${mentions} ${text}`; const result = await this.slackClient.chat.postMessage({ channel, text: messageText, }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Mensagem com menções enviada', mentioned_users: users.length, message_ts: result.ts, }, null, 2 ), }, ], }; } private async archiveOldChannels(args: any) { const { days_inactive = 30, dry_run = true } = args; const cutoffDate = Math.floor((Date.now() - days_inactive * 24 * 60 * 60 * 1000) / 1000); const channelsResult = await this.slackClient.conversations.list({ types: 'public_channel,private_channel', limit: 100, }); const inactiveChannels = []; for (const channel of channelsResult.channels || []) { if (!channel.id) continue; const history = await this.slackClient.conversations.history({ channel: channel.id, limit: 1, }); const lastMessage = history.messages?.[0]; if (!lastMessage?.ts || parseFloat(lastMessage.ts) < cutoffDate) { inactiveChannels.push({ id: channel.id, name: channel.name, last_activity: lastMessage?.ts ? new Date(parseFloat(lastMessage.ts) * 1000).toISOString() : 'Never', }); if (!dry_run && channel.id) { await this.slackClient.conversations.archive({ channel: channel.id, }); } } } return { content: [ { type: 'text', text: JSON.stringify( { success: true, dry_run, inactive_channels: inactiveChannels.length, channels: inactiveChannels, message: dry_run ? 'Simulação concluída' : `${inactiveChannels.length} canais arquivados`, }, null, 2 ), }, ], }; } private async bulkInviteUsers(args: any) { const { channel, users } = args; const results = []; for (const user of users) { try { await this.slackClient.conversations.invite({ channel, users: user, }); results.push({ user, success: true }); } catch (error) { results.push({ user, success: false, error: error instanceof Error ? error.message : String(error) }); } } const successful = results.filter(r => r.success).length; return { content: [ { type: 'text', text: JSON.stringify( { success: true, invited_users: successful, total_users: users.length, results, }, null, 2 ), }, ], }; } private async getPrivateChannelsWithUser(args: any) { const { user_id, include_history = false } = args; // Listar todos os grupos privados que o bot tem acesso const result = await this.slackClient.conversations.list({ types: 'private_channel,mpim', limit: 200, }); const privateChannels = []; for (const channel of result.channels || []) { if (!channel.id) continue; try { // Verificar se o usuário está no canal (se especificado) if (user_id) { const members = await this.slackClient.conversations.members({ channel: channel.id, }); if (!members.members?.includes(user_id)) { continue; // Usuário não está neste canal } } const channelInfo: any = { id: channel.id, name: channel.name, is_group: channel.is_group, is_mpim: channel.is_mpim, num_members: channel.num_members, topic: channel.topic?.value, purpose: channel.purpose?.value, }; // Incluir última mensagem se solicitado if (include_history) { try { const history = await this.slackClient.conversations.history({ channel: channel.id, limit: 1, }); const lastMessage = history.messages?.[0]; if (lastMessage) { channelInfo.last_message = { text: lastMessage.text, user: lastMessage.user, ts: lastMessage.ts, date: new Date(parseFloat(lastMessage.ts || '0') * 1000).toISOString(), }; } } catch (historyError) { channelInfo.last_message = null; } } privateChannels.push(channelInfo); } catch (error) { console.error(`Erro ao processar canal ${channel.id}:`, error); } } return { content: [ { type: 'text', text: JSON.stringify( { success: true, user_id: user_id || 'bot_user', private_channels_count: privateChannels.length, private_channels: privateChannels, }, null, 2 ), }, ], }; } private async searchInPrivateGroups(args: any) { const { query, group_ids, count = 20 } = args; try { // Se grupos específicos foram fornecidos, buscar apenas neles if (group_ids && group_ids.length > 0) { const results = []; for (const groupId of group_ids) { try { const searchQuery = `in:${groupId} ${query}`; const result = await this.slackClient.search.messages({ query: searchQuery, count: Math.floor(count / group_ids.length), }); const matches = result.messages?.matches?.map(match => ({ text: match.text, user: match.username, channel: match.channel?.name, channel_id: groupId, ts: match.ts, permalink: match.permalink, })) || []; results.push(...matches); } catch (error) { console.error(`Erro ao buscar no grupo ${groupId}:`, error); } } return { content: [ { type: 'text', text: JSON.stringify( { success: true, query, searched_groups: group_ids, matches_count: results.length, matches: results.slice(0, count), }, null, 2 ), }, ], }; } // Busca geral limitada a canais privados const searchQuery = `${query} in:private`; const result = await this.slackClient.search.messages({ query: searchQuery, count, }); const matches = result.messages?.matches?.map(match => ({ text: match.text, user: match.username, channel: match.channel?.name, ts: match.ts, permalink: match.permalink, is_private: true, })) || []; return { content: [ { type: 'text', text: JSON.stringify( { success: true, query, search_scope: 'private_channels', total_matches: result.messages?.total, matches_count: matches.length, matches, }, null, 2 ), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Erro ao buscar em grupos privados: ${error instanceof Error ? error.message : String(error)}` ); } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Slack MCP Server iniciado'); } } const server = new SlackMCPServer(); server.run().catch(console.error);

Latest Blog Posts

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/will2023a/MCP-SLACK'

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