bluelamp 3.0.8 → 4.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/README.md +68 -12
- package/bin/bluelamp-base +145 -330
- package/bin/bluelamp-hook +33 -0
- package/bin/bluelamp1 +2 -0
- package/bin/bluelamp10 +1 -373
- package/bin/bluelamp11 +1 -373
- package/bin/bluelamp12 +1 -373
- package/bin/bluelamp13 +1 -373
- package/bin/bluelamp14 +1 -373
- package/bin/bluelamp15 +1 -373
- package/bin/bluelamp16 +1 -373
- package/bin/bluelamp17 +1 -373
- package/bin/bluelamp18 +1 -373
- package/bin/bluelamp2 +1 -373
- package/bin/bluelamp3 +1 -373
- package/bin/bluelamp4 +1 -373
- package/bin/bluelamp5 +1 -373
- package/bin/bluelamp6 +1 -373
- package/bin/bluelamp7 +1 -373
- package/bin/bluelamp8 +1 -373
- package/bin/bluelamp9 +1 -373
- package/bin/docs/SCOPE_PROGRESS.md +222 -0
- package/lib/agents//343/203/226/343/203/253/343/203/274/343/203/251/343/203/263/343/203/227.md +106 -0
- package/lib/auth/InteractiveLogin.js +243 -0
- package/lib/auth/PortalAuthClient.js +187 -0
- package/lib/auth/TokenManager.js +258 -0
- package/lib/mcp-server/fetch-prompt.js +61 -0
- package/lib/mcp-server/index.cjs +164 -0
- package/lib/mcp-server/portal-api-client.cjs +210 -0
- package/package.json +24 -108
- package/scripts/build-release.js +426 -0
- package/scripts/generate-commands.js +40 -0
- package/scripts/setup-hook.js +98 -0
- package/bin/bluelamp +0 -374
- package/bin/bluelamp100 +0 -374
- package/bin/bluelamp19 +0 -374
- package/bin/bluelamp20 +0 -374
- package/bin/bluelamp21 +0 -374
- package/bin/bluelamp22 +0 -374
- package/bin/bluelamp23 +0 -374
- package/bin/bluelamp24 +0 -374
- package/bin/bluelamp25 +0 -374
- package/bin/bluelamp26 +0 -374
- package/bin/bluelamp27 +0 -374
- package/bin/bluelamp28 +0 -374
- package/bin/bluelamp29 +0 -374
- package/bin/bluelamp30 +0 -374
- package/bin/bluelamp31 +0 -374
- package/bin/bluelamp32 +0 -374
- package/bin/bluelamp33 +0 -374
- package/bin/bluelamp34 +0 -374
- package/bin/bluelamp35 +0 -374
- package/bin/bluelamp36 +0 -374
- package/bin/bluelamp37 +0 -374
- package/bin/bluelamp38 +0 -374
- package/bin/bluelamp39 +0 -374
- package/bin/bluelamp40 +0 -374
- package/bin/bluelamp41 +0 -374
- package/bin/bluelamp42 +0 -374
- package/bin/bluelamp43 +0 -374
- package/bin/bluelamp44 +0 -374
- package/bin/bluelamp45 +0 -374
- package/bin/bluelamp46 +0 -374
- package/bin/bluelamp47 +0 -374
- package/bin/bluelamp48 +0 -374
- package/bin/bluelamp49 +0 -374
- package/bin/bluelamp50 +0 -374
- package/bin/bluelamp51 +0 -374
- package/bin/bluelamp52 +0 -374
- package/bin/bluelamp53 +0 -374
- package/bin/bluelamp54 +0 -374
- package/bin/bluelamp55 +0 -374
- package/bin/bluelamp56 +0 -374
- package/bin/bluelamp57 +0 -374
- package/bin/bluelamp58 +0 -374
- package/bin/bluelamp59 +0 -374
- package/bin/bluelamp60 +0 -374
- package/bin/bluelamp61 +0 -374
- package/bin/bluelamp62 +0 -374
- package/bin/bluelamp63 +0 -374
- package/bin/bluelamp64 +0 -374
- package/bin/bluelamp65 +0 -374
- package/bin/bluelamp66 +0 -374
- package/bin/bluelamp67 +0 -374
- package/bin/bluelamp68 +0 -374
- package/bin/bluelamp69 +0 -374
- package/bin/bluelamp70 +0 -374
- package/bin/bluelamp71 +0 -374
- package/bin/bluelamp72 +0 -374
- package/bin/bluelamp73 +0 -374
- package/bin/bluelamp74 +0 -374
- package/bin/bluelamp75 +0 -374
- package/bin/bluelamp76 +0 -374
- package/bin/bluelamp77 +0 -374
- package/bin/bluelamp78 +0 -374
- package/bin/bluelamp79 +0 -374
- package/bin/bluelamp80 +0 -374
- package/bin/bluelamp81 +0 -374
- package/bin/bluelamp82 +0 -374
- package/bin/bluelamp83 +0 -374
- package/bin/bluelamp84 +0 -374
- package/bin/bluelamp85 +0 -374
- package/bin/bluelamp86 +0 -374
- package/bin/bluelamp87 +0 -374
- package/bin/bluelamp88 +0 -374
- package/bin/bluelamp89 +0 -374
- package/bin/bluelamp90 +0 -374
- package/bin/bluelamp91 +0 -374
- package/bin/bluelamp92 +0 -374
- package/bin/bluelamp93 +0 -374
- package/bin/bluelamp94 +0 -374
- package/bin/bluelamp95 +0 -374
- package/bin/bluelamp96 +0 -374
- package/bin/bluelamp97 +0 -374
- package/bin/bluelamp98 +0 -374
- package/bin/bluelamp99 +0 -374
- package/config/claude-config-template.json +0 -13
- package/lib/mcp-server/dist/InteractiveLogin.js +0 -27
- package/lib/mcp-server/dist/PortalAuthClient.js +0 -1
- package/lib/mcp-server/dist/TokenManager.js +0 -20
- package/lib/mcp-server/dist/bluelamp-system.js +0 -32
- package/lib/mcp-server/dist/crypto-utils.js +0 -27
- package/lib/mcp-server/dist/database-version.js +0 -33
- package/lib/mcp-server/dist/index.js +0 -62
- package/lib/mcp-server/dist/plugins/base-plugin.js +0 -24
- package/lib/mcp-server/dist/plugins/plugin-manager.js +0 -31
- package/lib/mcp-server/dist/plugins/services/leonardo-plugin.js +0 -95
- package/lib/mcp-server/dist/plugins/services/leonardo-prompt-parser.js +0 -36
- package/lib/mcp-server/dist/plugins/services/portal-api-client.js +0 -22
- package/lib/mcp-server/dist/plugins/services/portal-plugin.js +0 -52
- package/lib/mcp-server/dist/secure-mcp-server.js +0 -76
- package/lib/mcp-server/dist/security/ApiKeyManager.js +0 -24
- package/lib/mcp-server/dist/security/InteractiveLogin.js +0 -32
- package/lib/mcp-server/dist/security/PortalAuthClient.js +0 -1
- package/lib/mcp-server/dist/security/SimpleAuthManager.js +0 -1
- package/lib/mcp-server/dist/security/SlotAuthClient.js +0 -9
- package/lib/mcp-server/dist/security/TokenManager.js +0 -25
- package/lib/mcp-server/package.json +0 -21
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenManager - Portal統合CLIトークン管理クラス
|
|
3
|
+
* CommonJS版(bluelamp-vscode用)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
const https = require('https');
|
|
11
|
+
const http = require('http');
|
|
12
|
+
const { URL } = require('url');
|
|
13
|
+
|
|
14
|
+
class TokenManager {
|
|
15
|
+
static TOKEN_FILE_PATH = path.join(os.homedir(), '.bluelamp-vscode', 'portal-token.enc');
|
|
16
|
+
static PORTAL_API_URL_PROD = 'https://bluelamp-6clpzmy5pa-an.a.run.app/api/cli/verify';
|
|
17
|
+
static PORTAL_API_URL_LOCAL = 'https://bluelamp-6clpzmy5pa-an.a.run.app/api/cli/verify';
|
|
18
|
+
static ENCRYPTION_KEY = 'bluelamp-vscode-portal-token-key-2024';
|
|
19
|
+
static REQUEST_TIMEOUT = 10000; // 10秒
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 開発環境かどうかを判定
|
|
23
|
+
*/
|
|
24
|
+
static isLocalDev() {
|
|
25
|
+
return process.env.PORTAL_URL && process.env.PORTAL_URL.includes('localhost');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* APIエンドポイントを取得
|
|
30
|
+
*/
|
|
31
|
+
static getApiUrl(endpoint) {
|
|
32
|
+
// 環境変数から取得(デフォルトは本番)
|
|
33
|
+
const portalUrl = process.env.PORTAL_URL || 'https://bluelamp-6clpzmy5pa-an.a.run.app';
|
|
34
|
+
return portalUrl + endpoint;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Portal発行CLIトークンを暗号化して保存
|
|
39
|
+
*/
|
|
40
|
+
static async saveToken(token, userInfo = {}) {
|
|
41
|
+
try {
|
|
42
|
+
// トークン形式検証
|
|
43
|
+
if (!this.validateTokenFormat(token)) {
|
|
44
|
+
throw new Error('Portal発行のcli_形式トークンのみサポートされています');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 保存データ構造
|
|
48
|
+
const tokenData = {
|
|
49
|
+
token: token,
|
|
50
|
+
savedAt: Date.now(),
|
|
51
|
+
userInfo: userInfo,
|
|
52
|
+
version: '1.0',
|
|
53
|
+
source: 'portal'
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// 暗号化
|
|
57
|
+
const encrypted = this.encrypt(JSON.stringify(tokenData));
|
|
58
|
+
|
|
59
|
+
// ディレクトリ作成
|
|
60
|
+
const tokenDir = path.dirname(this.TOKEN_FILE_PATH);
|
|
61
|
+
if (!fs.existsSync(tokenDir)) {
|
|
62
|
+
fs.mkdirSync(tokenDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ファイル保存
|
|
66
|
+
fs.writeFileSync(this.TOKEN_FILE_PATH, encrypted);
|
|
67
|
+
console.log(' ✅ トークン保存完了');
|
|
68
|
+
|
|
69
|
+
return true;
|
|
70
|
+
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('❌ トークン保存エラー:', error.message);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 保存されたPortal CLIトークンを読み込み
|
|
79
|
+
*/
|
|
80
|
+
static async loadToken() {
|
|
81
|
+
try {
|
|
82
|
+
if (!fs.existsSync(this.TOKEN_FILE_PATH)) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ファイル読み込み
|
|
87
|
+
const encrypted = fs.readFileSync(this.TOKEN_FILE_PATH, 'utf8');
|
|
88
|
+
|
|
89
|
+
// 復号化
|
|
90
|
+
const decrypted = this.decrypt(encrypted);
|
|
91
|
+
const tokenData = JSON.parse(decrypted);
|
|
92
|
+
|
|
93
|
+
// データ構造確認
|
|
94
|
+
if (!tokenData.token || !tokenData.version) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return tokenData;
|
|
99
|
+
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('❌ トークン読み込みエラー:', error.message);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Portal API でトークン検証
|
|
108
|
+
*/
|
|
109
|
+
static async validateToken(token) {
|
|
110
|
+
try {
|
|
111
|
+
const response = await this.makeRequest({
|
|
112
|
+
method: 'POST',
|
|
113
|
+
url: this.getApiUrl('/api/cli/verify'),
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
'X-CLI-Token': token
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({ token: token })
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (response.success && response.data) {
|
|
122
|
+
return {
|
|
123
|
+
valid: true,
|
|
124
|
+
userInfo: {
|
|
125
|
+
id: response.data.userId,
|
|
126
|
+
name: response.data.userName,
|
|
127
|
+
email: response.data.userEmail,
|
|
128
|
+
role: response.data.userRole
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
} else {
|
|
132
|
+
return { valid: false, error: response.message };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return { valid: false, error: error.message };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 保存されたPortal CLIトークンを削除
|
|
142
|
+
*/
|
|
143
|
+
static async deleteToken() {
|
|
144
|
+
try {
|
|
145
|
+
if (fs.existsSync(this.TOKEN_FILE_PATH)) {
|
|
146
|
+
fs.unlinkSync(this.TOKEN_FILE_PATH);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('❌ トークン削除エラー:', error.message);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* トークン形式検証
|
|
158
|
+
*/
|
|
159
|
+
static validateTokenFormat(token) {
|
|
160
|
+
if (!token || typeof token !== 'string') {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Portal発行形式: cli_xxxxx_[64文字の16進数]
|
|
165
|
+
const patterns = [
|
|
166
|
+
/^cli_[a-z0-9]+_[a-f0-9]{64}$/, // 通常形式
|
|
167
|
+
/^cli_vscode_test_[a-f0-9]{64}$/ // テスト形式
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
return patterns.some(pattern => pattern.test(token));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* データ暗号化
|
|
175
|
+
*/
|
|
176
|
+
static encrypt(text) {
|
|
177
|
+
const algorithm = 'aes-256-cbc';
|
|
178
|
+
const key = crypto.scryptSync(this.ENCRYPTION_KEY, 'salt', 32);
|
|
179
|
+
const iv = crypto.randomBytes(16);
|
|
180
|
+
|
|
181
|
+
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
|
182
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
183
|
+
encrypted += cipher.final('hex');
|
|
184
|
+
|
|
185
|
+
return iv.toString('hex') + ':' + encrypted;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* データ復号化
|
|
190
|
+
*/
|
|
191
|
+
static decrypt(encryptedData) {
|
|
192
|
+
const algorithm = 'aes-256-cbc';
|
|
193
|
+
const key = crypto.scryptSync(this.ENCRYPTION_KEY, 'salt', 32);
|
|
194
|
+
|
|
195
|
+
const parts = encryptedData.split(':');
|
|
196
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
197
|
+
const encrypted = parts[1];
|
|
198
|
+
|
|
199
|
+
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
|
200
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
201
|
+
decrypted += decipher.final('utf8');
|
|
202
|
+
|
|
203
|
+
return decrypted;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* HTTP/HTTPS リクエスト実行
|
|
208
|
+
*/
|
|
209
|
+
static async makeRequest(options) {
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
const url = new URL(options.url);
|
|
212
|
+
const requestOptions = {
|
|
213
|
+
hostname: url.hostname,
|
|
214
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
215
|
+
path: url.pathname + url.search,
|
|
216
|
+
method: options.method || 'GET',
|
|
217
|
+
headers: options.headers || {},
|
|
218
|
+
timeout: this.REQUEST_TIMEOUT
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const client = url.protocol === 'https:' ? https : http;
|
|
222
|
+
|
|
223
|
+
const req = client.request(requestOptions, (res) => {
|
|
224
|
+
let data = '';
|
|
225
|
+
|
|
226
|
+
res.on('data', (chunk) => {
|
|
227
|
+
data += chunk;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
res.on('end', () => {
|
|
231
|
+
try {
|
|
232
|
+
const jsonData = JSON.parse(data);
|
|
233
|
+
resolve(jsonData);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
reject(new Error(`Invalid JSON response: ${data}`));
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
req.on('error', (error) => {
|
|
241
|
+
reject(error);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
req.on('timeout', () => {
|
|
245
|
+
req.destroy();
|
|
246
|
+
reject(new Error('Request timeout'));
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (options.body) {
|
|
250
|
+
req.write(options.body);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
req.end();
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
module.exports = { TokenManager };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portal APIからプロンプトを取得するモジュール
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fetch from 'node-fetch';
|
|
6
|
+
|
|
7
|
+
const PORTAL_URL = process.env.PORTAL_URL || 'https://bluelamp-6clpzmy5pa-an.a.run.app';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* キーワードに基づいてPortalからプロンプトを取得
|
|
11
|
+
*/
|
|
12
|
+
export async function fetchPromptFromPortal(keyword) {
|
|
13
|
+
try {
|
|
14
|
+
// Portal APIエンドポイント
|
|
15
|
+
const response = await fetch(`${PORTAL_URL}/api/prompts/by-keyword/${encodeURIComponent(keyword)}`, {
|
|
16
|
+
method: 'GET',
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
console.error(`Portal API error: ${response.status}`);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
|
|
29
|
+
if (!data.prompt) {
|
|
30
|
+
console.error('No prompt found in response');
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Portalから取得したプロンプトデータを返す
|
|
35
|
+
return {
|
|
36
|
+
title: data.prompt.name || keyword,
|
|
37
|
+
content: data.prompt.text || '',
|
|
38
|
+
tags: data.prompt.tags || [],
|
|
39
|
+
level: 'expert'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('Failed to fetch prompt from Portal:', error);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* フォールバック用のデフォルトプロンプト
|
|
50
|
+
*/
|
|
51
|
+
export const DEFAULT_PROMPTS = {
|
|
52
|
+
'BL-要件定義': {
|
|
53
|
+
title: '要件定義エンジニア',
|
|
54
|
+
content: `あなたは要件定義のスペシャリストです。
|
|
55
|
+
- ユーザーの要求を正確に理解し、技術要件に落とし込む
|
|
56
|
+
- 機能要件と非機能要件を明確に分離
|
|
57
|
+
- 実装可能性を考慮した要件定義`,
|
|
58
|
+
level: 'expert'
|
|
59
|
+
},
|
|
60
|
+
// ... 他の15個も同様
|
|
61
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BlueLamp VSCode MCP Server
|
|
5
|
+
* VSCode拡張機能専用のMCPサーバー実装
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
9
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
10
|
+
const {
|
|
11
|
+
CallToolRequestSchema,
|
|
12
|
+
ListToolsRequestSchema,
|
|
13
|
+
} = require('@modelcontextprotocol/sdk/types.js');
|
|
14
|
+
const { getPromptFromPortal } = require('./portal-api-client.cjs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
|
|
19
|
+
// .envファイルを読み込み(.env.localを優先)
|
|
20
|
+
const dotenvLocalPath = path.join(__dirname, '../../.env.local');
|
|
21
|
+
const dotenvPath = path.join(__dirname, '../../.env');
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(dotenvLocalPath)) {
|
|
24
|
+
require('dotenv').config({ path: dotenvLocalPath });
|
|
25
|
+
console.error('[MCP Server] Loaded .env.local');
|
|
26
|
+
} else if (fs.existsSync(dotenvPath)) {
|
|
27
|
+
require('dotenv').config({ path: dotenvPath });
|
|
28
|
+
console.error('[MCP Server] Loaded .env');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 環境変数から取得
|
|
32
|
+
const KEYWORD = process.env.BLUELAMP_KEYWORD || 'BL-要件定義';
|
|
33
|
+
const TOKEN = process.env.BLUELAMP_TOKEN;
|
|
34
|
+
const PORTAL_URL = process.env.PORTAL_URL || 'https://bluelamp-6clpzmy5pa-an.a.run.app';
|
|
35
|
+
|
|
36
|
+
// ログファイル設定(固定名にして見つけやすくする)
|
|
37
|
+
const LOG_FILE = path.join(os.tmpdir(), 'bluelamp-vscode-mcp-latest.log');
|
|
38
|
+
|
|
39
|
+
function log(message) {
|
|
40
|
+
const timestamp = new Date().toISOString();
|
|
41
|
+
const logMessage = `[${timestamp}] ${message}\n`;
|
|
42
|
+
fs.appendFileSync(LOG_FILE, logMessage);
|
|
43
|
+
console.error(`[MCP Server] ${message}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
log(`MCP Server started - Log file: ${LOG_FILE}`);
|
|
47
|
+
log(`Environment variables:`);
|
|
48
|
+
log(` KEYWORD: ${KEYWORD}`);
|
|
49
|
+
log(` TOKEN: ${TOKEN ? TOKEN.substring(0, 20) + '...' : 'NOT SET'}`);
|
|
50
|
+
log(` PORTAL_URL: ${PORTAL_URL}`);
|
|
51
|
+
log(` .env file exists: ${fs.existsSync(dotenvPath)}`);
|
|
52
|
+
|
|
53
|
+
class BlueLampVSCodeServer {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.server = new Server(
|
|
56
|
+
{
|
|
57
|
+
name: 'bluelamp-vscode',
|
|
58
|
+
version: '1.0.0',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
capabilities: {
|
|
62
|
+
tools: {},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
this.setupHandlers();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setupHandlers() {
|
|
71
|
+
// ツール一覧を返す
|
|
72
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
73
|
+
log('[MCP Server] ListTools request received');
|
|
74
|
+
return {
|
|
75
|
+
tools: [
|
|
76
|
+
{
|
|
77
|
+
name: 'inject_knowledge',
|
|
78
|
+
description: 'BlueLamp VSCode開発プロンプトの知識を注入',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
keyword: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'キーワード(@相談 など、そのまま指定してください。プレフィックスの追加は不要です)',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['keyword'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ツール実行
|
|
95
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
96
|
+
if (request.params.name === 'inject_knowledge') {
|
|
97
|
+
const { keyword } = request.params.arguments;
|
|
98
|
+
|
|
99
|
+
log(`[MCP Server] inject_knowledge called with keyword: ${keyword}`);
|
|
100
|
+
log(`[MCP Server] TOKEN: ${TOKEN ? TOKEN.substring(0, 20) + '...' : 'missing'}`);
|
|
101
|
+
log(`[MCP Server] PORTAL_URL: ${PORTAL_URL}`);
|
|
102
|
+
|
|
103
|
+
// Portal APIから認証付きで知識を取得
|
|
104
|
+
const result = await getPromptFromPortal(keyword, TOKEN);
|
|
105
|
+
|
|
106
|
+
if (!result || !result.success) {
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: `❌ Portal APIから知識を取得できませんでした: "${keyword}"`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 知識注入メッセージを返す
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: 'text',
|
|
122
|
+
text: `✅ BlueLamp VSCode 知識注入完了
|
|
123
|
+
|
|
124
|
+
${result.knowledge}
|
|
125
|
+
|
|
126
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
127
|
+
専門知識が注入されました。このロールで支援を開始します。`,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
type: 'text',
|
|
137
|
+
text: `Unknown tool: ${request.params.name}`,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async run() {
|
|
145
|
+
const transport = new StdioServerTransport();
|
|
146
|
+
await this.server.connect(transport);
|
|
147
|
+
console.error('BlueLamp VSCode MCP server started');
|
|
148
|
+
|
|
149
|
+
// 起動時に自動的に知識を注入(環境変数で指定されたキーワード)
|
|
150
|
+
if (KEYWORD && TOKEN) {
|
|
151
|
+
log(`[MCP Server] Auto-injecting knowledge for keyword: ${KEYWORD}`);
|
|
152
|
+
const result = await getPromptFromPortal(KEYWORD, TOKEN);
|
|
153
|
+
if (result && result.success) {
|
|
154
|
+
log(`[MCP Server] Auto-injection successful: ${result.title}`);
|
|
155
|
+
} else {
|
|
156
|
+
log(`[MCP Server] Auto-injection failed for keyword: ${KEYWORD}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// サーバー起動
|
|
163
|
+
const server = new BlueLampVSCodeServer();
|
|
164
|
+
server.run().catch(console.error);
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portal API Client for BlueLamp VSCode
|
|
3
|
+
* 認証付きでPortal APIからプロンプトを取得
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
|
|
7
|
+
const https = require('https');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
// 自己署名証明書を許可するHTTPSエージェント
|
|
13
|
+
const httpsAgent = new https.Agent({
|
|
14
|
+
rejectUnauthorized: false
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const PORTAL_URL = process.env.PORTAL_URL || 'https://bluelamp-6clpzmy5pa-an.a.run.app';
|
|
18
|
+
|
|
19
|
+
// ログファイル(index.cjsと同じファイルに書き込む)
|
|
20
|
+
const LOG_FILE = path.join(os.tmpdir(), 'bluelamp-vscode-mcp-latest.log');
|
|
21
|
+
|
|
22
|
+
// 共通設定のキャッシュ(5分間有効)
|
|
23
|
+
let globalConfigCache = null;
|
|
24
|
+
let cacheExpiry = 0;
|
|
25
|
+
|
|
26
|
+
function log(message) {
|
|
27
|
+
const timestamp = new Date().toISOString();
|
|
28
|
+
const logMessage = `[${timestamp}] ${message}\n`;
|
|
29
|
+
fs.appendFileSync(LOG_FILE, logMessage);
|
|
30
|
+
console.error(message);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 共通CLAUDE.MD設定を取得(オプショナル)
|
|
35
|
+
* エラーが発生してもnullを返して処理を継続
|
|
36
|
+
*/
|
|
37
|
+
async function getGlobalConfig(token) {
|
|
38
|
+
try {
|
|
39
|
+
// キャッシュが有効ならそれを返す
|
|
40
|
+
if (globalConfigCache && Date.now() < cacheExpiry) {
|
|
41
|
+
log('[portal-api-client] Using cached global config');
|
|
42
|
+
return globalConfigCache;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!token) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Portal APIから共通設定を取得
|
|
50
|
+
const url = `${PORTAL_URL}/api/cli/global-config`;
|
|
51
|
+
log(`[portal-api-client] Fetching global config from: ${url}`);
|
|
52
|
+
|
|
53
|
+
const response = await fetch(url, {
|
|
54
|
+
method: 'GET',
|
|
55
|
+
headers: {
|
|
56
|
+
'X-CLI-Token': token,
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
'User-Agent': 'BlueLamp-VSCode-MCP/1.0'
|
|
59
|
+
},
|
|
60
|
+
agent: httpsAgent
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (response.ok) {
|
|
64
|
+
const data = await response.json();
|
|
65
|
+
if (data.success && data.content) {
|
|
66
|
+
log('[portal-api-client] Global config fetched successfully');
|
|
67
|
+
globalConfigCache = data.content;
|
|
68
|
+
cacheExpiry = Date.now() + 300000; // 5分間キャッシュ
|
|
69
|
+
return data.content;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
log('[portal-api-client] Global config not available (optional)');
|
|
74
|
+
return null;
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// エラーが発生しても処理を継続(後方互換性)
|
|
78
|
+
log(`[portal-api-client] Global config fetch error (optional): ${error.message}`);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Portal APIから認証付きでプロンプトを取得
|
|
85
|
+
*/
|
|
86
|
+
async function getPromptFromPortal(keyword, token) {
|
|
87
|
+
try {
|
|
88
|
+
log(`[portal-api-client] getPromptFromPortal called with keyword: ${keyword}, token: ${token ? 'present' : 'missing'}`);
|
|
89
|
+
|
|
90
|
+
if (!token) {
|
|
91
|
+
log('[portal-api-client] No authentication token provided');
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// キーワードをそのまま使用
|
|
96
|
+
const encodedKeyword = encodeURIComponent(keyword);
|
|
97
|
+
const url = `${PORTAL_URL}/api/cli/prompts/knowledge-injection/${encodedKeyword}`;
|
|
98
|
+
log(`[portal-api-client] Direct keyword search: ${url}`);
|
|
99
|
+
log(`[portal-api-client] Token (first 30 chars): ${token.substring(0, 30)}...`);
|
|
100
|
+
|
|
101
|
+
log(`[portal-api-client] About to call fetch...`);
|
|
102
|
+
const response = await fetch(url, {
|
|
103
|
+
method: 'GET',
|
|
104
|
+
headers: {
|
|
105
|
+
'X-CLI-Token': token,
|
|
106
|
+
'Content-Type': 'application/json',
|
|
107
|
+
'User-Agent': 'BlueLamp-VSCode-MCP/1.0'
|
|
108
|
+
},
|
|
109
|
+
agent: url.startsWith('https') ? httpsAgent : undefined
|
|
110
|
+
});
|
|
111
|
+
log(`[portal-api-client] Fetch completed`);
|
|
112
|
+
|
|
113
|
+
log(`[portal-api-client] Response status: ${response.status}`);
|
|
114
|
+
log(`[portal-api-client] Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
115
|
+
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
log(`[portal-api-client] Portal API error: ${response.status}`);
|
|
118
|
+
const errorText = await response.text();
|
|
119
|
+
log(`[portal-api-client] Error response: ${errorText}`);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const data = await response.json();
|
|
124
|
+
log(`[portal-api-client] Direct keyword search completed successfully`);
|
|
125
|
+
|
|
126
|
+
if (!data.success) {
|
|
127
|
+
log(`[portal-api-client] API returned success: false`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 直接プロンプトデータを取得(検索不要)
|
|
132
|
+
const prompt = data.data;
|
|
133
|
+
|
|
134
|
+
if (!prompt) {
|
|
135
|
+
log(`[portal-api-client] No prompt found for keyword: ${keyword}`);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
log(`[portal-api-client] Found prompt: ${prompt.title}`);
|
|
140
|
+
|
|
141
|
+
// プロンプトの詳細情報が既に含まれているかチェック
|
|
142
|
+
if (prompt.content || prompt.text) {
|
|
143
|
+
// 詳細情報が含まれている場合はそのまま返す
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
knowledge: prompt.content || prompt.text,
|
|
147
|
+
title: prompt.title,
|
|
148
|
+
tags: prompt.tags || []
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 個別のプロンプト詳細を取得
|
|
153
|
+
try {
|
|
154
|
+
const detailUrl = `${PORTAL_URL}/api/cli/prompts/${prompt.id}`;
|
|
155
|
+
log(`[portal-api-client] Fetching prompt details from: ${detailUrl}`);
|
|
156
|
+
|
|
157
|
+
const detailResponse = await fetch(detailUrl, {
|
|
158
|
+
method: 'GET',
|
|
159
|
+
headers: {
|
|
160
|
+
'X-CLI-Token': token,
|
|
161
|
+
'Content-Type': 'application/json',
|
|
162
|
+
'User-Agent': 'BlueLamp-VSCode-MCP/1.0'
|
|
163
|
+
},
|
|
164
|
+
agent: httpsAgent
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (detailResponse.ok) {
|
|
168
|
+
const detailData = await detailResponse.json();
|
|
169
|
+
if (detailData.success && detailData.data?.prompt?.content) {
|
|
170
|
+
log(`[portal-api-client] Successfully fetched prompt content`);
|
|
171
|
+
|
|
172
|
+
// 共通設定を取得(オプショナル)
|
|
173
|
+
const globalConfig = await getGlobalConfig(token);
|
|
174
|
+
let finalContent = detailData.data.prompt.content;
|
|
175
|
+
|
|
176
|
+
// 共通設定があれば先頭に追加
|
|
177
|
+
if (globalConfig) {
|
|
178
|
+
log(`[portal-api-client] Adding global config to prompt content`);
|
|
179
|
+
finalContent = `${globalConfig}\n\n---\n\n${finalContent}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
success: true,
|
|
184
|
+
knowledge: finalContent,
|
|
185
|
+
title: prompt.title || prompt.name,
|
|
186
|
+
tags: prompt.tags
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
log(`[portal-api-client] Error fetching prompt details: ${error.message}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// フォールバック: リスト情報のみ返す
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
knowledge: prompt.description || `# ${prompt.title}\n\n詳細情報の取得に失敗しました。`,
|
|
198
|
+
title: prompt.title || prompt.name,
|
|
199
|
+
tags: prompt.tags
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
} catch (error) {
|
|
203
|
+
log(`[portal-api-client] FATAL ERROR in getPromptFromPortal: ${error.message}`);
|
|
204
|
+
log(`[portal-api-client] Error stack: ${error.stack}`);
|
|
205
|
+
console.error('Failed to get prompt from Portal:', error);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = { getPromptFromPortal, getGlobalConfig };
|