/**
* Slack MCP Server - Core Implementation
* Provides tools for interacting with Slack via the Web API
*/
import { WebClient } from '@slack/web-api';
export class SlackMCP {
constructor(token) {
if (!token) {
throw new Error('SLACK_TOKEN environment variable is required');
}
this.client = new WebClient(token);
}
// === Channel Tools ===
async listChannels(options = {}) {
try {
const result = await this.client.conversations.list({
types: options.types || 'public_channel,private_channel',
limit: options.limit || 100,
exclude_archived: options.excludeArchived !== false
});
return {
success: true,
channels: result.channels.map(ch => ({
id: ch.id,
name: ch.name,
is_private: ch.is_private,
is_member: ch.is_member,
topic: ch.topic?.value,
purpose: ch.purpose?.value,
num_members: ch.num_members
}))
};
} catch (error) {
return { success: false, error: error.message };
}
}
async getChannelHistory(channelId, options = {}) {
try {
const result = await this.client.conversations.history({
channel: channelId,
limit: options.limit || 20
});
return {
success: true,
messages: result.messages.map(msg => ({
ts: msg.ts,
user: msg.user,
text: msg.text,
type: msg.type,
thread_ts: msg.thread_ts,
reply_count: msg.reply_count
}))
};
} catch (error) {
return { success: false, error: error.message };
}
}
// === Message Tools ===
async postMessage(channel, text, options = {}) {
try {
const result = await this.client.chat.postMessage({
channel,
text,
thread_ts: options.threadTs,
unfurl_links: options.unfurlLinks !== false
});
return {
success: true,
ts: result.ts,
channel: result.channel,
message: result.message
};
} catch (error) {
return { success: false, error: error.message };
}
}
async updateMessage(channel, ts, text) {
try {
const result = await this.client.chat.update({
channel,
ts,
text
});
return { success: true, ts: result.ts, channel: result.channel };
} catch (error) {
return { success: false, error: error.message };
}
}
async deleteMessage(channel, ts) {
try {
await this.client.chat.delete({ channel, ts });
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
}
// === Reaction Tools ===
async addReaction(channel, ts, emoji) {
try {
await this.client.reactions.add({
channel,
timestamp: ts,
name: emoji.replace(/:/g, '')
});
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
}
async removeReaction(channel, ts, emoji) {
try {
await this.client.reactions.remove({
channel,
timestamp: ts,
name: emoji.replace(/:/g, '')
});
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
}
// === User Tools ===
async getUserInfo(userId) {
try {
const result = await this.client.users.info({ user: userId });
return {
success: true,
user: {
id: result.user.id,
name: result.user.name,
real_name: result.user.real_name,
display_name: result.user.profile?.display_name,
email: result.user.profile?.email,
is_admin: result.user.is_admin,
is_bot: result.user.is_bot
}
};
} catch (error) {
return { success: false, error: error.message };
}
}
async listUsers(options = {}) {
try {
const result = await this.client.users.list({
limit: options.limit || 100
});
return {
success: true,
users: result.members
.filter(u => !u.deleted && !u.is_bot)
.map(u => ({
id: u.id,
name: u.name,
real_name: u.real_name,
display_name: u.profile?.display_name,
email: u.profile?.email
}))
};
} catch (error) {
return { success: false, error: error.message };
}
}
// === Search Tools ===
async searchMessages(query, options = {}) {
try {
const result = await this.client.search.messages({
query,
count: options.limit || 20,
sort: options.sort || 'timestamp',
sort_dir: options.sortDir || 'desc'
});
return {
success: true,
total: result.messages.total,
matches: result.messages.matches.map(m => ({
ts: m.ts,
channel: m.channel?.id,
channel_name: m.channel?.name,
user: m.user,
username: m.username,
text: m.text,
permalink: m.permalink
}))
};
} catch (error) {
return { success: false, error: error.message };
}
}
// === File Tools ===
async uploadFile(channels, content, options = {}) {
try {
const result = await this.client.files.uploadV2({
channels,
content,
filename: options.filename || 'file.txt',
title: options.title,
initial_comment: options.comment
});
return {
success: true,
file: result.file
};
} catch (error) {
return { success: false, error: error.message };
}
}
}
// Tool definitions for MCP
export const TOOL_DEFINITIONS = [
{
name: 'slack_list_channels',
description: 'List Slack channels the bot has access to',
inputSchema: {
type: 'object',
properties: {
types: { type: 'string', description: 'Channel types: public_channel,private_channel' },
limit: { type: 'number', description: 'Max channels to return' }
}
}
},
{
name: 'slack_get_channel_history',
description: 'Get recent messages from a Slack channel',
inputSchema: {
type: 'object',
properties: {
channel_id: { type: 'string', description: 'Channel ID (e.g., C1234567890)' },
limit: { type: 'number', description: 'Number of messages to fetch' }
},
required: ['channel_id']
}
},
{
name: 'slack_post_message',
description: 'Post a message to a Slack channel',
inputSchema: {
type: 'object',
properties: {
channel: { type: 'string', description: 'Channel ID or name' },
text: { type: 'string', description: 'Message text' },
thread_ts: { type: 'string', description: 'Thread timestamp to reply to' }
},
required: ['channel', 'text']
}
},
{
name: 'slack_update_message',
description: 'Update an existing Slack message',
inputSchema: {
type: 'object',
properties: {
channel: { type: 'string', description: 'Channel ID' },
ts: { type: 'string', description: 'Message timestamp' },
text: { type: 'string', description: 'New message text' }
},
required: ['channel', 'ts', 'text']
}
},
{
name: 'slack_delete_message',
description: 'Delete a Slack message',
inputSchema: {
type: 'object',
properties: {
channel: { type: 'string', description: 'Channel ID' },
ts: { type: 'string', description: 'Message timestamp' }
},
required: ['channel', 'ts']
}
},
{
name: 'slack_add_reaction',
description: 'Add an emoji reaction to a message',
inputSchema: {
type: 'object',
properties: {
channel: { type: 'string', description: 'Channel ID' },
ts: { type: 'string', description: 'Message timestamp' },
emoji: { type: 'string', description: 'Emoji name (without colons)' }
},
required: ['channel', 'ts', 'emoji']
}
},
{
name: 'slack_get_user_info',
description: 'Get information about a Slack user',
inputSchema: {
type: 'object',
properties: {
user_id: { type: 'string', description: 'User ID (e.g., U1234567890)' }
},
required: ['user_id']
}
},
{
name: 'slack_list_users',
description: 'List users in the Slack workspace',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Max users to return' }
}
}
},
{
name: 'slack_search_messages',
description: 'Search for messages in Slack',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
limit: { type: 'number', description: 'Max results to return' }
},
required: ['query']
}
}
];
export default SlackMCP;