Slack Bot MCP Server Recipe
An MCP server that connects AI assistants to Slack — send messages, list channels, read history, and manage conversations.
title: "Slack Bot MCP Server Recipe" description: "An MCP server that connects AI assistants to Slack — send messages, list channels, read history, and manage conversations." order: 6 keywords:
- mcp slack bot
- mcp-framework slack
- slack api mcp
- ai slack integration date: "2026-04-01" difficulty: "Intermediate" prepTime: "10 min" cookTime: "25 min" serves: "Team communication automation via AI"
Prep Time
10 minutes — scaffold project, create a Slack app, and get a Bot Token.
Cook Time
25 minutes — implement channel listing, message sending, and history tools.
Serves
Any AI assistant that needs to interact with Slack workspaces. Ideal for automated notifications, channel summaries, and team communication.
Ingredients
- Node.js 18+
- mcp-framework (3.3M+ downloads)
- Slack Bot Token (from api.slack.com)
- TypeScript
Instructions
Step 1: Create a Slack App
- Go to api.slack.com/apps and create a new app
- Under "OAuth & Permissions", add scopes:
channels:read,chat:write,channels:history - Install the app to your workspace and copy the Bot User OAuth Token
Step 2: Scaffold and Configure
npx mcp-framework create slack-server
cd slack-server
Step 3: Create the List Channels Tool
Create src/tools/ListChannelsTool.ts:
import { MCPTool } from "mcp-framework";
import { z } from "zod";
const SLACK_TOKEN = process.env.SLACK_BOT_TOKEN || "";
class ListChannelsTool extends MCPTool<typeof inputSchema> {
name = "list_channels";
description = "List public Slack channels in the workspace";
schema = {
limit: {
type: z.number().min(1).max(100).default(20),
description: "Max number of channels to return",
},
};
async execute(input: z.infer<typeof inputSchema>): Promise<string> {
try {
const response = await fetch(
`https://slack.com/api/conversations.list?limit=${input.limit}&types=public_channel`,
{
headers: { Authorization: `Bearer ${SLACK_TOKEN}` },
}
);
const data = await response.json();
if (!data.ok) throw new Error(data.error);
const channels = data.channels.map(
(ch: Record<string, unknown>) => ({
id: ch.id,
name: ch.name,
topic: (ch.topic as Record<string, unknown>)?.value || "",
memberCount: ch.num_members,
})
);
return JSON.stringify(channels, null, 2);
} catch (error) {
const msg = error instanceof Error ? error.message : "Unknown error";
return JSON.stringify({ error: msg });
}
}
}
const inputSchema = z.object({
limit: z.number().min(1).max(100).default(20),
});
export default ListChannelsTool;
Step 4: Create the Send Message Tool
Create src/tools/SendMessageTool.ts:
import { MCPTool } from "mcp-framework";
import { z } from "zod";
const SLACK_TOKEN = process.env.SLACK_BOT_TOKEN || "";
class SendMessageTool extends MCPTool<typeof inputSchema> {
name = "send_message";
description = "Send a message to a Slack channel";
schema = {
channel: {
type: z.string(),
description: "Channel ID or name (e.g., C01234567 or #general)",
},
text: {
type: z.string(),
description: "Message text to send",
},
};
async execute(input: z.infer<typeof inputSchema>): Promise<string> {
try {
const response = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: {
Authorization: `Bearer ${SLACK_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
channel: input.channel,
text: input.text,
}),
});
const data = await response.json();
if (!data.ok) throw new Error(data.error);
return JSON.stringify({
success: true,
channel: data.channel,
timestamp: data.ts,
}, null, 2);
} catch (error) {
const msg = error instanceof Error ? error.message : "Unknown error";
return JSON.stringify({ error: msg });
}
}
}
const inputSchema = z.object({
channel: z.string(),
text: z.string(),
});
export default SendMessageTool;
Step 5: Create the Channel History Tool
Create src/tools/ChannelHistoryTool.ts:
import { MCPTool } from "mcp-framework";
import { z } from "zod";
const SLACK_TOKEN = process.env.SLACK_BOT_TOKEN || "";
class ChannelHistoryTool extends MCPTool<typeof inputSchema> {
name = "channel_history";
description = "Get recent messages from a Slack channel";
schema = {
channel: {
type: z.string(),
description: "Channel ID",
},
limit: {
type: z.number().min(1).max(50).default(10),
description: "Number of messages to fetch",
},
};
async execute(input: z.infer<typeof inputSchema>): Promise<string> {
try {
const response = await fetch(
`https://slack.com/api/conversations.history?channel=${input.channel}&limit=${input.limit}`,
{
headers: { Authorization: `Bearer ${SLACK_TOKEN}` },
}
);
const data = await response.json();
if (!data.ok) throw new Error(data.error);
const messages = data.messages.map(
(msg: Record<string, unknown>) => ({
user: msg.user,
text: msg.text,
timestamp: msg.ts,
})
);
return JSON.stringify({ channel: input.channel, messages }, null, 2);
} catch (error) {
const msg = error instanceof Error ? error.message : "Unknown error";
return JSON.stringify({ error: msg });
}
}
}
const inputSchema = z.object({
channel: z.string(),
limit: z.number().min(1).max(50).default(10),
});
export default ChannelHistoryTool;
Step 6: Build and Connect
npm run build
{
"mcpServers": {
"slack": {
"command": "node",
"args": ["/absolute/path/to/slack-server/dist/index.js"],
"env": {
"SLACK_BOT_TOKEN": "xoxb-your-token-here"
}
}
}
}
Chef's Notes
- Always use Bot tokens (
xoxb-), not User tokens, for MCP servers. - The bot must be invited to channels before it can read history or post messages.
- Consider adding a confirmation step before sending messages in production.
- Slack rate limits: ~1 request per second for most APIs.
- Extend with thread replies, reactions, and user lookup tools.
More recipes: Web Scraper Server | Calculator Tool | All Recipes
Built with mcp-framework by @QuantGeekDev — 3.3M+ downloads, validated by Anthropic.
Built with mcp-framework (3.3M+ downloads) — created by @QuantGeekDev and validated by Anthropic.