@jtalk22/slack-mcp 1.0.0

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/src/server.js ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Slack MCP Server
4
+ *
5
+ * A Model Context Protocol server for Slack integration.
6
+ * Provides read/write access to Slack messages, channels, and users.
7
+ *
8
+ * @version 1.0.0
9
+ */
10
+
11
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import {
14
+ CallToolRequestSchema,
15
+ ListToolsRequestSchema,
16
+ } from "@modelcontextprotocol/sdk/types.js";
17
+
18
+ import { loadTokens } from "../lib/token-store.js";
19
+ import { TOOLS } from "../lib/tools.js";
20
+ import {
21
+ handleHealthCheck,
22
+ handleRefreshTokens,
23
+ handleListConversations,
24
+ handleConversationsHistory,
25
+ handleGetFullConversation,
26
+ handleSearchMessages,
27
+ handleUsersInfo,
28
+ handleSendMessage,
29
+ handleGetThread,
30
+ handleListUsers,
31
+ } from "../lib/handlers.js";
32
+
33
+ // Package info
34
+ const SERVER_NAME = "slack-mcp-server";
35
+ const SERVER_VERSION = "1.0.0";
36
+
37
+ // Initialize server
38
+ const server = new Server(
39
+ { name: SERVER_NAME, version: SERVER_VERSION },
40
+ { capabilities: { tools: {} } }
41
+ );
42
+
43
+ // Register tool list handler
44
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
45
+ tools: TOOLS
46
+ }));
47
+
48
+ // Register tool call handler
49
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
50
+ const { name, arguments: args } = request.params;
51
+
52
+ try {
53
+ switch (name) {
54
+ case "slack_health_check":
55
+ return await handleHealthCheck();
56
+
57
+ case "slack_refresh_tokens":
58
+ return await handleRefreshTokens();
59
+
60
+ case "slack_list_conversations":
61
+ return await handleListConversations(args);
62
+
63
+ case "slack_conversations_history":
64
+ return await handleConversationsHistory(args);
65
+
66
+ case "slack_get_full_conversation":
67
+ return await handleGetFullConversation(args);
68
+
69
+ case "slack_search_messages":
70
+ return await handleSearchMessages(args);
71
+
72
+ case "slack_users_info":
73
+ return await handleUsersInfo(args);
74
+
75
+ case "slack_send_message":
76
+ return await handleSendMessage(args);
77
+
78
+ case "slack_get_thread":
79
+ return await handleGetThread(args);
80
+
81
+ case "slack_list_users":
82
+ return await handleListUsers(args);
83
+
84
+ default:
85
+ return {
86
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
87
+ isError: true
88
+ };
89
+ }
90
+ } catch (error) {
91
+ return {
92
+ content: [{ type: "text", text: `Error: ${error.message}` }],
93
+ isError: true
94
+ };
95
+ }
96
+ });
97
+
98
+ // Main entry point
99
+ async function main() {
100
+ // Check for credentials at startup
101
+ const credentials = loadTokens();
102
+ if (!credentials) {
103
+ console.error("WARNING: No Slack credentials found at startup");
104
+ console.error("Will attempt Chrome auto-extraction on first API call");
105
+ } else {
106
+ console.error(`Credentials loaded from: ${credentials.source}`);
107
+ }
108
+
109
+ // Start server
110
+ const transport = new StdioServerTransport();
111
+ await server.connect(transport);
112
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} running`);
113
+ }
114
+
115
+ main().catch(error => {
116
+ console.error("Fatal error:", error);
117
+ process.exit(1);
118
+ });
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Slack Web API Server
4
+ *
5
+ * Exposes Slack MCP tools as REST endpoints for browser access.
6
+ * Run alongside or instead of the MCP server for web-based access.
7
+ *
8
+ * @version 1.0.0
9
+ */
10
+
11
+ import express from "express";
12
+ import { fileURLToPath } from "url";
13
+ import { dirname, join } from "path";
14
+ import { loadTokens } from "../lib/token-store.js";
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ import {
18
+ handleHealthCheck,
19
+ handleRefreshTokens,
20
+ handleListConversations,
21
+ handleConversationsHistory,
22
+ handleGetFullConversation,
23
+ handleSearchMessages,
24
+ handleUsersInfo,
25
+ handleSendMessage,
26
+ handleGetThread,
27
+ handleListUsers,
28
+ } from "../lib/handlers.js";
29
+
30
+ const app = express();
31
+ const PORT = process.env.PORT || 3000;
32
+
33
+ // Default API key for convenience - override with SLACK_API_KEY env var for production
34
+ const DEFAULT_API_KEY = "slack-mcp-local";
35
+ const API_KEY = process.env.SLACK_API_KEY || DEFAULT_API_KEY;
36
+
37
+ // Middleware
38
+ app.use(express.json());
39
+ app.use(express.static(join(__dirname, "../public")));
40
+
41
+ // CORS for local development
42
+ app.use((req, res, next) => {
43
+ res.header("Access-Control-Allow-Origin", "*");
44
+ res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
45
+ res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
46
+ if (req.method === "OPTIONS") {
47
+ return res.sendStatus(200);
48
+ }
49
+ next();
50
+ });
51
+
52
+ // API Key authentication
53
+ function authenticate(req, res, next) {
54
+ const authHeader = req.headers.authorization;
55
+ const providedKey = authHeader?.replace("Bearer ", "");
56
+
57
+ if (providedKey !== API_KEY) {
58
+ return res.status(401).json({ error: "Invalid API key" });
59
+ }
60
+ next();
61
+ }
62
+
63
+ // Helper to extract response content
64
+ function extractContent(result) {
65
+ if (result.content && result.content[0] && result.content[0].text) {
66
+ try {
67
+ return JSON.parse(result.content[0].text);
68
+ } catch {
69
+ return { text: result.content[0].text };
70
+ }
71
+ }
72
+ return result;
73
+ }
74
+
75
+ // Root endpoint (no auth required)
76
+ app.get("/", (req, res) => {
77
+ res.json({
78
+ name: "Slack Web API Server",
79
+ version: "1.0.0",
80
+ status: "running",
81
+ endpoints: [
82
+ "GET /health",
83
+ "POST /refresh",
84
+ "GET /conversations",
85
+ "GET /conversations/:id/history",
86
+ "GET /conversations/:id/full",
87
+ "GET /conversations/:id/thread/:ts",
88
+ "GET /search",
89
+ "POST /messages",
90
+ "GET /users",
91
+ "GET /users/:id",
92
+ ],
93
+ docs: "Add Authorization: Bearer <api-key> header to all requests"
94
+ });
95
+ });
96
+
97
+ // Health check
98
+ app.get("/health", authenticate, async (req, res) => {
99
+ try {
100
+ const result = await handleHealthCheck();
101
+ res.json(extractContent(result));
102
+ } catch (e) {
103
+ res.status(500).json({ error: e.message });
104
+ }
105
+ });
106
+
107
+ // Refresh tokens
108
+ app.post("/refresh", authenticate, async (req, res) => {
109
+ try {
110
+ const result = await handleRefreshTokens();
111
+ res.json(extractContent(result));
112
+ } catch (e) {
113
+ res.status(500).json({ error: e.message });
114
+ }
115
+ });
116
+
117
+ // List conversations
118
+ app.get("/conversations", authenticate, async (req, res) => {
119
+ try {
120
+ const result = await handleListConversations({
121
+ types: req.query.types || "im,mpim",
122
+ limit: parseInt(req.query.limit) || 100
123
+ });
124
+ res.json(extractContent(result));
125
+ } catch (e) {
126
+ res.status(500).json({ error: e.message });
127
+ }
128
+ });
129
+
130
+ // Get conversation history
131
+ app.get("/conversations/:id/history", authenticate, async (req, res) => {
132
+ try {
133
+ const result = await handleConversationsHistory({
134
+ channel_id: req.params.id,
135
+ limit: parseInt(req.query.limit) || 50,
136
+ oldest: req.query.oldest,
137
+ latest: req.query.latest,
138
+ resolve_users: req.query.resolve_users !== "false"
139
+ });
140
+ res.json(extractContent(result));
141
+ } catch (e) {
142
+ res.status(500).json({ error: e.message });
143
+ }
144
+ });
145
+
146
+ // Get full conversation with threads
147
+ app.get("/conversations/:id/full", authenticate, async (req, res) => {
148
+ try {
149
+ const result = await handleGetFullConversation({
150
+ channel_id: req.params.id,
151
+ oldest: req.query.oldest,
152
+ latest: req.query.latest,
153
+ max_messages: parseInt(req.query.max_messages) || 2000,
154
+ include_threads: req.query.include_threads !== "false",
155
+ output_file: req.query.output_file
156
+ });
157
+ res.json(extractContent(result));
158
+ } catch (e) {
159
+ res.status(500).json({ error: e.message });
160
+ }
161
+ });
162
+
163
+ // Get thread
164
+ app.get("/conversations/:id/thread/:ts", authenticate, async (req, res) => {
165
+ try {
166
+ const result = await handleGetThread({
167
+ channel_id: req.params.id,
168
+ thread_ts: req.params.ts
169
+ });
170
+ res.json(extractContent(result));
171
+ } catch (e) {
172
+ res.status(500).json({ error: e.message });
173
+ }
174
+ });
175
+
176
+ // Search messages
177
+ app.get("/search", authenticate, async (req, res) => {
178
+ try {
179
+ if (!req.query.q) {
180
+ return res.status(400).json({ error: "Query parameter 'q' is required" });
181
+ }
182
+ const result = await handleSearchMessages({
183
+ query: req.query.q,
184
+ count: parseInt(req.query.count) || 20
185
+ });
186
+ res.json(extractContent(result));
187
+ } catch (e) {
188
+ res.status(500).json({ error: e.message });
189
+ }
190
+ });
191
+
192
+ // Send message
193
+ app.post("/messages", authenticate, async (req, res) => {
194
+ try {
195
+ if (!req.body.channel_id || !req.body.text) {
196
+ return res.status(400).json({ error: "channel_id and text are required" });
197
+ }
198
+ const result = await handleSendMessage({
199
+ channel_id: req.body.channel_id,
200
+ text: req.body.text,
201
+ thread_ts: req.body.thread_ts
202
+ });
203
+ res.json(extractContent(result));
204
+ } catch (e) {
205
+ res.status(500).json({ error: e.message });
206
+ }
207
+ });
208
+
209
+ // List users
210
+ app.get("/users", authenticate, async (req, res) => {
211
+ try {
212
+ const result = await handleListUsers({
213
+ limit: parseInt(req.query.limit) || 100
214
+ });
215
+ res.json(extractContent(result));
216
+ } catch (e) {
217
+ res.status(500).json({ error: e.message });
218
+ }
219
+ });
220
+
221
+ // Get user info
222
+ app.get("/users/:id", authenticate, async (req, res) => {
223
+ try {
224
+ const result = await handleUsersInfo({
225
+ user_id: req.params.id
226
+ });
227
+ res.json(extractContent(result));
228
+ } catch (e) {
229
+ res.status(500).json({ error: e.message });
230
+ }
231
+ });
232
+
233
+ // Start server
234
+ async function main() {
235
+ // Check for credentials
236
+ const credentials = loadTokens();
237
+ if (!credentials) {
238
+ console.error("WARNING: No Slack credentials found");
239
+ console.error("Run: npm run tokens:auto");
240
+ } else {
241
+ console.log(`Credentials loaded from: ${credentials.source}`);
242
+ }
243
+
244
+ app.listen(PORT, () => {
245
+ console.log(`\nšŸš€ Slack Web API Server running at http://localhost:${PORT}`);
246
+ console.log(`\nšŸ”‘ API Key: ${API_KEY}`);
247
+ console.log(`\nExample usage:`);
248
+ console.log(` curl -H "Authorization: Bearer ${API_KEY}" http://localhost:${PORT}/health`);
249
+ console.log(` curl -H "Authorization: Bearer ${API_KEY}" http://localhost:${PORT}/conversations`);
250
+ });
251
+ }
252
+
253
+ main().catch(e => {
254
+ console.error("Fatal error:", e);
255
+ process.exit(1);
256
+ });