slack-workspace-mcp-server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/build/index.d.ts +2 -0
- package/build/index.integration-with-mock.d.ts +2 -0
- package/build/index.integration-with-mock.js +34 -0
- package/build/index.js +76 -0
- package/package.json +47 -0
- package/shared/index.d.ts +4 -0
- package/shared/index.js +7 -0
- package/shared/logging.d.ts +10 -0
- package/shared/logging.js +21 -0
- package/shared/server.d.ts +134 -0
- package/shared/server.js +68 -0
- package/shared/slack-client/lib/add-reaction.d.ts +5 -0
- package/shared/slack-client/lib/add-reaction.js +30 -0
- package/shared/slack-client/lib/get-channel.d.ts +5 -0
- package/shared/slack-client/lib/get-channel.js +24 -0
- package/shared/slack-client/lib/get-channels.d.ts +10 -0
- package/shared/slack-client/lib/get-channels.js +37 -0
- package/shared/slack-client/lib/get-messages.d.ts +16 -0
- package/shared/slack-client/lib/get-messages.js +38 -0
- package/shared/slack-client/lib/get-thread.d.ts +16 -0
- package/shared/slack-client/lib/get-thread.js +39 -0
- package/shared/slack-client/lib/post-message.d.ts +10 -0
- package/shared/slack-client/lib/post-message.js +44 -0
- package/shared/slack-client/lib/update-message.d.ts +5 -0
- package/shared/slack-client/lib/update-message.js +32 -0
- package/shared/slack-client/slack-client.integration-mock.d.ts +16 -0
- package/shared/slack-client/slack-client.integration-mock.js +106 -0
- package/shared/tools/get-channel.d.ts +55 -0
- package/shared/tools/get-channel.js +136 -0
- package/shared/tools/get-channels.d.ts +46 -0
- package/shared/tools/get-channels.js +107 -0
- package/shared/tools/get-thread.d.ts +54 -0
- package/shared/tools/get-thread.js +130 -0
- package/shared/tools/post-message.d.ts +44 -0
- package/shared/tools/post-message.js +81 -0
- package/shared/tools/react-to-message.d.ts +51 -0
- package/shared/tools/react-to-message.js +90 -0
- package/shared/tools/reply-to-thread.d.ts +59 -0
- package/shared/tools/reply-to-thread.js +97 -0
- package/shared/tools/update-message.d.ts +51 -0
- package/shared/tools/update-message.js +87 -0
- package/shared/tools.d.ts +18 -0
- package/shared/tools.js +78 -0
- package/shared/types.d.ts +112 -0
- package/shared/types.js +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Slack MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server for integrating with Slack workspaces. This server provides tools for reading and writing messages, managing threads, and reacting to messages in Slack.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **List Channels** - View all accessible public and private channels
|
|
8
|
+
- **Read Channel** - Get channel info and recent messages
|
|
9
|
+
- **Read Threads** - Get full threaded conversations
|
|
10
|
+
- **Post Messages** - Send new messages to channels
|
|
11
|
+
- **Reply to Threads** - Continue threaded conversations
|
|
12
|
+
- **Update Messages** - Edit previously posted messages
|
|
13
|
+
- **React to Messages** - Add emoji reactions to messages
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install slack-workspace-mcp-server
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or run directly with npx:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx slack-workspace-mcp-server
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
### Required Environment Variables
|
|
30
|
+
|
|
31
|
+
| Variable | Description | Example |
|
|
32
|
+
| ----------------- | -------------------------- | -------------- |
|
|
33
|
+
| `SLACK_BOT_TOKEN` | Slack Bot User OAuth Token | `xoxb-1234...` |
|
|
34
|
+
|
|
35
|
+
### Optional Environment Variables
|
|
36
|
+
|
|
37
|
+
| Variable | Description | Default |
|
|
38
|
+
| -------------------- | ----------------------------------- | ----------- |
|
|
39
|
+
| `ENABLED_TOOLGROUPS` | Comma-separated list of tool groups | All enabled |
|
|
40
|
+
|
|
41
|
+
### Getting a Bot Token
|
|
42
|
+
|
|
43
|
+
1. Go to [api.slack.com/apps](https://api.slack.com/apps)
|
|
44
|
+
2. Create a new app or select an existing one
|
|
45
|
+
3. Navigate to **OAuth & Permissions**
|
|
46
|
+
4. Add the following **Bot Token Scopes**:
|
|
47
|
+
- `channels:read` - View basic channel information
|
|
48
|
+
- `channels:history` - View messages in public channels
|
|
49
|
+
- `groups:read` - View private channels
|
|
50
|
+
- `groups:history` - View messages in private channels
|
|
51
|
+
- `chat:write` - Send messages
|
|
52
|
+
- `reactions:write` - Add reactions
|
|
53
|
+
5. Install the app to your workspace
|
|
54
|
+
6. Copy the **Bot User OAuth Token** (starts with `xoxb-`)
|
|
55
|
+
|
|
56
|
+
### MCP Client Configuration
|
|
57
|
+
|
|
58
|
+
For Claude Desktop, add to your configuration:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"slack": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["slack-workspace-mcp-server"],
|
|
66
|
+
"env": {
|
|
67
|
+
"SLACK_BOT_TOKEN": "xoxb-your-token-here"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Available Tools
|
|
75
|
+
|
|
76
|
+
### Read-Only Tools
|
|
77
|
+
|
|
78
|
+
| Tool | Description |
|
|
79
|
+
| -------------------- | ------------------------------------- |
|
|
80
|
+
| `slack_get_channels` | List all accessible channels |
|
|
81
|
+
| `slack_get_channel` | Get channel info with recent messages |
|
|
82
|
+
| `slack_get_thread` | Get a thread with all replies |
|
|
83
|
+
|
|
84
|
+
### Write Tools
|
|
85
|
+
|
|
86
|
+
| Tool | Description |
|
|
87
|
+
| ------------------------ | ---------------------------------- |
|
|
88
|
+
| `slack_post_message` | Post a new message to a channel |
|
|
89
|
+
| `slack_reply_to_thread` | Reply to an existing thread |
|
|
90
|
+
| `slack_update_message` | Update a previously posted message |
|
|
91
|
+
| `slack_react_to_message` | Add an emoji reaction to a message |
|
|
92
|
+
|
|
93
|
+
## Tool Groups
|
|
94
|
+
|
|
95
|
+
You can control which tools are available using the `ENABLED_TOOLGROUPS` environment variable:
|
|
96
|
+
|
|
97
|
+
- `readonly` - Only read operations (get channels, messages, threads)
|
|
98
|
+
- `write` - All operations including posting and reactions
|
|
99
|
+
|
|
100
|
+
Example: `ENABLED_TOOLGROUPS=readonly` to disable all write operations.
|
|
101
|
+
|
|
102
|
+
## Development
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Install dependencies
|
|
106
|
+
npm run install-all
|
|
107
|
+
|
|
108
|
+
# Run in development mode
|
|
109
|
+
npm run dev
|
|
110
|
+
|
|
111
|
+
# Run tests
|
|
112
|
+
npm test # Functional tests
|
|
113
|
+
npm run test:integration # Integration tests
|
|
114
|
+
npm run test:manual # Manual tests (requires real credentials)
|
|
115
|
+
|
|
116
|
+
# Build
|
|
117
|
+
npm run build
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Integration Test Server Entry Point with Mock Client Factory
|
|
4
|
+
*
|
|
5
|
+
* This file is used for integration testing with a mocked SlackClient.
|
|
6
|
+
* It uses the real MCP server but injects a mock client factory.
|
|
7
|
+
*
|
|
8
|
+
* Mock data is passed via the SLACK_MOCK_DATA environment variable.
|
|
9
|
+
*/
|
|
10
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
|
+
import { createMCPServer } from '../shared/index.js';
|
|
12
|
+
import { createIntegrationMockSlackClient } from '../shared/slack-client/slack-client.integration-mock.js';
|
|
13
|
+
async function main() {
|
|
14
|
+
const transport = new StdioServerTransport();
|
|
15
|
+
// Parse mock data from environment variable
|
|
16
|
+
let mockData = {};
|
|
17
|
+
if (process.env.SLACK_MOCK_DATA) {
|
|
18
|
+
try {
|
|
19
|
+
mockData = JSON.parse(process.env.SLACK_MOCK_DATA);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
console.error('Failed to parse SLACK_MOCK_DATA:', e);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Create client factory that returns our mock
|
|
26
|
+
const clientFactory = () => createIntegrationMockSlackClient(mockData);
|
|
27
|
+
const { server, registerHandlers } = createMCPServer();
|
|
28
|
+
await registerHandlers(server, clientFactory);
|
|
29
|
+
await server.connect(transport);
|
|
30
|
+
}
|
|
31
|
+
main().catch((error) => {
|
|
32
|
+
console.error('Server error:', error);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
package/build/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createMCPServer } from '../shared/index.js';
|
|
4
|
+
import { logServerStart, logError, logWarning } from '../shared/logging.js';
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// ENVIRONMENT VALIDATION
|
|
7
|
+
// =============================================================================
|
|
8
|
+
function validateEnvironment() {
|
|
9
|
+
const required = [
|
|
10
|
+
{
|
|
11
|
+
name: 'SLACK_BOT_TOKEN',
|
|
12
|
+
description: 'Slack Bot User OAuth Token (starts with xoxb-)',
|
|
13
|
+
example: 'xoxb-YOUR-WORKSPACE-ID-YOUR-BOT-TOKEN-HERE',
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
const optional = [
|
|
17
|
+
{
|
|
18
|
+
name: 'ENABLED_TOOLGROUPS',
|
|
19
|
+
description: 'Comma-separated list of tool groups to enable (readonly, write)',
|
|
20
|
+
defaultValue: 'all groups enabled',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const missing = required.filter(({ name }) => !process.env[name]);
|
|
24
|
+
if (missing.length > 0) {
|
|
25
|
+
logError('validateEnvironment', 'Missing required environment variables:');
|
|
26
|
+
missing.forEach(({ name, description, example }) => {
|
|
27
|
+
console.error(` - ${name}: ${description}`);
|
|
28
|
+
console.error(` Example: ${example}`);
|
|
29
|
+
});
|
|
30
|
+
if (optional.length > 0) {
|
|
31
|
+
console.error('\nOptional environment variables:');
|
|
32
|
+
optional.forEach(({ name, description, defaultValue }) => {
|
|
33
|
+
const defaultStr = defaultValue ? ` (default: ${defaultValue})` : '';
|
|
34
|
+
console.error(` - ${name}: ${description}${defaultStr}`);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
console.error('\n----------------------------------------');
|
|
38
|
+
console.error('Please set the required environment variables and try again.');
|
|
39
|
+
console.error('\nTo get a bot token:');
|
|
40
|
+
console.error('1. Go to https://api.slack.com/apps');
|
|
41
|
+
console.error('2. Create or select your app');
|
|
42
|
+
console.error('3. Go to "OAuth & Permissions"');
|
|
43
|
+
console.error('4. Copy the "Bot User OAuth Token"');
|
|
44
|
+
console.error('----------------------------------------\n');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
// Validate token format
|
|
48
|
+
const token = process.env.SLACK_BOT_TOKEN;
|
|
49
|
+
if (!token.startsWith('xoxb-')) {
|
|
50
|
+
logWarning('validateEnvironment', 'SLACK_BOT_TOKEN should start with "xoxb-"');
|
|
51
|
+
}
|
|
52
|
+
// Log active tool groups if configured
|
|
53
|
+
if (process.env.ENABLED_TOOLGROUPS) {
|
|
54
|
+
logWarning('config', `Tool groups filter active: ${process.env.ENABLED_TOOLGROUPS}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// MAIN ENTRY POINT
|
|
59
|
+
// =============================================================================
|
|
60
|
+
async function main() {
|
|
61
|
+
// Step 1: Validate environment variables
|
|
62
|
+
validateEnvironment();
|
|
63
|
+
// Step 2: Create server using factory
|
|
64
|
+
const { server, registerHandlers } = createMCPServer();
|
|
65
|
+
// Step 3: Register all handlers (tools)
|
|
66
|
+
await registerHandlers(server);
|
|
67
|
+
// Step 4: Start server with stdio transport
|
|
68
|
+
const transport = new StdioServerTransport();
|
|
69
|
+
await server.connect(transport);
|
|
70
|
+
logServerStart('Slack');
|
|
71
|
+
}
|
|
72
|
+
// Run the server
|
|
73
|
+
main().catch((error) => {
|
|
74
|
+
logError('main', error);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "slack-workspace-mcp-server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "MCP server for Slack workspace integration",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"slack-workspace-mcp-server": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build/**/*.js",
|
|
12
|
+
"build/**/*.d.ts",
|
|
13
|
+
"shared/**/*.js",
|
|
14
|
+
"shared/**/*.d.ts",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc && npm run build:integration",
|
|
19
|
+
"build:integration": "tsc -p tsconfig.integration.json",
|
|
20
|
+
"start": "node build/index.js",
|
|
21
|
+
"dev": "tsx src/index.ts",
|
|
22
|
+
"predev": "cd ../shared && npm run build && cd ../local && node setup-dev.js",
|
|
23
|
+
"prebuild": "cd ../shared && npm run build && cd ../local && node setup-dev.js",
|
|
24
|
+
"prepublishOnly": "node prepare-publish.js && node ../scripts/prepare-npm-readme.js",
|
|
25
|
+
"lint": "eslint . --ext .ts,.tsx",
|
|
26
|
+
"lint:fix": "eslint . --ext .ts,.tsx --fix",
|
|
27
|
+
"format": "prettier --write .",
|
|
28
|
+
"format:check": "prettier --check .",
|
|
29
|
+
"stage-publish": "npm version"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
33
|
+
"zod": "^3.24.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.10.6",
|
|
37
|
+
"tsx": "^4.19.4",
|
|
38
|
+
"typescript": "^5.7.3"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"mcp",
|
|
42
|
+
"slack",
|
|
43
|
+
"model-context-protocol"
|
|
44
|
+
],
|
|
45
|
+
"author": "PulseMCP",
|
|
46
|
+
"license": "MIT"
|
|
47
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createMCPServer, SlackClient, type ISlackClient, type ClientFactory } from './server.js';
|
|
2
|
+
export { createRegisterTools, registerTools } from './tools.js';
|
|
3
|
+
export { logServerStart, logError, logWarning, logDebug } from './logging.js';
|
|
4
|
+
export type { Channel, Message, Reaction, Attachment, Block, ThreadWithReplies, User, PaginatedResponse, } from './types.js';
|
package/shared/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Main exports for Slack MCP Server
|
|
2
|
+
// Server and client
|
|
3
|
+
export { createMCPServer, SlackClient } from './server.js';
|
|
4
|
+
// Tools
|
|
5
|
+
export { createRegisterTools, registerTools } from './tools.js';
|
|
6
|
+
// Logging utilities
|
|
7
|
+
export { logServerStart, logError, logWarning, logDebug } from './logging.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utilities for Slack MCP Server
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: All logging uses console.error() to write to stderr.
|
|
5
|
+
* The MCP protocol requires stdout to contain only JSON messages.
|
|
6
|
+
*/
|
|
7
|
+
export declare function logServerStart(serverName: string, transport?: string): void;
|
|
8
|
+
export declare function logError(context: string, error: unknown): void;
|
|
9
|
+
export declare function logWarning(context: string, message: string): void;
|
|
10
|
+
export declare function logDebug(context: string, message: string): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utilities for Slack MCP Server
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: All logging uses console.error() to write to stderr.
|
|
5
|
+
* The MCP protocol requires stdout to contain only JSON messages.
|
|
6
|
+
*/
|
|
7
|
+
export function logServerStart(serverName, transport = 'stdio') {
|
|
8
|
+
console.error(`MCP server ${serverName} running on ${transport}`);
|
|
9
|
+
}
|
|
10
|
+
export function logError(context, error) {
|
|
11
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12
|
+
console.error(`[ERROR] ${context}: ${message}`);
|
|
13
|
+
}
|
|
14
|
+
export function logWarning(context, message) {
|
|
15
|
+
console.error(`[WARN] ${context}: ${message}`);
|
|
16
|
+
}
|
|
17
|
+
export function logDebug(context, message) {
|
|
18
|
+
if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
|
|
19
|
+
console.error(`[DEBUG] ${context}: ${message}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { Channel, Message } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Slack API client interface
|
|
5
|
+
* Defines all methods for interacting with the Slack Web API
|
|
6
|
+
*/
|
|
7
|
+
export interface ISlackClient {
|
|
8
|
+
/**
|
|
9
|
+
* Get all channels the bot has access to
|
|
10
|
+
*/
|
|
11
|
+
getChannels(options?: {
|
|
12
|
+
types?: string;
|
|
13
|
+
excludeArchived?: boolean;
|
|
14
|
+
}): Promise<Channel[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Get information about a specific channel
|
|
17
|
+
*/
|
|
18
|
+
getChannel(channelId: string): Promise<Channel>;
|
|
19
|
+
/**
|
|
20
|
+
* Get message history from a channel
|
|
21
|
+
*/
|
|
22
|
+
getMessages(channelId: string, options?: {
|
|
23
|
+
limit?: number;
|
|
24
|
+
cursor?: string;
|
|
25
|
+
oldest?: string;
|
|
26
|
+
latest?: string;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
messages: Message[];
|
|
29
|
+
hasMore: boolean;
|
|
30
|
+
nextCursor?: string;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Get all replies in a thread
|
|
34
|
+
*/
|
|
35
|
+
getThread(channelId: string, threadTs: string, options?: {
|
|
36
|
+
limit?: number;
|
|
37
|
+
cursor?: string;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
messages: Message[];
|
|
40
|
+
hasMore: boolean;
|
|
41
|
+
nextCursor?: string;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Post a new message to a channel
|
|
45
|
+
*/
|
|
46
|
+
postMessage(channelId: string, text: string, options?: {
|
|
47
|
+
threadTs?: string;
|
|
48
|
+
replyBroadcast?: boolean;
|
|
49
|
+
}): Promise<Message>;
|
|
50
|
+
/**
|
|
51
|
+
* Update an existing message
|
|
52
|
+
*/
|
|
53
|
+
updateMessage(channelId: string, ts: string, text: string): Promise<Message>;
|
|
54
|
+
/**
|
|
55
|
+
* Add a reaction to a message
|
|
56
|
+
*/
|
|
57
|
+
addReaction(channelId: string, timestamp: string, emoji: string): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Slack API client implementation
|
|
61
|
+
*/
|
|
62
|
+
export declare class SlackClient implements ISlackClient {
|
|
63
|
+
private botToken;
|
|
64
|
+
private baseUrl;
|
|
65
|
+
private headers;
|
|
66
|
+
constructor(botToken: string);
|
|
67
|
+
getChannels(options?: {
|
|
68
|
+
types?: string;
|
|
69
|
+
excludeArchived?: boolean;
|
|
70
|
+
}): Promise<Channel[]>;
|
|
71
|
+
getChannel(channelId: string): Promise<Channel>;
|
|
72
|
+
getMessages(channelId: string, options?: {
|
|
73
|
+
limit?: number;
|
|
74
|
+
cursor?: string;
|
|
75
|
+
oldest?: string;
|
|
76
|
+
latest?: string;
|
|
77
|
+
}): Promise<{
|
|
78
|
+
messages: Message[];
|
|
79
|
+
hasMore: boolean;
|
|
80
|
+
nextCursor?: string;
|
|
81
|
+
}>;
|
|
82
|
+
getThread(channelId: string, threadTs: string, options?: {
|
|
83
|
+
limit?: number;
|
|
84
|
+
cursor?: string;
|
|
85
|
+
}): Promise<{
|
|
86
|
+
messages: Message[];
|
|
87
|
+
hasMore: boolean;
|
|
88
|
+
nextCursor?: string;
|
|
89
|
+
}>;
|
|
90
|
+
postMessage(channelId: string, text: string, options?: {
|
|
91
|
+
threadTs?: string;
|
|
92
|
+
replyBroadcast?: boolean;
|
|
93
|
+
}): Promise<Message>;
|
|
94
|
+
updateMessage(channelId: string, ts: string, text: string): Promise<Message>;
|
|
95
|
+
addReaction(channelId: string, timestamp: string, emoji: string): Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
export type ClientFactory = () => ISlackClient;
|
|
98
|
+
export declare function createMCPServer(): {
|
|
99
|
+
server: Server<{
|
|
100
|
+
method: string;
|
|
101
|
+
params?: {
|
|
102
|
+
[x: string]: unknown;
|
|
103
|
+
_meta?: {
|
|
104
|
+
[x: string]: unknown;
|
|
105
|
+
progressToken?: string | number | undefined;
|
|
106
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
107
|
+
taskId: string;
|
|
108
|
+
} | undefined;
|
|
109
|
+
} | undefined;
|
|
110
|
+
} | undefined;
|
|
111
|
+
}, {
|
|
112
|
+
method: string;
|
|
113
|
+
params?: {
|
|
114
|
+
[x: string]: unknown;
|
|
115
|
+
_meta?: {
|
|
116
|
+
[x: string]: unknown;
|
|
117
|
+
progressToken?: string | number | undefined;
|
|
118
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
119
|
+
taskId: string;
|
|
120
|
+
} | undefined;
|
|
121
|
+
} | undefined;
|
|
122
|
+
} | undefined;
|
|
123
|
+
}, {
|
|
124
|
+
[x: string]: unknown;
|
|
125
|
+
_meta?: {
|
|
126
|
+
[x: string]: unknown;
|
|
127
|
+
progressToken?: string | number | undefined;
|
|
128
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
129
|
+
taskId: string;
|
|
130
|
+
} | undefined;
|
|
131
|
+
} | undefined;
|
|
132
|
+
}>;
|
|
133
|
+
registerHandlers: (server: Server, clientFactory?: ClientFactory) => Promise<void>;
|
|
134
|
+
};
|
package/shared/server.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { createRegisterTools } from './tools.js';
|
|
3
|
+
/**
|
|
4
|
+
* Slack API client implementation
|
|
5
|
+
*/
|
|
6
|
+
export class SlackClient {
|
|
7
|
+
botToken;
|
|
8
|
+
baseUrl = 'https://slack.com/api';
|
|
9
|
+
headers;
|
|
10
|
+
constructor(botToken) {
|
|
11
|
+
this.botToken = botToken;
|
|
12
|
+
this.headers = {
|
|
13
|
+
Authorization: `Bearer ${botToken}`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async getChannels(options) {
|
|
17
|
+
const { getChannels } = await import('./slack-client/lib/get-channels.js');
|
|
18
|
+
return getChannels(this.baseUrl, this.headers, options);
|
|
19
|
+
}
|
|
20
|
+
async getChannel(channelId) {
|
|
21
|
+
const { getChannel } = await import('./slack-client/lib/get-channel.js');
|
|
22
|
+
return getChannel(this.baseUrl, this.headers, channelId);
|
|
23
|
+
}
|
|
24
|
+
async getMessages(channelId, options) {
|
|
25
|
+
const { getMessages } = await import('./slack-client/lib/get-messages.js');
|
|
26
|
+
return getMessages(this.baseUrl, this.headers, channelId, options);
|
|
27
|
+
}
|
|
28
|
+
async getThread(channelId, threadTs, options) {
|
|
29
|
+
const { getThread } = await import('./slack-client/lib/get-thread.js');
|
|
30
|
+
return getThread(this.baseUrl, this.headers, channelId, threadTs, options);
|
|
31
|
+
}
|
|
32
|
+
async postMessage(channelId, text, options) {
|
|
33
|
+
const { postMessage } = await import('./slack-client/lib/post-message.js');
|
|
34
|
+
return postMessage(this.baseUrl, this.headers, channelId, text, options);
|
|
35
|
+
}
|
|
36
|
+
async updateMessage(channelId, ts, text) {
|
|
37
|
+
const { updateMessage } = await import('./slack-client/lib/update-message.js');
|
|
38
|
+
return updateMessage(this.baseUrl, this.headers, channelId, ts, text);
|
|
39
|
+
}
|
|
40
|
+
async addReaction(channelId, timestamp, emoji) {
|
|
41
|
+
const { addReaction } = await import('./slack-client/lib/add-reaction.js');
|
|
42
|
+
return addReaction(this.baseUrl, this.headers, channelId, timestamp, emoji);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function createMCPServer() {
|
|
46
|
+
const server = new Server({
|
|
47
|
+
name: 'slack-workspace-mcp-server',
|
|
48
|
+
version: '0.0.1',
|
|
49
|
+
}, {
|
|
50
|
+
capabilities: {
|
|
51
|
+
tools: {},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const registerHandlers = async (server, clientFactory) => {
|
|
55
|
+
// Use provided factory or create default client
|
|
56
|
+
const factory = clientFactory ||
|
|
57
|
+
(() => {
|
|
58
|
+
const botToken = process.env.SLACK_BOT_TOKEN;
|
|
59
|
+
if (!botToken) {
|
|
60
|
+
throw new Error('SLACK_BOT_TOKEN environment variable must be configured');
|
|
61
|
+
}
|
|
62
|
+
return new SlackClient(botToken);
|
|
63
|
+
});
|
|
64
|
+
const registerTools = createRegisterTools(factory);
|
|
65
|
+
registerTools(server);
|
|
66
|
+
};
|
|
67
|
+
return { server, registerHandlers };
|
|
68
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds a reaction (emoji) to a message
|
|
3
|
+
* @param name - The emoji name without colons (e.g., "thumbsup" not ":thumbsup:")
|
|
4
|
+
*/
|
|
5
|
+
export declare function addReaction(baseUrl: string, headers: Record<string, string>, channelId: string, timestamp: string, name: string): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds a reaction (emoji) to a message
|
|
3
|
+
* @param name - The emoji name without colons (e.g., "thumbsup" not ":thumbsup:")
|
|
4
|
+
*/
|
|
5
|
+
export async function addReaction(baseUrl, headers, channelId, timestamp, name) {
|
|
6
|
+
const body = {
|
|
7
|
+
channel: channelId,
|
|
8
|
+
timestamp,
|
|
9
|
+
name,
|
|
10
|
+
};
|
|
11
|
+
const response = await fetch(`${baseUrl}/reactions.add`, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: {
|
|
14
|
+
...headers,
|
|
15
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify(body),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new Error(`Failed to add reaction: ${response.status} ${response.statusText}`);
|
|
21
|
+
}
|
|
22
|
+
const data = (await response.json());
|
|
23
|
+
if (!data.ok) {
|
|
24
|
+
// already_reacted is not really an error for our purposes
|
|
25
|
+
if (data.error === 'already_reacted') {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Slack API error: ${data.error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches information about a specific channel
|
|
3
|
+
*/
|
|
4
|
+
export async function getChannel(baseUrl, headers, channelId) {
|
|
5
|
+
const params = new URLSearchParams({
|
|
6
|
+
channel: channelId,
|
|
7
|
+
include_num_members: 'true',
|
|
8
|
+
});
|
|
9
|
+
const response = await fetch(`${baseUrl}/conversations.info?${params}`, {
|
|
10
|
+
method: 'GET',
|
|
11
|
+
headers,
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error(`Failed to fetch channel: ${response.status} ${response.statusText}`);
|
|
15
|
+
}
|
|
16
|
+
const data = (await response.json());
|
|
17
|
+
if (!data.ok) {
|
|
18
|
+
throw new Error(`Slack API error: ${data.error}`);
|
|
19
|
+
}
|
|
20
|
+
if (!data.channel) {
|
|
21
|
+
throw new Error('Channel not found in response');
|
|
22
|
+
}
|
|
23
|
+
return data.channel;
|
|
24
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Channel } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fetches all channels the bot has access to
|
|
4
|
+
* Uses cursor-based pagination to get all results
|
|
5
|
+
*/
|
|
6
|
+
export declare function getChannels(baseUrl: string, headers: Record<string, string>, options?: {
|
|
7
|
+
types?: string;
|
|
8
|
+
excludeArchived?: boolean;
|
|
9
|
+
limit?: number;
|
|
10
|
+
}): Promise<Channel[]>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches all channels the bot has access to
|
|
3
|
+
* Uses cursor-based pagination to get all results
|
|
4
|
+
*/
|
|
5
|
+
export async function getChannels(baseUrl, headers, options) {
|
|
6
|
+
const allChannels = [];
|
|
7
|
+
let cursor;
|
|
8
|
+
const types = options?.types ?? 'public_channel,private_channel';
|
|
9
|
+
const excludeArchived = options?.excludeArchived ?? true;
|
|
10
|
+
const limit = options?.limit ?? 200;
|
|
11
|
+
do {
|
|
12
|
+
const params = new URLSearchParams({
|
|
13
|
+
types,
|
|
14
|
+
exclude_archived: excludeArchived.toString(),
|
|
15
|
+
limit: limit.toString(),
|
|
16
|
+
});
|
|
17
|
+
if (cursor) {
|
|
18
|
+
params.set('cursor', cursor);
|
|
19
|
+
}
|
|
20
|
+
const response = await fetch(`${baseUrl}/conversations.list?${params}`, {
|
|
21
|
+
method: 'GET',
|
|
22
|
+
headers,
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`Failed to fetch channels: ${response.status} ${response.statusText}`);
|
|
26
|
+
}
|
|
27
|
+
const data = (await response.json());
|
|
28
|
+
if (!data.ok) {
|
|
29
|
+
throw new Error(`Slack API error: ${data.error}`);
|
|
30
|
+
}
|
|
31
|
+
if (data.channels) {
|
|
32
|
+
allChannels.push(...data.channels);
|
|
33
|
+
}
|
|
34
|
+
cursor = data.response_metadata?.next_cursor;
|
|
35
|
+
} while (cursor);
|
|
36
|
+
return allChannels;
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Message } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fetches message history from a channel
|
|
4
|
+
* Returns messages in reverse chronological order (newest first)
|
|
5
|
+
*/
|
|
6
|
+
export declare function getMessages(baseUrl: string, headers: Record<string, string>, channelId: string, options?: {
|
|
7
|
+
limit?: number;
|
|
8
|
+
cursor?: string;
|
|
9
|
+
oldest?: string;
|
|
10
|
+
latest?: string;
|
|
11
|
+
inclusive?: boolean;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
messages: Message[];
|
|
14
|
+
hasMore: boolean;
|
|
15
|
+
nextCursor?: string;
|
|
16
|
+
}>;
|