mirror of
https://github.com/open-webui/openapi-servers
synced 2025-06-26 18:17:04 +00:00
Update readme
This commit is contained in:
parent
fd91c939f2
commit
e9757341c2
@ -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
|
||||
|
@ -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);
|
||||
});
|
Loading…
Reference in New Issue
Block a user