index.ts•4.96 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 {
conversationsHistory,
conversationsReplies,
conversationsAddMessage,
channelsList
} from './tools.js';
import {
ConversationsHistorySchema,
ConversationsRepliesSchema,
ConversationsAddMessageSchema,
ChannelsListSchema
} from './schemas.js';
/**
* Slack MCP Server for AgenticLedger Platform
*
* Enables AI agents to interact with Slack workspaces through standardized tools:
* - conversations_history: Retrieve message history
* - conversations_replies: Get thread messages
* - conversations_search_messages: Search with filters
* - conversations_add_message: Post messages (disabled by default)
* - channels_list: List workspace channels
*/
const server = new Server(
{
name: '@agenticledger/slack-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
/**
* Convert Zod schema to MCP tool input schema
*/
function zodToMCPSchema(zodSchema: any) {
const shape = zodSchema._def.shape();
const properties: any = {};
const required: string[] = [];
for (const [key, value] of Object.entries(shape)) {
const field: any = value;
const description = field._def.description || '';
// Determine type
let type = 'string';
if (field._def.typeName === 'ZodNumber') {
type = 'number';
} else if (field._def.typeName === 'ZodBoolean') {
type = 'boolean';
} else if (field._def.typeName === 'ZodUnion') {
type = 'string'; // Union of string/number for limit parameter
} else if (field._def.typeName === 'ZodEnum') {
type = 'string';
}
properties[key] = {
type,
description
};
// Check if field is optional
if (!field._def.typeName?.includes('Optional') && field._def.typeName !== 'ZodOptional') {
required.push(key);
}
}
return {
type: 'object',
properties,
required
};
}
/**
* List all available tools
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'conversations_history',
description: 'Retrieves message history from a Slack channel, DM, or thread. Supports pagination and filtering.',
inputSchema: zodToMCPSchema(ConversationsHistorySchema)
},
{
name: 'conversations_replies',
description: 'Fetches all messages in a specific thread by channel and thread timestamp. Supports pagination.',
inputSchema: zodToMCPSchema(ConversationsRepliesSchema)
},
{
name: 'conversations_add_message',
description: 'Posts a message to a channel, thread, or DM. Supports markdown and plain text. NOTE: Disabled by default for safety - set SLACK_MCP_ADD_MESSAGE_TOOL environment variable to enable.',
inputSchema: zodToMCPSchema(ConversationsAddMessageSchema)
},
{
name: 'channels_list',
description: 'Lists workspace channels by type (public, private, DMs, group DMs) with optional popularity sorting. Supports pagination.',
inputSchema: zodToMCPSchema(ChannelsListSchema)
}
],
};
});
/**
* Handle tool execution requests
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result;
switch (name) {
case 'conversations_history':
result = await conversationsHistory(args);
break;
case 'conversations_replies':
result = await conversationsReplies(args);
break;
case 'conversations_add_message':
result = await conversationsAddMessage(args);
break;
case 'channels_list':
result = await channelsList(args);
break;
default:
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: `Unknown tool: ${name}`
})
}
]
};
}
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : String(error)
})
}
],
isError: true
};
}
});
/**
* Start the MCP 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('Fatal error in main():', error);
process.exit(1);
});