index.ts•12.1 kB
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { getSlackConfig } from './config/credentials.js';
import { SlackClientWrapper } from './utils/slack-client.js';
// Import tool handlers
import * as channelTools from './tools/channels.js';
import * as userTools from './tools/users.js';
import * as messageTools from './tools/messages.js';
import * as fileTools from './tools/files.js';
import * as reactionTools from './tools/reactions.js';
import * as workspaceTools from './tools/workspace.js';
// Initialize Slack client
let slackClient: SlackClientWrapper;
try {
const config = getSlackConfig();
slackClient = new SlackClientWrapper(config);
} catch (error) {
console.error('Failed to initialize Slack client:', error);
process.exit(1);
}
// Create MCP server
const server = new Server(
{
name: 'slack-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Tool definitions
const tools = [
// Channel tools
{
name: 'list_channels',
description: 'List channels in the Slack workspace',
inputSchema: {
type: 'object',
properties: {
types: {
type: 'string',
description: 'Comma-separated list of channel types (public_channel, private_channel, mpim, im)',
default: 'public_channel,private_channel',
},
exclude_archived: {
type: 'boolean',
description: 'Exclude archived channels',
default: true,
},
limit: {
type: 'number',
description: 'Maximum number of channels to return',
default: 100,
minimum: 1,
maximum: 1000,
},
},
},
},
{
name: 'get_channel_info',
description: 'Get detailed information about a specific channel',
inputSchema: {
type: 'object',
properties: {
channel: {
type: 'string',
description: 'Channel ID (e.g., C1234567890)',
},
},
required: ['channel'],
},
},
{
name: 'create_channel',
description: 'Create a new channel',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Channel name (lowercase, no spaces)',
},
is_private: {
type: 'boolean',
description: 'Create as private channel',
default: false,
},
},
required: ['name'],
},
},
{
name: 'archive_channel',
description: 'Archive a channel',
inputSchema: {
type: 'object',
properties: {
channel: {
type: 'string',
description: 'Channel ID to archive',
},
},
required: ['channel'],
},
},
// User tools
{
name: 'list_users',
description: 'List users in the Slack workspace',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of users to return',
default: 100,
minimum: 1,
maximum: 1000,
},
},
},
},
{
name: 'get_user_info',
description: 'Get detailed information about a specific user',
inputSchema: {
type: 'object',
properties: {
user: {
type: 'string',
description: 'User ID (e.g., U1234567890)',
},
},
required: ['user'],
},
},
{
name: 'invite_to_channel',
description: 'Invite users to a channel',
inputSchema: {
type: 'object',
properties: {
channel: {
type: 'string',
description: 'Channel ID',
},
users: {
type: 'array',
items: {
type: 'string',
},
description: 'Array of user IDs to invite',
},
},
required: ['channel', 'users'],
},
},
// Message tools
{
name: 'send_message',
description: 'Send a message to a channel',
inputSchema: {
type: 'object',
properties: {
channel: {
type: 'string',
description: 'Channel ID',
},
text: {
type: 'string',
description: 'Message text',
},
thread_ts: {
type: 'string',
description: 'Optional thread timestamp to reply to',
},
},
required: ['channel', 'text'],
},
},
{
name: 'update_message',
description: 'Update an existing 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: 'delete_message',
description: 'Delete a message',
inputSchema: {
type: 'object',
properties: {
channel: {
type: 'string',
description: 'Channel ID',
},
ts: {
type: 'string',
description: 'Message timestamp',
},
},
required: ['channel', 'ts'],
},
},
{
name: 'get_channel_history',
description: 'Get message history from a channel',
inputSchema: {
type: 'object',
properties: {
channel: {
type: 'string',
description: 'Channel ID',
},
limit: {
type: 'number',
description: 'Maximum number of messages to return',
default: 100,
minimum: 1,
maximum: 1000,
},
oldest: {
type: 'string',
description: 'Start of time range (timestamp)',
},
latest: {
type: 'string',
description: 'End of time range (timestamp)',
},
},
required: ['channel'],
},
},
{
name: 'search_messages',
description: 'Search for messages across the workspace',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query',
},
count: {
type: 'number',
description: 'Number of results to return',
default: 20,
minimum: 1,
maximum: 100,
},
sort: {
type: 'string',
enum: ['score', 'timestamp'],
description: 'Sort order',
default: 'score',
},
},
required: ['query'],
},
},
{
name: 'send_formatted_message',
description: 'Send a message with Block Kit formatting',
inputSchema: {
type: 'object',
properties: {
channel: {
type: 'string',
description: 'Channel ID',
},
text: {
type: 'string',
description: 'Fallback text',
},
blocks: {
type: 'array',
description: 'Block Kit blocks array',
},
thread_ts: {
type: 'string',
description: 'Optional thread timestamp to reply to',
},
},
required: ['channel', 'text', 'blocks'],
},
},
// File tools
{
name: 'upload_file',
description: 'Upload a file to Slack',
inputSchema: {
type: 'object',
properties: {
channels: {
type: 'array',
items: {
type: 'string',
},
description: 'Array of channel IDs to share the file to',
},
content: {
type: 'string',
description: 'File content',
},
filename: {
type: 'string',
description: 'Filename',
},
title: {
type: 'string',
description: 'File title',
},
initial_comment: {
type: 'string',
description: 'Initial comment',
},
},
required: ['channels', 'content', 'filename'],
},
},
// Reaction tools
{
name: 'add_reaction',
description: 'Add an emoji reaction to a message',
inputSchema: {
type: 'object',
properties: {
channel: {
type: 'string',
description: 'Channel ID',
},
timestamp: {
type: 'string',
description: 'Message timestamp',
},
name: {
type: 'string',
description: 'Emoji name (without colons)',
},
},
required: ['channel', 'timestamp', 'name'],
},
},
{
name: 'remove_reaction',
description: 'Remove an emoji reaction from a message',
inputSchema: {
type: 'object',
properties: {
channel: {
type: 'string',
description: 'Channel ID',
},
timestamp: {
type: 'string',
description: 'Message timestamp',
},
name: {
type: 'string',
description: 'Emoji name (without colons)',
},
},
required: ['channel', 'timestamp', 'name'],
},
},
// Workspace tools
{
name: 'get_team_info',
description: 'Get information about the Slack workspace',
inputSchema: {
type: 'object',
properties: {},
},
},
];
// Register list_tools handler
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools,
}));
// Tool handler map
const toolHandlers: Record<string, (args: unknown) => Promise<unknown>> = {
// Channel tools
list_channels: (args) => channelTools.listChannels(slackClient, args),
get_channel_info: (args) => channelTools.getChannelInfo(slackClient, args),
create_channel: (args) => channelTools.createChannel(slackClient, args),
archive_channel: (args) => channelTools.archiveChannel(slackClient, args),
// User tools
list_users: (args) => userTools.listUsers(slackClient, args),
get_user_info: (args) => userTools.getUserInfo(slackClient, args),
invite_to_channel: (args) => userTools.inviteToChannel(slackClient, args),
// Message tools
send_message: (args) => messageTools.sendMessage(slackClient, args),
update_message: (args) => messageTools.updateMessage(slackClient, args),
delete_message: (args) => messageTools.deleteMessage(slackClient, args),
get_channel_history: (args) => messageTools.getChannelHistory(slackClient, args),
search_messages: (args) => messageTools.searchMessages(slackClient, args),
send_formatted_message: (args) => messageTools.sendFormattedMessage(slackClient, args),
// File tools
upload_file: (args) => fileTools.uploadFile(slackClient, args),
// Reaction tools
add_reaction: (args) => reactionTools.addReaction(slackClient, args),
remove_reaction: (args) => reactionTools.removeReaction(slackClient, args),
// Workspace tools
get_team_info: (args) => workspaceTools.getTeamInfo(slackClient, args),
};
// Register call_tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const handler = toolHandlers[name];
if (!handler) {
throw new Error(`Unknown tool: ${name}`);
}
const result = await handler(args);
return {
content: [
{
type: 'text',
text: JSON.stringify(result),
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return {
content: [
{
type: 'text',
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Slack MCP Server running on stdio');
}
main().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});