Update readme

This commit is contained in:
Taylor Wilsdon 2025-04-18 10:19:11 -04:00
parent fd91c939f2
commit e9757341c2
2 changed files with 2 additions and 584 deletions

View File

@ -21,7 +21,7 @@ pip install -r requirements.txt
# Set up environment variables
export SLACK_BOT_TOKEN="xoxb-your-bot-token"
export SLACK_TEAM_ID="your-team-id"
export SLACK_CHANNEL_IDS="comma,separated,channel,ids" # Optional: restrict to specific channels
export SLACK_CHANNEL_IDS="comma,separated,channel,ids" # Optional: restrict to specific channels - leave blank to include all channels that the bot user has been added to
# Run the server
uvicorn main:app --host 0.0.0.0 --reload
@ -44,7 +44,7 @@ All functionality is wrapped in a developer-friendly OpenAPI interface, making i
---
## 🔑 Prerequisites
Most of this is pulled straight from the Slack Python SDK so the barebones readme can easily be supplemented by reading the official one. Setup looks like:
Most of this is pulled straight from the Slack Python SDK so the barebones readme can easily be supplemented by reading the official docs. To set up, you need to follow these steps:
1. **Slack Bot Token**: Create a Slack App and get a Bot User OAuth Token
- Visit [Slack API Apps](https://api.slack.com/apps)
- Create a new app or select existing

View File

@ -1,582 +0,0 @@
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequest,
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
// Type definitions for tool arguments
interface ListChannelsArgs {
limit?: number;
cursor?: string;
}
interface PostMessageArgs {
channel_id: string;
text: string;
}
interface ReplyToThreadArgs {
channel_id: string;
thread_ts: string;
text: string;
}
interface AddReactionArgs {
channel_id: string;
timestamp: string;
reaction: string;
}
interface GetChannelHistoryArgs {
channel_id: string;
limit?: number;
}
interface GetThreadRepliesArgs {
channel_id: string;
thread_ts: string;
}
interface GetUsersArgs {
cursor?: string;
limit?: number;
}
interface GetUserProfileArgs {
user_id: string;
}
// Tool definitions
const listChannelsTool: Tool = {
name: "slack_list_channels",
description: "List public or pre-defined channels in the workspace with pagination",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description:
"Maximum number of channels to return (default 100, max 200)",
default: 100,
},
cursor: {
type: "string",
description: "Pagination cursor for next page of results",
},
},
},
};
const postMessageTool: Tool = {
name: "slack_post_message",
description: "Post a new message to a Slack channel",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel to post to",
},
text: {
type: "string",
description: "The message text to post",
},
},
required: ["channel_id", "text"],
},
};
const replyToThreadTool: Tool = {
name: "slack_reply_to_thread",
description: "Reply to a specific message thread in Slack",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel containing the thread",
},
thread_ts: {
type: "string",
description: "The timestamp of the parent message in the format '1234567890.123456'. Timestamps in the format without the period can be converted by adding the period such that 6 numbers come after it.",
},
text: {
type: "string",
description: "The reply text",
},
},
required: ["channel_id", "thread_ts", "text"],
},
};
const addReactionTool: Tool = {
name: "slack_add_reaction",
description: "Add a reaction emoji to a message",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel containing the message",
},
timestamp: {
type: "string",
description: "The timestamp of the message to react to",
},
reaction: {
type: "string",
description: "The name of the emoji reaction (without ::)",
},
},
required: ["channel_id", "timestamp", "reaction"],
},
};
const getChannelHistoryTool: Tool = {
name: "slack_get_channel_history",
description: "Get recent messages from a channel",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel",
},
limit: {
type: "number",
description: "Number of messages to retrieve (default 10)",
default: 10,
},
},
required: ["channel_id"],
},
};
const getThreadRepliesTool: Tool = {
name: "slack_get_thread_replies",
description: "Get all replies in a message thread",
inputSchema: {
type: "object",
properties: {
channel_id: {
type: "string",
description: "The ID of the channel containing the thread",
},
thread_ts: {
type: "string",
description: "The timestamp of the parent message in the format '1234567890.123456'. Timestamps in the format without the period can be converted by adding the period such that 6 numbers come after it.",
},
},
required: ["channel_id", "thread_ts"],
},
};
const getUsersTool: Tool = {
name: "slack_get_users",
description:
"Get a list of all users in the workspace with their basic profile information",
inputSchema: {
type: "object",
properties: {
cursor: {
type: "string",
description: "Pagination cursor for next page of results",
},
limit: {
type: "number",
description: "Maximum number of users to return (default 100, max 200)",
default: 100,
},
},
},
};
const getUserProfileTool: Tool = {
name: "slack_get_user_profile",
description: "Get detailed profile information for a specific user",
inputSchema: {
type: "object",
properties: {
user_id: {
type: "string",
description: "The ID of the user",
},
},
required: ["user_id"],
},
};
class SlackClient {
private botHeaders: { Authorization: string; "Content-Type": string };
constructor(botToken: string) {
this.botHeaders = {
Authorization: `Bearer ${botToken}`,
"Content-Type": "application/json",
};
}
async getChannels(limit: number = 100, cursor?: string): Promise<any> {
const predefinedChannelIds = process.env.SLACK_CHANNEL_IDS;
if (!predefinedChannelIds) {
const params = new URLSearchParams({
types: "public_channel",
exclude_archived: "true",
limit: Math.min(limit, 200).toString(),
team_id: process.env.SLACK_TEAM_ID!,
});
if (cursor) {
params.append("cursor", cursor);
}
const response = await fetch(
`https://slack.com/api/conversations.list?${params}`,
{ headers: this.botHeaders },
);
return response.json();
}
const predefinedChannelIdsArray = predefinedChannelIds.split(",").map((id: string) => id.trim());
const channels = [];
for (const channelId of predefinedChannelIdsArray) {
const params = new URLSearchParams({
channel: channelId,
});
const response = await fetch(
`https://slack.com/api/conversations.info?${params}`,
{ headers: this.botHeaders }
);
const data = await response.json();
if (data.ok && data.channel && !data.channel.is_archived) {
channels.push(data.channel);
}
}
return {
ok: true,
channels: channels,
response_metadata: { next_cursor: "" },
};
}
async postMessage(channel_id: string, text: string): Promise<any> {
const response = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: this.botHeaders,
body: JSON.stringify({
channel: channel_id,
text: text,
}),
});
return response.json();
}
async postReply(
channel_id: string,
thread_ts: string,
text: string,
): Promise<any> {
const response = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: this.botHeaders,
body: JSON.stringify({
channel: channel_id,
thread_ts: thread_ts,
text: text,
}),
});
return response.json();
}
async addReaction(
channel_id: string,
timestamp: string,
reaction: string,
): Promise<any> {
const response = await fetch("https://slack.com/api/reactions.add", {
method: "POST",
headers: this.botHeaders,
body: JSON.stringify({
channel: channel_id,
timestamp: timestamp,
name: reaction,
}),
});
return response.json();
}
async getChannelHistory(
channel_id: string,
limit: number = 10,
): Promise<any> {
const params = new URLSearchParams({
channel: channel_id,
limit: limit.toString(),
});
const response = await fetch(
`https://slack.com/api/conversations.history?${params}`,
{ headers: this.botHeaders },
);
return response.json();
}
async getThreadReplies(channel_id: string, thread_ts: string): Promise<any> {
const params = new URLSearchParams({
channel: channel_id,
ts: thread_ts,
});
const response = await fetch(
`https://slack.com/api/conversations.replies?${params}`,
{ headers: this.botHeaders },
);
return response.json();
}
async getUsers(limit: number = 100, cursor?: string): Promise<any> {
const params = new URLSearchParams({
limit: Math.min(limit, 200).toString(),
team_id: process.env.SLACK_TEAM_ID!,
});
if (cursor) {
params.append("cursor", cursor);
}
const response = await fetch(`https://slack.com/api/users.list?${params}`, {
headers: this.botHeaders,
});
return response.json();
}
async getUserProfile(user_id: string): Promise<any> {
const params = new URLSearchParams({
user: user_id,
include_labels: "true",
});
const response = await fetch(
`https://slack.com/api/users.profile.get?${params}`,
{ headers: this.botHeaders },
);
return response.json();
}
}
async function main() {
const botToken = process.env.SLACK_BOT_TOKEN;
const teamId = process.env.SLACK_TEAM_ID;
if (!botToken || !teamId) {
console.error(
"Please set SLACK_BOT_TOKEN and SLACK_TEAM_ID environment variables",
);
process.exit(1);
}
console.error("Starting Slack MCP Server...");
const server = new Server(
{
name: "Slack MCP Server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
},
);
const slackClient = new SlackClient(botToken);
server.setRequestHandler(
CallToolRequestSchema,
async (request: CallToolRequest) => {
console.error("Received CallToolRequest:", request);
try {
if (!request.params.arguments) {
throw new Error("No arguments provided");
}
switch (request.params.name) {
case "slack_list_channels": {
const args = request.params
.arguments as unknown as ListChannelsArgs;
const response = await slackClient.getChannels(
args.limit,
args.cursor,
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_post_message": {
const args = request.params.arguments as unknown as PostMessageArgs;
if (!args.channel_id || !args.text) {
throw new Error(
"Missing required arguments: channel_id and text",
);
}
const response = await slackClient.postMessage(
args.channel_id,
args.text,
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_reply_to_thread": {
const args = request.params
.arguments as unknown as ReplyToThreadArgs;
if (!args.channel_id || !args.thread_ts || !args.text) {
throw new Error(
"Missing required arguments: channel_id, thread_ts, and text",
);
}
const response = await slackClient.postReply(
args.channel_id,
args.thread_ts,
args.text,
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_add_reaction": {
const args = request.params.arguments as unknown as AddReactionArgs;
if (!args.channel_id || !args.timestamp || !args.reaction) {
throw new Error(
"Missing required arguments: channel_id, timestamp, and reaction",
);
}
const response = await slackClient.addReaction(
args.channel_id,
args.timestamp,
args.reaction,
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_get_channel_history": {
const args = request.params
.arguments as unknown as GetChannelHistoryArgs;
if (!args.channel_id) {
throw new Error("Missing required argument: channel_id");
}
const response = await slackClient.getChannelHistory(
args.channel_id,
args.limit,
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_get_thread_replies": {
const args = request.params
.arguments as unknown as GetThreadRepliesArgs;
if (!args.channel_id || !args.thread_ts) {
throw new Error(
"Missing required arguments: channel_id and thread_ts",
);
}
const response = await slackClient.getThreadReplies(
args.channel_id,
args.thread_ts,
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_get_users": {
const args = request.params.arguments as unknown as GetUsersArgs;
const response = await slackClient.getUsers(
args.limit,
args.cursor,
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "slack_get_user_profile": {
const args = request.params
.arguments as unknown as GetUserProfileArgs;
if (!args.user_id) {
throw new Error("Missing required argument: user_id");
}
const response = await slackClient.getUserProfile(args.user_id);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error) {
console.error("Error executing tool:", error);
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
}),
},
],
};
}
},
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error("Received ListToolsRequest");
return {
tools: [
listChannelsTool,
postMessageTool,
replyToThreadTool,
addReactionTool,
getChannelHistoryTool,
getThreadRepliesTool,
getUsersTool,
getUserProfileTool,
],
};
});
const transport = new StdioServerTransport();
console.error("Connecting server to transport...");
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);
});