#
tokens: 4838/50000 10/10 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── index.ts
│   └── slack
│       ├── client.ts
│       ├── messages.ts
│       └── users.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | dist
2 | node_modules
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Slack Model Context Protocol Server
  2 | 
  3 | This is a connector to allow Claude Desktop (or any MCP client) to interact with your Slack workspace to post messages and query a list of all users.
  4 | 
  5 | ## Local Development
  6 | 
  7 | In order to run this client locally, add the following configuration to your Claude Desktop MCP Server config file: 
  8 | 
  9 | ```
 10 |  {
 11 |   "mcpServers": {
 12 |     "mcp-slack-local": {
 13 |       "command": "node",
 14 |       "args": ["/path/to/project/dist/index.js"], <---- replace this with your project path
 15 |       "env": {
 16 |         "SLACK_BOT_USER_OAUTH_TOKEN": "test-bot-token", 
 17 |         "SLACK_TEAM_ID": "test-team-id"
 18 |       }
 19 |     },
 20 |   }
 21 | }
 22 | ```
 23 | 
 24 | After this, you should be able to test this implementation in your Claude Desktop App using example prompts like: 
 25 | 
 26 | - "Can you list all users of my Slack team?"
 27 | - "Can you send a welcome message to my Slack Channel with the ID `<channel id>`?"
 28 | 
 29 | Running the server locally: 
 30 | 
 31 | ```
 32 | node dist/index.js
 33 | ```
 34 | 
 35 | With the build in another terminal 
 36 | 
 37 | ```
 38 | npm run watch
 39 | ```
 40 | 
 41 | ## Slack Permission Scopes 
 42 | 
 43 | The following permissions are already implemented: 
 44 | 
 45 | | Permission | Description | Implemented |
 46 | |------------|-------------|-------------|
 47 | | app_configurations:read | Read app configuration info via App Manifest APIs | ❌ |
 48 | | app_configurations:write | Write app configuration info and create apps via App Manifest APIs | ❌ |
 49 | | app_mentions:read | View messages that directly mention @your_slack_app in conversations that the app is in | ❌ |
 50 | | assistant:write | Allow your slack app to act as an AI Assistant | ❌ |
 51 | | bookmarks:read | List bookmarks | ❌ |
 52 | | bookmarks:write | Create, edit, and remove bookmarks | ❌ |
 53 | | calls:read | View information about ongoing and past calls | ❌ |
 54 | | calls:write | Start and manage calls in a workspace | ❌ |
 55 | | canvases:read | your slack app will be able to access contents of canvases created inside Slack. | ❌ |
 56 | | canvases:write | your slack app will be able to create, edit and remove canvases. | ❌ |
 57 | | channels:history | View messages and other content in public channels that your slack app has been added to | ❌ |
 58 | | channels:join | Join public channels in a workspace | ❌ |
 59 | | channels:manage | Manage public channels that your slack app has been added to and create new ones | ❌ |
 60 | | channels:read | View basic information about public channels in a workspace | ❌ |
 61 | | channels:write.invites | Invite members to public channels | ❌ |
 62 | | channels:write.topic | Set the description of public channels | ❌ |
 63 | | chat:write | Post messages in approved channels & conversations | ✅ |
 64 | | chat:write.customize | Send messages as @your_slack_app with a customized username and avatar | ❌ |
 65 | | chat:write.public | Send messages to channels @your_slack_app isn't a member of | ❌ |
 66 | | commands | Add shortcuts and/or slash commands that people can use | ❌ |
 67 | | conversations.connect:manage | Allows your slack app to manage Slack Connect channels | ❌ |
 68 | | conversations.connect:read | Receive Slack Connect invite events sent to the channels your slack app is in | ❌ |
 69 | | conversations.connect:write | Create Slack Connect invitations for channels that your slack app has been added to, and accept invitations sent to your slack app | ❌ |
 70 | | datastore:read | View and see data from Slack App Datastore | ❌ |
 71 | | datastore:write | Write data to Slack App Datastore | ❌ |
 72 | | dnd:read | View Do Not Disturb settings for people in a workspace | ❌ |
 73 | | emoji:read | View custom emoji in a workspace | ❌ |
 74 | | files:read | View files shared in channels and conversations that your slack app has been added to | ❌ |
 75 | | files:write | Upload, edit, and delete files as your slack app | ❌ |
 76 | | groups:history | View messages and other content in private channels that your slack app has been added to | ❌ |
 77 | | groups:read | View basic information about private channels that your slack app has been added to | ❌ |
 78 | | groups:write | Manage private channels that your slack app has been added to and create new ones | ❌ |
 79 | | groups:write.invites | Invite members to private channels | ❌ |
 80 | | groups:write.topic | Set the description of private channels | ❌ |
 81 | | im:history | View messages and other content in direct messages that your slack app has been added to | ❌ |
 82 | | im:read | View basic information about direct messages that your slack app has been added to | ❌ |
 83 | | im:write | Start direct messages with people | ❌ |
 84 | | im:write.topic | Set the description in direct messages | ❌ |
 85 | | incoming-webhook | Create one-way webhooks to post messages to a specific channel | ❌ |
 86 | | links.embed:write | Embed video player URLs in messages and app surfaces | ❌ |
 87 | | links:read | View URLs in messages | ❌ |
 88 | | links:write | Show previews of URLs in messages | ❌ |
 89 | | metadata.message:read | Allows your slack app to read message metadata in channels that your slack app has been added to | ❌ |
 90 | | mpim:history | View messages and other content in group direct messages that your slack app has been added to | ❌ |
 91 | | mpim:read | View basic information about group direct messages that your slack app has been added to | ❌ |
 92 | | mpim:write | Start group direct messages with people | ❌ |
 93 | | mpim:write.topic | Set the description in group direct messages | ❌ |
 94 | | none | Execute methods without needing a scope | ❌ |
 95 | | pins:read | View pinned content in channels and conversations that your slack app has been added to | ❌ |
 96 | | pins:write | Add and remove pinned messages and files | ❌ |
 97 | | reactions:read | View emoji reactions and their associated content in channels and conversations that your slack app has been added to | ❌ |
 98 | | reactions:write | Add and edit emoji reactions | ❌ |
 99 | | reminders:read | View reminders created by your slack app | ❌ |
100 | | reminders:write | Add, remove, or mark reminders as complete | ❌ |
101 | | remote_files:read | View remote files added by the app in a workspace | ❌ |
102 | | remote_files:share | Share remote files on a user's behalf | ❌ |
103 | | remote_files:write | Add, edit, and delete remote files on a user's behalf | ❌ |
104 | | search:read.files | Search a workspace's content in files | ❌ |
105 | | search:read.im | Search a workspace's content in direct messages | ❌ |
106 | | search:read.mpim | Search a workspace's content in group direct messages | ❌ |
107 | | search:read.private | Search a workspace's content in private channels | ❌ |
108 | | search:read.public | Search a workspace's content in public channels | ❌ |
109 | | team.billing:read | Allows your slack app to read the billing plan for workspaces your slack app has been installed to | ❌ |
110 | | team.preferences:read | Allows your slack app to read the preferences for workspaces your slack app has been installed to | ❌ |
111 | | team:read | View the name, email domain, and icon for workspaces your slack app is connected to | ❌ |
112 | | tokens.basic | Execute methods without needing a scope | ❌ |
113 | | triggers:read | Read new Platform triggers | ❌ |
114 | | triggers:write | Create new Platform triggers | ❌ |
115 | | usergroups:read | View user groups in a workspace | ❌ |
116 | | usergroups:write | Create and manage user groups | ❌ |
117 | | users.profile:read | View profile details about people in a workspace | ❌ |
118 | | users:read | View people in a workspace | ✅ |
119 | | users:read.email | View email addresses of people in a workspace | ❌ |
120 | | users:write | Set presence for your slack app | ❌ |
121 | | workflow.steps:execute | Add steps that people can use in Workflow Builder | ❌ |
122 | | workflows.templates:read | Read a workflow template | ❌ |
123 | | workflows.templates:write | Write a workflow template | ❌ |
124 | 
125 | ## Contact 
126 | 
127 | If you have questions, feel free to contact us via [AVIMBU](https://avimbu.com).
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | FROM node:23-alpine
 2 | 
 3 | WORKDIR /app
 4 | 
 5 | COPY . ./
 6 | 
 7 | # Install dependencies
 8 | RUN npm install
 9 | 
10 | # Build the application
11 | RUN npm run build
12 | 
13 | # Command will be provided by smithery.yaml
14 | CMD ["node", "dist/index.js"]
15 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "outDir": "./dist",
 4 |     "rootDir": "./src",
 5 |     "target": "ES2020",
 6 |     "module": "ESNext",
 7 |     "moduleResolution": "node",
 8 |     "esModuleInterop": true,
 9 |     "lib": ["ES2020", "DOM"],
10 |     "strict": true,
11 |     "skipLibCheck": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | startCommand:
 2 |   type: stdio
 3 |   configSchema:
 4 |     type: object
 5 |     required:
 6 |       - slackBotUserOauthToken
 7 |       - slackTeamId
 8 |     properties:
 9 |       slackBotUserOauthToken:
10 |         type: string
11 |         description: The OAuth Token of your Slack's App User.
12 |       slackTeamId:
13 |         type: string
14 |         description: The Slack Team ID of the workspace.
15 |   commandFunction:
16 |     |-
17 |     (config) => ({ command: 'node', args: ['dist/index.js'], env: { SLACK_BOT_USER_OAUTH_TOKEN: config.slackBotToken, SLACK_TEAM_ID: config.slackTeamId } })
```

--------------------------------------------------------------------------------
/src/slack/messages.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Tool } from "@modelcontextprotocol/sdk/types.js";
 2 | 
 3 | export interface PostMessageArgs {
 4 |   channel_id: string;
 5 |   text: string;
 6 | }
 7 | 
 8 | export const postMessageTool: Tool = {
 9 |   name: "slack_post_message",
10 |   description: "Post a new message to a Slack channel",
11 |   inputSchema: {
12 |     type: "object",
13 |     properties: {
14 |       channel_id: {
15 |         type: "string",
16 |         description: "The Channel ID to post the message to",
17 |       },
18 |       text: {
19 |         type: "string",
20 |         description: "The message text to post",
21 |       },
22 |     },
23 |     required: ["channel_id", "text"],
24 |   },
25 | };
26 | 
```

--------------------------------------------------------------------------------
/src/slack/users.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Tool } from "@modelcontextprotocol/sdk/types.js";
 2 | 
 3 | export interface GetUsersArgs {
 4 |   cursor?: string;
 5 |   limit?: number;
 6 | }
 7 | 
 8 | export const getUsersTool: Tool = {
 9 |   name: "slack_get_users",
10 |   description:
11 |     "Get a list of all users in the workspace with basic information",
12 |   inputSchema: {
13 |     type: "object",
14 |     properties: {
15 |       cursor: {
16 |         type: "string",
17 |         description: "Pagination cursor for next page of results",
18 |       },
19 |       limit: {
20 |         type: "number",
21 |         description: "Maximum number of users to return (default 100, max 200)",
22 |         default: 100,
23 |       },
24 |     },
25 |   },
26 | };
27 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "name": "slack-model-context-protocol-server",
 3 |     "version": "0.0.1",
 4 |     "description": "MCP Interaction Server for Slack Workspaces",
 5 |     "license": "MIT",
 6 |     "author": "AVIMBU FlexCo, Austria avimbu.com",
 7 |     "homepage": "https://avimbu.com",
 8 |     "type": "module",
 9 |     "bin": {
10 |         "mcp-slack-server": "dist/index.js"
11 |     },
12 |     "files": [
13 |         "dist"
14 |     ],
15 |     "scripts": {
16 |         "build": "tsc && shx chmod +x dist/*.js",
17 |         "prepare": "npm run build",
18 |         "watch": "tsc --watch"
19 |     },
20 |     "dependencies": {
21 |         "@modelcontextprotocol/sdk": "1.7.0",
22 |         "@types/node": "^22"
23 |     },
24 |     "devDependencies": {
25 |         "@types/node": "^22",
26 |         "shx": "^0.3.4",
27 |         "typescript": "^5.8.2"
28 |     }
29 | }
30 | 
```

--------------------------------------------------------------------------------
/src/slack/client.ts:
--------------------------------------------------------------------------------

```typescript
 1 | class SlackClient {
 2 |   public static readonly BOT_USER_OAUTH_TOKEN =
 3 |     process.env.SLACK_BOT_USER_OAUTH_TOKEN || "test-token";
 4 |   public static readonly TEAM_ID = process.env.SLACK_TEAM_ID || "team-id";
 5 | 
 6 |   private readonly botHeaders: {
 7 |     Authorization: string;
 8 |     "Content-Type": string;
 9 |   };
10 | 
11 |   constructor() {
12 |     // Check if token is properly set
13 |     if (
14 |       !SlackClient.BOT_USER_OAUTH_TOKEN ||
15 |       SlackClient.BOT_USER_OAUTH_TOKEN === "test-token"
16 |     ) {
17 |       console.warn(
18 |         "Warning: Using default Slack token. Set SLACK_BOT_TOKEN environment variable for production use."
19 |       );
20 |     }
21 | 
22 |     if (!SlackClient.TEAM_ID || SlackClient.TEAM_ID === "team-id") {
23 |       console.warn(
24 |         "Warning: Using default Team ID. Set SLACK_TEAM_ID environment variable for production use."
25 |       );
26 |     }
27 | 
28 |     this.botHeaders = {
29 |       Authorization: `Bearer ${SlackClient.BOT_USER_OAUTH_TOKEN}`,
30 |       "Content-Type": "application/json",
31 |     };
32 |   }
33 | 
34 |   async postMessage(channel_id: string, text: string): Promise<any> {
35 |     const response = await fetch("https://slack.com/api/chat.postMessage", {
36 |       method: "POST",
37 |       headers: this.botHeaders,
38 |       body: JSON.stringify({
39 |         channel: channel_id,
40 |         text: text,
41 |       }),
42 |     });
43 | 
44 |     return response.json();
45 |   }
46 | 
47 |   async getUsers(limit: number = 100, cursor?: string): Promise<any> {
48 |     const params = new URLSearchParams({
49 |       limit: Math.min(limit, 200).toString(),
50 |       team_id: SlackClient.TEAM_ID,
51 |     });
52 | 
53 |     if (cursor) {
54 |       params.append("cursor", cursor);
55 |     }
56 | 
57 |     const response = await fetch(`https://slack.com/api/users.list?${params}`, {
58 |       headers: this.botHeaders,
59 |     });
60 | 
61 |     return response.json();
62 |   }
63 | }
64 | 
65 | export const slackClient = new SlackClient();
66 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 4 | import {
 5 |   CallToolRequest,
 6 |   CallToolRequestSchema,
 7 |   ListToolsRequestSchema,
 8 | } from "@modelcontextprotocol/sdk/types.js";
 9 | import { GetUsersArgs, getUsersTool } from "./slack/users.js";
10 | import { PostMessageArgs, postMessageTool } from "./slack/messages.js";
11 | import { slackClient } from "./slack/client.js";
12 | 
13 | const server = new Server(
14 |   {
15 |     name: "slack-model-context-protocol-server",
16 |     version: "0.0.1",
17 |   },
18 |   {
19 |     capabilities: {
20 |       tools: {},
21 |     },
22 |   }
23 | );
24 | 
25 | server.setRequestHandler(ListToolsRequestSchema, async () => {
26 |   return {
27 |     tools: [getUsersTool, postMessageTool],
28 |   };
29 | });
30 | 
31 | server.setRequestHandler(
32 |   CallToolRequestSchema,
33 |   async (request: CallToolRequest) => {
34 |     try {
35 |       if (!request.params.arguments) {
36 |         throw new Error("Arguments are required");
37 |       }
38 | 
39 |       switch (request.params.name) {
40 |         case "slack_post_message": {
41 |           const args = request.params.arguments as unknown as PostMessageArgs;
42 |           if (!args.channel_id || !args.text) {
43 |             throw new Error("Missing required arguments: channel_id and text");
44 |           }
45 |           const response = await slackClient.postMessage(
46 |             args.channel_id,
47 |             args.text
48 |           );
49 |           return {
50 |             content: [{ type: "text", text: JSON.stringify(response) }],
51 |           };
52 |         }
53 | 
54 |         case "slack_get_users": {
55 |           const args = request.params.arguments as unknown as GetUsersArgs;
56 |           const response = await slackClient.getUsers(args.limit, args.cursor);
57 |           return {
58 |             content: [{ type: "text", text: JSON.stringify(response) }],
59 |           };
60 |         }
61 | 
62 |         default:
63 |           throw new Error(`Unknown tool: ${request.params.name}`);
64 |       }
65 |     } catch (error) {
66 |       console.error("Error executing tool:", error);
67 |       return {
68 |         content: [
69 |           {
70 |             type: "text",
71 |             text: JSON.stringify({
72 |               error: error instanceof Error ? error.message : String(error),
73 |             }),
74 |           },
75 |         ],
76 |       };
77 |     }
78 |   }
79 | );
80 | 
81 | async function runServer() {
82 |   const transport = new StdioServerTransport();
83 |   await server.connect(transport);
84 |   console.error("Slack MCP Server running on stdio");
85 | }
86 | 
87 | runServer().catch((error) => {
88 |   console.error("Fatal error in main():", error);
89 |   process.exit(1);
90 | });
91 | 
```