@ignission/slack-task-mcp 0.1.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/auth.js ADDED
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * OAuth 認証モジュール
5
+ *
6
+ * Cloudflare Workers を使った OAuth 認証フロー
7
+ */
8
+
9
+ import crypto from "node:crypto";
10
+ import fs from "node:fs/promises";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import open from "open";
14
+
15
+ // 定数
16
+ const DATA_DIR = path.join(os.homedir(), ".slack-task-mcp");
17
+ const CREDENTIALS_FILE = path.join(DATA_DIR, "credentials.json");
18
+ const AUTH_TIMEOUT = 5 * 60 * 1000; // 5分
19
+ const POLL_INTERVAL = 2000; // 2秒
20
+
21
+ // OAuth Worker URL
22
+ const OAUTH_WORKER_URL =
23
+ process.env.OAUTH_WORKER_URL || "https://slack-task-mcp-oauth.ignission.workers.dev";
24
+
25
+ /**
26
+ * データディレクトリを初期化
27
+ */
28
+ async function initDataDir() {
29
+ try {
30
+ await fs.mkdir(DATA_DIR, { recursive: true });
31
+ } catch (_err) {
32
+ // 既に存在する場合は無視
33
+ }
34
+ }
35
+
36
+ /**
37
+ * credentials.json を読み込み
38
+ */
39
+ export async function loadCredentials() {
40
+ try {
41
+ const data = await fs.readFile(CREDENTIALS_FILE, "utf-8");
42
+ return JSON.parse(data);
43
+ } catch (_err) {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * credentials.json を保存
50
+ */
51
+ async function saveCredentials(credentials) {
52
+ await initDataDir();
53
+ await fs.writeFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { mode: 0o600 });
54
+ }
55
+
56
+ /**
57
+ * credentials.json を削除
58
+ */
59
+ async function deleteCredentials() {
60
+ try {
61
+ await fs.unlink(CREDENTIALS_FILE);
62
+ return true;
63
+ } catch (_err) {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * セッション ID を生成
70
+ */
71
+ function generateSessionId() {
72
+ return crypto.randomBytes(16).toString("hex");
73
+ }
74
+
75
+ /**
76
+ * Worker にポーリングしてトークンを取得
77
+ */
78
+ async function pollForToken(sessionId) {
79
+ const pollUrl = `${OAUTH_WORKER_URL}/poll?session_id=${sessionId}`;
80
+
81
+ const response = await fetch(pollUrl);
82
+ const data = await response.json();
83
+
84
+ return data;
85
+ }
86
+
87
+ /**
88
+ * OAuth 認証フローを実行
89
+ * Cloudflare Workers を使用
90
+ */
91
+ export async function authenticate(options = {}) {
92
+ const noBrowser = options.noBrowser || false;
93
+
94
+ // セッション ID を生成
95
+ const sessionId = generateSessionId();
96
+
97
+ // Worker の認証 URL を生成
98
+ const authUrl = `${OAUTH_WORKER_URL}/auth?session_id=${sessionId}`;
99
+
100
+ console.log("🔐 Slack OAuth 認証を開始します...");
101
+ console.log("");
102
+
103
+ if (noBrowser) {
104
+ console.log("以下の URL をブラウザで開いてください:");
105
+ console.log("");
106
+ console.log(authUrl);
107
+ } else {
108
+ console.log("ブラウザで Slack ログイン画面を開いています...");
109
+ try {
110
+ await open(authUrl);
111
+ } catch (_err) {
112
+ console.log("");
113
+ console.log("ブラウザを自動で開けませんでした。以下の URL を手動で開いてください:");
114
+ console.log("");
115
+ console.log(authUrl);
116
+ }
117
+ }
118
+
119
+ console.log("");
120
+ console.log("認証が完了するまで待機中...");
121
+
122
+ // ポーリング開始
123
+ const startTime = Date.now();
124
+
125
+ while (Date.now() - startTime < AUTH_TIMEOUT) {
126
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
127
+
128
+ try {
129
+ const result = await pollForToken(sessionId);
130
+
131
+ if (result.status === "success") {
132
+ // credentials を保存
133
+ const credentials = {
134
+ access_token: result.access_token,
135
+ token_type: result.token_type,
136
+ scope: result.scope,
137
+ user_id: result.user_id,
138
+ team_id: result.team_id,
139
+ team_name: result.team_name,
140
+ created_at: result.created_at,
141
+ };
142
+
143
+ await saveCredentials(credentials);
144
+
145
+ console.log("");
146
+ console.log("✅ 認証が完了しました!");
147
+ console.log(` ワークスペース: ${credentials.team_name}`);
148
+ console.log(` トークンは ${CREDENTIALS_FILE} に保存されました`);
149
+
150
+ return true;
151
+ }
152
+
153
+ if (result.status === "error") {
154
+ console.error("");
155
+ console.error(`❌ 認証エラー: ${result.error}`);
156
+ return false;
157
+ }
158
+
159
+ // pending の場合は継続
160
+ process.stdout.write(".");
161
+ } catch (_err) {
162
+ // ネットワークエラーは無視して継続
163
+ process.stdout.write("x");
164
+ }
165
+ }
166
+
167
+ console.error("");
168
+ console.error("❌ 認証がタイムアウトしました(5分)");
169
+ return false;
170
+ }
171
+
172
+ /**
173
+ * 認証状態を表示
174
+ */
175
+ export async function showStatus() {
176
+ const credentials = await loadCredentials();
177
+
178
+ console.log("📋 認証状態");
179
+ console.log("");
180
+
181
+ if (!credentials) {
182
+ console.log("状態: ❌ 未認証");
183
+ console.log("");
184
+ console.log("`npx slack-task-mcp auth` を実行して認証してください");
185
+ return;
186
+ }
187
+
188
+ console.log("状態: ✅ 認証済み");
189
+ console.log(`ユーザー ID: ${credentials.user_id}`);
190
+ console.log(`ワークスペース: ${credentials.team_name} (${credentials.team_id})`);
191
+ console.log(`認証日時: ${credentials.created_at}`);
192
+ console.log(`スコープ: ${credentials.scope}`);
193
+ }
194
+
195
+ /**
196
+ * ログアウト
197
+ */
198
+ export async function logout() {
199
+ const credentials = await loadCredentials();
200
+
201
+ if (!credentials) {
202
+ console.log("ℹ️ 認証情報はありません");
203
+ return true;
204
+ }
205
+
206
+ const deleted = await deleteCredentials();
207
+
208
+ if (deleted) {
209
+ console.log("✅ ログアウトしました");
210
+ console.log(` ${CREDENTIALS_FILE} を削除しました`);
211
+ return true;
212
+ } else {
213
+ console.error("❌ ログアウトに失敗しました");
214
+ return false;
215
+ }
216
+ }
package/src/cli.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI エントリーポイント
5
+ *
6
+ * npx slack-task-mcp [command] [options]
7
+ */
8
+
9
+ import { authenticate, logout, showStatus } from "./auth.js";
10
+
11
+ // コマンドライン引数を解析
12
+ const args = process.argv.slice(2);
13
+ const command = args[0];
14
+ const subCommand = args[1];
15
+
16
+ // オプションを解析
17
+ function parseOptions(args) {
18
+ const options = {};
19
+ for (let i = 0; i < args.length; i++) {
20
+ if (args[i] === "--no-browser") {
21
+ options.noBrowser = true;
22
+ }
23
+ }
24
+ return options;
25
+ }
26
+
27
+ // ヘルプを表示
28
+ function showHelp() {
29
+ console.log(`
30
+ Slack Task MCP Server
31
+
32
+ Usage:
33
+ npx slack-task-mcp [command] [options]
34
+
35
+ Commands:
36
+ auth Slack OAuth 認証を開始
37
+ auth status 認証状態を表示
38
+ auth logout ログアウト
39
+ (なし) MCP サーバーとして起動
40
+
41
+ Options:
42
+ --no-browser ブラウザを自動で開かない
43
+ --help, -h ヘルプを表示
44
+
45
+ Examples:
46
+ npx slack-task-mcp auth
47
+ npx slack-task-mcp auth status
48
+ npx slack-task-mcp auth logout
49
+ npx slack-task-mcp auth --no-browser
50
+ `);
51
+ }
52
+
53
+ // メイン処理
54
+ async function main() {
55
+ // ヘルプ
56
+ if (args.includes("--help") || args.includes("-h")) {
57
+ showHelp();
58
+ process.exit(0);
59
+ }
60
+
61
+ // auth コマンド
62
+ if (command === "auth") {
63
+ if (subCommand === "status") {
64
+ await showStatus();
65
+ process.exit(0);
66
+ } else if (subCommand === "logout") {
67
+ const success = await logout();
68
+ process.exit(success ? 0 : 1);
69
+ } else {
70
+ // 認証フロー
71
+ const options = parseOptions(args);
72
+ const success = await authenticate(options);
73
+ process.exit(success ? 0 : 1);
74
+ }
75
+ }
76
+
77
+ // コマンドなし → MCP サーバー起動
78
+ if (!command) {
79
+ // MCP サーバーをインポートして起動
80
+ await import("./index.js");
81
+ } else {
82
+ console.error(`❌ 不明なコマンド: ${command}`);
83
+ console.error("");
84
+ console.error("ヘルプを表示するには: npx slack-task-mcp --help");
85
+ process.exit(1);
86
+ }
87
+ }
88
+
89
+ main().catch((err) => {
90
+ console.error(`❌ エラー: ${err.message}`);
91
+ process.exit(1);
92
+ });