@goforgeit/mcp-slack 0.4.11

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.
@@ -0,0 +1,30 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+
3
+ /**
4
+ * @goforgeit/mcp-slack — MCP server for Slack
5
+ *
6
+ * Provides channel, message, and user tools via stdio transport.
7
+ * Reads Forge's OAuth tokens and uses Slack Web API.
8
+ */
9
+
10
+ interface SlackMCPOptions {
11
+ tokenPath: string;
12
+ clientId: string;
13
+ clientSecret: string;
14
+ }
15
+ interface ToolHandler {
16
+ handler: (args: any) => Promise<{
17
+ content: Array<{
18
+ type: 'text';
19
+ text: string;
20
+ }>;
21
+ }>;
22
+ }
23
+ interface SlackMCPServer {
24
+ server: McpServer;
25
+ getRegisteredTools(): Record<string, ToolHandler>;
26
+ start(): Promise<void>;
27
+ }
28
+ declare function createSlackMCPServer(options: SlackMCPOptions): SlackMCPServer;
29
+
30
+ export { type SlackMCPOptions, createSlackMCPServer };
package/dist/index.js ADDED
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z as z2 } from "zod";
7
+ import { WebClient } from "@slack/web-api";
8
+
9
+ // ../shared/dist/mcp-auth/forge-token-manager.js
10
+ import * as fs from "fs/promises";
11
+ import * as path from "path";
12
+ import { z } from "zod";
13
+ var ForgeOAuthTokensSchema = z.object({
14
+ accessToken: z.string(),
15
+ refreshToken: z.string().optional(),
16
+ expiresAt: z.number(),
17
+ providerId: z.string(),
18
+ connectedAt: z.string(),
19
+ scope: z.string().optional(),
20
+ email: z.string().optional(),
21
+ accountId: z.string().optional()
22
+ });
23
+ var DEFAULT_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
24
+ function createForgeTokenManager(options) {
25
+ const { tokenPath, clientId, clientSecret, tokenUrl, refreshBufferMs = DEFAULT_REFRESH_BUFFER_MS } = options;
26
+ let cachedTokens = null;
27
+ const refreshCallbacks = [];
28
+ function isExpiredOrNearExpiry(tokens) {
29
+ return Date.now() >= tokens.expiresAt - refreshBufferMs;
30
+ }
31
+ async function readTokenFile() {
32
+ let raw;
33
+ try {
34
+ raw = await fs.readFile(tokenPath, "utf-8");
35
+ } catch (err) {
36
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
37
+ throw new Error(`Token file not found: ${tokenPath}`);
38
+ }
39
+ throw err;
40
+ }
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(raw);
44
+ } catch {
45
+ throw new Error(`Invalid JSON in token file: ${tokenPath}`);
46
+ }
47
+ const result = ForgeOAuthTokensSchema.safeParse(parsed);
48
+ if (!result.success) {
49
+ throw new Error(`Invalid token file format: ${result.error.message}`);
50
+ }
51
+ return result.data;
52
+ }
53
+ async function refreshToken(tokens) {
54
+ if (!tokens.refreshToken) {
55
+ throw new Error("No refresh token available \u2014 cannot refresh expired token");
56
+ }
57
+ const body = new URLSearchParams({
58
+ grant_type: "refresh_token",
59
+ refresh_token: tokens.refreshToken,
60
+ client_id: clientId,
61
+ client_secret: clientSecret
62
+ });
63
+ const response = await fetch(tokenUrl, {
64
+ method: "POST",
65
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
66
+ body: body.toString()
67
+ });
68
+ if (!response.ok) {
69
+ const errorText = await response.text();
70
+ throw new Error(`Token refresh failed (${response.status}): ${errorText}`);
71
+ }
72
+ const data = await response.json();
73
+ const refreshedTokens = {
74
+ ...tokens,
75
+ accessToken: data.access_token,
76
+ refreshToken: data.refresh_token || tokens.refreshToken,
77
+ expiresAt: Date.now() + data.expires_in * 1e3
78
+ };
79
+ await fs.mkdir(path.dirname(tokenPath), { recursive: true });
80
+ await fs.writeFile(tokenPath, JSON.stringify(refreshedTokens), { mode: 384 });
81
+ for (const cb of refreshCallbacks) {
82
+ cb(refreshedTokens);
83
+ }
84
+ return refreshedTokens;
85
+ }
86
+ return {
87
+ async getAccessToken() {
88
+ if (cachedTokens && !isExpiredOrNearExpiry(cachedTokens)) {
89
+ return cachedTokens.accessToken;
90
+ }
91
+ const tokens = cachedTokens || await readTokenFile();
92
+ if (isExpiredOrNearExpiry(tokens)) {
93
+ if (tokens.refreshToken) {
94
+ cachedTokens = await refreshToken(tokens);
95
+ } else {
96
+ cachedTokens = tokens;
97
+ }
98
+ } else {
99
+ cachedTokens = tokens;
100
+ }
101
+ return cachedTokens.accessToken;
102
+ },
103
+ getTokenInfo() {
104
+ return cachedTokens;
105
+ },
106
+ onRefresh(callback) {
107
+ refreshCallbacks.push(callback);
108
+ }
109
+ };
110
+ }
111
+
112
+ // src/index.ts
113
+ var SLACK_TOKEN_URL = "https://slack.com/api/oauth.v2.access";
114
+ function createSlackMCPServer(options) {
115
+ const tokenManager = createForgeTokenManager({
116
+ tokenPath: options.tokenPath,
117
+ clientId: options.clientId,
118
+ clientSecret: options.clientSecret,
119
+ tokenUrl: SLACK_TOKEN_URL
120
+ });
121
+ const server = new McpServer({
122
+ name: "@goforgeit/mcp-slack",
123
+ version: "0.1.0"
124
+ });
125
+ const registeredTools = {};
126
+ function registerTool(name, description, schema, handler) {
127
+ registeredTools[name] = { handler };
128
+ server.tool(name, description, schema, handler);
129
+ }
130
+ async function getClient() {
131
+ const token = await tokenManager.getAccessToken();
132
+ return new WebClient(token);
133
+ }
134
+ registerTool(
135
+ "list_channels",
136
+ "List Slack channels the bot has access to",
137
+ {
138
+ types: z2.string().optional().default("public_channel").describe("Channel types (comma-separated: public_channel, private_channel, mpim, im)"),
139
+ limit: z2.number().optional().default(100).describe("Maximum channels to return")
140
+ },
141
+ async (args) => {
142
+ const client = await getClient();
143
+ const result = await client.conversations.list({
144
+ types: args.types || "public_channel",
145
+ limit: args.limit || 100
146
+ });
147
+ const channels = (result.channels || []).map((ch) => ({
148
+ id: ch.id,
149
+ name: ch.name,
150
+ topic: ch.topic?.value,
151
+ num_members: ch.num_members
152
+ }));
153
+ return {
154
+ content: [{ type: "text", text: JSON.stringify({ channels }) }]
155
+ };
156
+ }
157
+ );
158
+ registerTool(
159
+ "get_channel_history",
160
+ "Get message history from a Slack channel",
161
+ {
162
+ channelId: z2.string().describe("Slack channel ID"),
163
+ limit: z2.number().optional().default(20).describe("Number of messages to fetch"),
164
+ oldest: z2.string().optional().describe("Start of time range (Unix timestamp)"),
165
+ latest: z2.string().optional().describe("End of time range (Unix timestamp)")
166
+ },
167
+ async (args) => {
168
+ const client = await getClient();
169
+ const result = await client.conversations.history({
170
+ channel: args.channelId,
171
+ limit: args.limit || 20,
172
+ oldest: args.oldest,
173
+ latest: args.latest
174
+ });
175
+ const messages = (result.messages || []).map((msg) => ({
176
+ ts: msg.ts,
177
+ text: msg.text,
178
+ user: msg.user,
179
+ type: msg.type
180
+ }));
181
+ return {
182
+ content: [{ type: "text", text: JSON.stringify({ messages }) }]
183
+ };
184
+ }
185
+ );
186
+ registerTool(
187
+ "search_messages",
188
+ "Search Slack messages across all channels",
189
+ {
190
+ query: z2.string().describe("Search query"),
191
+ sort: z2.enum(["score", "timestamp"]).optional().default("score").describe("Sort order"),
192
+ count: z2.number().optional().default(20).describe("Number of results")
193
+ },
194
+ async (args) => {
195
+ const client = await getClient();
196
+ const result = await client.search.messages({
197
+ query: args.query,
198
+ sort: args.sort || "score",
199
+ count: args.count || 20
200
+ });
201
+ const matches = result.messages?.matches || [];
202
+ return {
203
+ content: [
204
+ {
205
+ type: "text",
206
+ text: JSON.stringify({
207
+ matches: matches.map((m) => ({
208
+ ts: m.ts,
209
+ text: m.text,
210
+ channel: m.channel,
211
+ user: m.user
212
+ })),
213
+ total: result.messages?.total || 0
214
+ })
215
+ }
216
+ ]
217
+ };
218
+ }
219
+ );
220
+ registerTool(
221
+ "list_users",
222
+ "List Slack workspace members",
223
+ {
224
+ limit: z2.number().optional().default(100).describe("Maximum users to return")
225
+ },
226
+ async (args) => {
227
+ const client = await getClient();
228
+ const result = await client.users.list({
229
+ limit: args.limit || 100
230
+ });
231
+ const members = (result.members || []).map((m) => ({
232
+ id: m.id,
233
+ name: m.name,
234
+ real_name: m.real_name,
235
+ is_bot: m.is_bot
236
+ }));
237
+ return {
238
+ content: [{ type: "text", text: JSON.stringify({ members }) }]
239
+ };
240
+ }
241
+ );
242
+ registerTool(
243
+ "get_user_info",
244
+ "Get detailed information about a Slack user",
245
+ {
246
+ userId: z2.string().describe("Slack user ID")
247
+ },
248
+ async (args) => {
249
+ const client = await getClient();
250
+ const result = await client.users.info({
251
+ user: args.userId
252
+ });
253
+ const user = result.user;
254
+ return {
255
+ content: [
256
+ {
257
+ type: "text",
258
+ text: JSON.stringify({
259
+ id: user?.id,
260
+ name: user?.name,
261
+ real_name: user?.real_name,
262
+ profile: user?.profile,
263
+ is_bot: user?.is_bot,
264
+ tz: user?.tz
265
+ })
266
+ }
267
+ ]
268
+ };
269
+ }
270
+ );
271
+ return {
272
+ server,
273
+ getRegisteredTools() {
274
+ return registeredTools;
275
+ },
276
+ async start() {
277
+ const transport = new StdioServerTransport();
278
+ await server.connect(transport);
279
+ }
280
+ };
281
+ }
282
+ var isMainModule = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"));
283
+ if (isMainModule || process.env.FORGE_MCP_START === "true") {
284
+ const tokenPath = process.env.FORGE_TOKEN_PATH;
285
+ const clientId = process.env.FORGE_CLIENT_ID;
286
+ const clientSecret = process.env.FORGE_CLIENT_SECRET;
287
+ if (!tokenPath || !clientId || !clientSecret) {
288
+ console.error(
289
+ "Required environment variables: FORGE_TOKEN_PATH, FORGE_CLIENT_ID, FORGE_CLIENT_SECRET"
290
+ );
291
+ process.exit(1);
292
+ }
293
+ const mcpServer = createSlackMCPServer({ tokenPath, clientId, clientSecret });
294
+ mcpServer.start().catch((err) => {
295
+ console.error("Failed to start MCP server:", err);
296
+ process.exit(1);
297
+ });
298
+ }
299
+ export {
300
+ createSlackMCPServer
301
+ };
302
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../../shared/src/mcp-auth/forge-token-manager.ts"],"sourcesContent":["/**\n * @goforgeit/mcp-slack — MCP server for Slack\n *\n * Provides channel, message, and user tools via stdio transport.\n * Reads Forge's OAuth tokens and uses Slack Web API.\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport { WebClient } from '@slack/web-api';\nimport { createForgeTokenManager } from '@forge/shared/mcp-auth';\n\nconst SLACK_TOKEN_URL = 'https://slack.com/api/oauth.v2.access';\n\nexport interface SlackMCPOptions {\n tokenPath: string;\n clientId: string;\n clientSecret: string;\n}\n\ninterface ToolHandler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (args: any) => Promise<{ content: Array<{ type: 'text'; text: string }> }>;\n}\n\ninterface SlackMCPServer {\n server: McpServer;\n getRegisteredTools(): Record<string, ToolHandler>;\n start(): Promise<void>;\n}\n\nexport function createSlackMCPServer(options: SlackMCPOptions): SlackMCPServer {\n const tokenManager = createForgeTokenManager({\n tokenPath: options.tokenPath,\n clientId: options.clientId,\n clientSecret: options.clientSecret,\n tokenUrl: SLACK_TOKEN_URL,\n });\n\n const server = new McpServer({\n name: '@goforgeit/mcp-slack',\n version: '0.1.0',\n });\n\n const registeredTools: Record<string, ToolHandler> = {};\n\n function registerTool(\n name: string,\n description: string,\n schema: Record<string, z.ZodType>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (args: any) => Promise<{ content: Array<{ type: 'text'; text: string }> }>,\n ) {\n registeredTools[name] = { handler };\n server.tool(name, description, schema, handler);\n }\n\n async function getClient(): Promise<WebClient> {\n const token = await tokenManager.getAccessToken();\n return new WebClient(token);\n }\n\n // --- Channel Tools ---\n\n registerTool(\n 'list_channels',\n 'List Slack channels the bot has access to',\n {\n types: z\n .string()\n .optional()\n .default('public_channel')\n .describe('Channel types (comma-separated: public_channel, private_channel, mpim, im)'),\n limit: z.number().optional().default(100).describe('Maximum channels to return'),\n },\n async (args: { types?: string; limit?: number }) => {\n const client = await getClient();\n const result = await client.conversations.list({\n types: args.types || 'public_channel',\n limit: args.limit || 100,\n });\n\n const channels = (result.channels || []).map((ch) => ({\n id: ch.id,\n name: ch.name,\n topic: ch.topic?.value,\n num_members: ch.num_members,\n }));\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ channels }) }],\n };\n },\n );\n\n registerTool(\n 'get_channel_history',\n 'Get message history from a Slack channel',\n {\n channelId: z.string().describe('Slack channel ID'),\n limit: z.number().optional().default(20).describe('Number of messages to fetch'),\n oldest: z.string().optional().describe('Start of time range (Unix timestamp)'),\n latest: z.string().optional().describe('End of time range (Unix timestamp)'),\n },\n async (args: { channelId: string; limit?: number; oldest?: string; latest?: string }) => {\n const client = await getClient();\n const result = await client.conversations.history({\n channel: args.channelId,\n limit: args.limit || 20,\n oldest: args.oldest,\n latest: args.latest,\n });\n\n const messages = (result.messages || []).map((msg) => ({\n ts: msg.ts,\n text: msg.text,\n user: msg.user,\n type: msg.type,\n }));\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ messages }) }],\n };\n },\n );\n\n // --- Message Tools ---\n\n registerTool(\n 'search_messages',\n 'Search Slack messages across all channels',\n {\n query: z.string().describe('Search query'),\n sort: z.enum(['score', 'timestamp']).optional().default('score').describe('Sort order'),\n count: z.number().optional().default(20).describe('Number of results'),\n },\n async (args: { query: string; sort?: 'score' | 'timestamp'; count?: number }) => {\n const client = await getClient();\n const result = await client.search.messages({\n query: args.query,\n sort: args.sort || 'score',\n count: args.count || 20,\n });\n\n const matches = result.messages?.matches || [];\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n matches: matches.map((m) => ({\n ts: m.ts,\n text: m.text,\n channel: m.channel,\n user: m.user,\n })),\n total: result.messages?.total || 0,\n }),\n },\n ],\n };\n },\n );\n\n // --- User Tools ---\n\n registerTool(\n 'list_users',\n 'List Slack workspace members',\n {\n limit: z.number().optional().default(100).describe('Maximum users to return'),\n },\n async (args: { limit?: number }) => {\n const client = await getClient();\n const result = await client.users.list({\n limit: args.limit || 100,\n });\n\n const members = (result.members || []).map((m) => ({\n id: m.id,\n name: m.name,\n real_name: m.real_name,\n is_bot: m.is_bot,\n }));\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ members }) }],\n };\n },\n );\n\n registerTool(\n 'get_user_info',\n 'Get detailed information about a Slack user',\n {\n userId: z.string().describe('Slack user ID'),\n },\n async (args: { userId: string }) => {\n const client = await getClient();\n const result = await client.users.info({\n user: args.userId,\n });\n\n const user = result.user;\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n id: user?.id,\n name: user?.name,\n real_name: user?.real_name,\n profile: user?.profile,\n is_bot: user?.is_bot,\n tz: user?.tz,\n }),\n },\n ],\n };\n },\n );\n\n return {\n server,\n getRegisteredTools() {\n return registeredTools;\n },\n async start() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n },\n };\n}\n\n// CLI entry point\nconst isMainModule =\n process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\\\/g, '/'));\nif (isMainModule || process.env.FORGE_MCP_START === 'true') {\n const tokenPath = process.env.FORGE_TOKEN_PATH;\n const clientId = process.env.FORGE_CLIENT_ID;\n const clientSecret = process.env.FORGE_CLIENT_SECRET;\n\n if (!tokenPath || !clientId || !clientSecret) {\n console.error(\n 'Required environment variables: FORGE_TOKEN_PATH, FORGE_CLIENT_ID, FORGE_CLIENT_SECRET',\n );\n process.exit(1);\n }\n\n const mcpServer = createSlackMCPServer({ tokenPath, clientId, clientSecret });\n mcpServer.start().catch((err) => {\n console.error('Failed to start MCP server:', err);\n process.exit(1);\n });\n}\n","/**\n * Forge Token Manager — reads Forge's OAuthTokens format and handles refresh.\n *\n * Used by @forge/mcp-* packages to get valid access tokens for API calls.\n * Reads from FORGE_TOKEN_PATH, validates with Zod, and refreshes when\n * the token is expired or within 5 minutes of expiry.\n */\n\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\n/** Zod schema for Forge's OAuth token file format */\nexport const ForgeOAuthTokensSchema = z.object({\n accessToken: z.string(),\n refreshToken: z.string().optional(),\n expiresAt: z.number(),\n providerId: z.string(),\n connectedAt: z.string(),\n scope: z.string().optional(),\n email: z.string().optional(),\n accountId: z.string().optional(),\n});\n\nexport type ForgeOAuthTokens = z.infer<typeof ForgeOAuthTokensSchema>;\n\nexport interface ForgeTokenManagerOptions {\n /** Path to the Forge OAuth token JSON file */\n tokenPath: string;\n /** OAuth client ID for token refresh */\n clientId: string;\n /** OAuth client secret for token refresh */\n clientSecret: string;\n /** OAuth token endpoint URL for refresh requests */\n tokenUrl: string;\n /** Buffer in ms before expiry to trigger refresh (default: 5 minutes) */\n refreshBufferMs?: number;\n}\n\nexport interface ForgeTokenManager {\n /** Returns a valid access token, refreshing if needed */\n getAccessToken(): Promise<string>;\n /** Returns the current token data, or null if not yet loaded */\n getTokenInfo(): ForgeOAuthTokens | null;\n /** Register a callback for when tokens are refreshed */\n onRefresh(callback: (tokens: ForgeOAuthTokens) => void): void;\n}\n\nconst DEFAULT_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes\n\nexport function createForgeTokenManager(options: ForgeTokenManagerOptions): ForgeTokenManager {\n const {\n tokenPath,\n clientId,\n clientSecret,\n tokenUrl,\n refreshBufferMs = DEFAULT_REFRESH_BUFFER_MS,\n } = options;\n\n let cachedTokens: ForgeOAuthTokens | null = null;\n const refreshCallbacks: Array<(tokens: ForgeOAuthTokens) => void> = [];\n\n function isExpiredOrNearExpiry(tokens: ForgeOAuthTokens): boolean {\n return Date.now() >= tokens.expiresAt - refreshBufferMs;\n }\n\n async function readTokenFile(): Promise<ForgeOAuthTokens> {\n let raw: string;\n try {\n raw = await fs.readFile(tokenPath, 'utf-8');\n } catch (err: unknown) {\n if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {\n throw new Error(`Token file not found: ${tokenPath}`);\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON in token file: ${tokenPath}`);\n }\n\n const result = ForgeOAuthTokensSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(`Invalid token file format: ${result.error.message}`);\n }\n\n return result.data;\n }\n\n async function refreshToken(tokens: ForgeOAuthTokens): Promise<ForgeOAuthTokens> {\n if (!tokens.refreshToken) {\n throw new Error('No refresh token available — cannot refresh expired token');\n }\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: tokens.refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n });\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed (${response.status}): ${errorText}`);\n }\n\n const data = await response.json();\n\n const refreshedTokens: ForgeOAuthTokens = {\n ...tokens,\n accessToken: data.access_token,\n refreshToken: data.refresh_token || tokens.refreshToken,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n\n // Write refreshed tokens back to file\n await fs.mkdir(path.dirname(tokenPath), { recursive: true });\n await fs.writeFile(tokenPath, JSON.stringify(refreshedTokens), { mode: 0o600 });\n\n // Notify listeners\n for (const cb of refreshCallbacks) {\n cb(refreshedTokens);\n }\n\n return refreshedTokens;\n }\n\n return {\n async getAccessToken(): Promise<string> {\n if (cachedTokens && !isExpiredOrNearExpiry(cachedTokens)) {\n return cachedTokens.accessToken;\n }\n\n const tokens = cachedTokens || (await readTokenFile());\n\n if (isExpiredOrNearExpiry(tokens)) {\n if (tokens.refreshToken) {\n cachedTokens = await refreshToken(tokens);\n } else {\n // No refresh token — return the access token as-is.\n // Some providers (e.g. Slack bot tokens) issue non-expiring tokens\n // without refresh tokens, so the expiresAt may be unreliable.\n cachedTokens = tokens;\n }\n } else {\n cachedTokens = tokens;\n }\n\n return cachedTokens.accessToken;\n },\n\n getTokenInfo(): ForgeOAuthTokens | null {\n return cachedTokens;\n },\n\n onRefresh(callback: (tokens: ForgeOAuthTokens) => void): void {\n refreshCallbacks.push(callback);\n },\n };\n}\n\n/**\n * Create a ForgeTokenManager from standard environment variables.\n *\n * Expected env vars:\n * - FORGE_TOKEN_PATH: path to the OAuth token JSON file\n * - FORGE_CLIENT_ID: OAuth client ID\n * - FORGE_CLIENT_SECRET: OAuth client secret\n *\n * @param tokenUrl The provider's token endpoint URL\n */\nexport function createForgeTokenManagerFromEnv(tokenUrl: string): ForgeTokenManager {\n const tokenPath = process.env.FORGE_TOKEN_PATH;\n const clientId = process.env.FORGE_CLIENT_ID;\n const clientSecret = process.env.FORGE_CLIENT_SECRET;\n\n if (!tokenPath) throw new Error('FORGE_TOKEN_PATH environment variable is required');\n if (!clientId) throw new Error('FORGE_CLIENT_ID environment variable is required');\n if (!clientSecret) throw new Error('FORGE_CLIENT_SECRET environment variable is required');\n\n return createForgeTokenManager({ tokenPath, clientId, clientSecret, tokenUrl });\n}\n"],"mappings":"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","names":["z","z"]}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@goforgeit/mcp-slack",
3
+ "version": "0.4.11",
4
+ "type": "module",
5
+ "description": "Forge MCP server for Slack (channels, messages, users)",
6
+ "bin": {
7
+ "mcp-slack": "./dist/index.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "vitest run"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.12.1",
25
+ "@slack/web-api": "^7.0.0",
26
+ "zod": "^4.2.1"
27
+ },
28
+ "devDependencies": {
29
+ "@forge/shared": "workspace:*",
30
+ "@types/node": "^22.10.2",
31
+ "tsup": "^8.0.0",
32
+ "vitest": "^3.2.4"
33
+ }
34
+ }