bluelamp 4.1.2 → 4.2.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/bin/bluelamp-vscode-base +1 -1
- package/lib/auth/InteractiveLogin.js +1 -1
- package/lib/auth/PortalAuthClient.js +1 -1
- package/lib/auth/TokenManager.js +1 -1
- package/lib/i18n/en.json +6 -6
- package/lib/i18n/ja.json +6 -6
- package/lib/mcp-gmail/credentials.json +1 -0
- package/lib/mcp-gmail/index.js +547 -0
- package/lib/mcp-password-manager/index.js +248 -0
- package/lib/mcp-server/index.cjs +1 -1
- package/lib/mcp-server/portal-api-client.cjs +1 -1
- package/package.json +5 -3
- package/scripts/playwright-launch.js +1 -1
- package/scripts/setup-mcps.js +2 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import initSqlJs from "sql.js";
|
|
10
|
+
const ALGORITHM = "aes-256-gcm";
|
|
11
|
+
const VAULT_DIR = join(homedir(), ".musuhi");
|
|
12
|
+
const DB_PATH = join(VAULT_DIR, "vault.db");
|
|
13
|
+
const KEY_PATH = join(VAULT_DIR, "vault.key");
|
|
14
|
+
function ensureVaultDir() {
|
|
15
|
+
if (!existsSync(VAULT_DIR)) {
|
|
16
|
+
mkdirSync(VAULT_DIR, { recursive: true, mode: 0o700 });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function getEncryptionKey() {
|
|
20
|
+
// 1. Environment variable takes priority (backward compat)
|
|
21
|
+
const envKey = process.env.VAULT_ENCRYPTION_KEY;
|
|
22
|
+
if (envKey) {
|
|
23
|
+
const buf = Buffer.from(envKey, "hex");
|
|
24
|
+
if (buf.length !== 32) {
|
|
25
|
+
throw new Error("VAULT_ENCRYPTION_KEY must be 32 bytes (64 hex characters)");
|
|
26
|
+
}
|
|
27
|
+
return buf;
|
|
28
|
+
}
|
|
29
|
+
// 2. Read from key file, or auto-generate on first run
|
|
30
|
+
ensureVaultDir();
|
|
31
|
+
if (existsSync(KEY_PATH)) {
|
|
32
|
+
const hex = readFileSync(KEY_PATH, "utf-8").trim();
|
|
33
|
+
const buf = Buffer.from(hex, "hex");
|
|
34
|
+
if (buf.length !== 32) {
|
|
35
|
+
throw new Error(`Invalid key file at ${KEY_PATH}. Delete it to regenerate.`);
|
|
36
|
+
}
|
|
37
|
+
return buf;
|
|
38
|
+
}
|
|
39
|
+
// 3. First run: generate and save
|
|
40
|
+
const newKey = randomBytes(32);
|
|
41
|
+
writeFileSync(KEY_PATH, newKey.toString("hex"), { mode: 0o600 });
|
|
42
|
+
return newKey;
|
|
43
|
+
}
|
|
44
|
+
let _db = null;
|
|
45
|
+
async function getDB() {
|
|
46
|
+
if (_db)
|
|
47
|
+
return _db;
|
|
48
|
+
ensureVaultDir();
|
|
49
|
+
const SQL = await initSqlJs();
|
|
50
|
+
if (existsSync(DB_PATH)) {
|
|
51
|
+
const buffer = readFileSync(DB_PATH);
|
|
52
|
+
_db = new SQL.Database(buffer);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
_db = new SQL.Database();
|
|
56
|
+
}
|
|
57
|
+
_db.run(`
|
|
58
|
+
CREATE TABLE IF NOT EXISTS vault_entries (
|
|
59
|
+
service TEXT PRIMARY KEY,
|
|
60
|
+
username TEXT NOT NULL,
|
|
61
|
+
encrypted TEXT NOT NULL,
|
|
62
|
+
iv TEXT NOT NULL,
|
|
63
|
+
auth_tag TEXT NOT NULL,
|
|
64
|
+
note TEXT,
|
|
65
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
66
|
+
)
|
|
67
|
+
`);
|
|
68
|
+
return _db;
|
|
69
|
+
}
|
|
70
|
+
function saveDB(db) {
|
|
71
|
+
const data = db.export();
|
|
72
|
+
writeFileSync(DB_PATH, Buffer.from(data), { mode: 0o600 });
|
|
73
|
+
}
|
|
74
|
+
function encrypt(plaintext) {
|
|
75
|
+
const key = getEncryptionKey();
|
|
76
|
+
const iv = randomBytes(16);
|
|
77
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
78
|
+
let encrypted = cipher.update(plaintext, "utf-8", "hex");
|
|
79
|
+
encrypted += cipher.final("hex");
|
|
80
|
+
const authTag = cipher.getAuthTag().toString("hex");
|
|
81
|
+
return { encrypted, iv: iv.toString("hex"), authTag };
|
|
82
|
+
}
|
|
83
|
+
function decrypt(encrypted, ivHex, authTagHex) {
|
|
84
|
+
const key = getEncryptionKey();
|
|
85
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
86
|
+
const authTag = Buffer.from(authTagHex, "hex");
|
|
87
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
88
|
+
decipher.setAuthTag(authTag);
|
|
89
|
+
let decrypted = decipher.update(encrypted, "hex", "utf-8");
|
|
90
|
+
decrypted += decipher.final("utf-8");
|
|
91
|
+
return decrypted;
|
|
92
|
+
}
|
|
93
|
+
const server = new Server({ name: "password-manager", version: "3.1.0" }, { capabilities: { tools: {} } });
|
|
94
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
95
|
+
tools: [
|
|
96
|
+
{
|
|
97
|
+
name: "store_password",
|
|
98
|
+
description: "サービス名・ユーザー名・パスワード/APIキーを暗号化してローカルに保存",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
service: { type: "string", description: "サービス名(例: OpenAI, Google)" },
|
|
103
|
+
username: { type: "string", description: "ユーザー名・メールアドレス" },
|
|
104
|
+
password: { type: "string", description: "パスワード・APIキー・秘密鍵" },
|
|
105
|
+
note: { type: "string", description: "メモ(任意)" },
|
|
106
|
+
},
|
|
107
|
+
required: ["service", "username", "password"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "get_password",
|
|
112
|
+
description: "サービス名で検索し、復号したパスワード/APIキーを返す",
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
service: { type: "string", description: "サービス名" },
|
|
117
|
+
},
|
|
118
|
+
required: ["service"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "list_passwords",
|
|
123
|
+
description: "保存済みサービス一覧を表示(パスワードは非表示)",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "delete_password",
|
|
131
|
+
description: "指定サービスのパスワードを削除",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
service: { type: "string", description: "削除するサービス名" },
|
|
136
|
+
},
|
|
137
|
+
required: ["service"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
}));
|
|
142
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
143
|
+
const { name, arguments: args } = request.params;
|
|
144
|
+
try {
|
|
145
|
+
const db = await getDB();
|
|
146
|
+
switch (name) {
|
|
147
|
+
case "store_password": {
|
|
148
|
+
const { service, username, password, note } = args;
|
|
149
|
+
const { encrypted, iv, authTag } = encrypt(password);
|
|
150
|
+
db.run(`INSERT INTO vault_entries (service, username, encrypted, iv, auth_tag, note, updated_at)
|
|
151
|
+
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
|
|
152
|
+
ON CONFLICT (service) DO UPDATE SET
|
|
153
|
+
username = excluded.username,
|
|
154
|
+
encrypted = excluded.encrypted,
|
|
155
|
+
iv = excluded.iv,
|
|
156
|
+
auth_tag = excluded.auth_tag,
|
|
157
|
+
note = excluded.note,
|
|
158
|
+
updated_at = datetime('now')`, [service, username, encrypted, iv, authTag, note ?? null]);
|
|
159
|
+
saveDB(db);
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: `「${service}」を保存しました(ユーザー: ${username})` }],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
case "get_password": {
|
|
165
|
+
const { service } = args;
|
|
166
|
+
const stmt = db.prepare("SELECT * FROM vault_entries WHERE service = ?");
|
|
167
|
+
stmt.bind([service]);
|
|
168
|
+
if (!stmt.step()) {
|
|
169
|
+
stmt.free();
|
|
170
|
+
return {
|
|
171
|
+
content: [{ type: "text", text: `「${service}」は見つかりません` }],
|
|
172
|
+
isError: true,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const entry = stmt.getAsObject();
|
|
176
|
+
stmt.free();
|
|
177
|
+
const password = decrypt(entry.encrypted, entry.iv, entry.auth_tag);
|
|
178
|
+
const lines = [
|
|
179
|
+
`${service}`,
|
|
180
|
+
` ユーザー名: ${entry.username}`,
|
|
181
|
+
` パスワード: ${password}`,
|
|
182
|
+
...(entry.note ? [` メモ: ${entry.note}`] : []),
|
|
183
|
+
` 更新日時: ${entry.updated_at}`,
|
|
184
|
+
];
|
|
185
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
186
|
+
}
|
|
187
|
+
case "list_passwords": {
|
|
188
|
+
const rows = [];
|
|
189
|
+
const stmt = db.prepare("SELECT service, username, note, updated_at FROM vault_entries ORDER BY service");
|
|
190
|
+
while (stmt.step()) {
|
|
191
|
+
rows.push(stmt.getAsObject());
|
|
192
|
+
}
|
|
193
|
+
stmt.free();
|
|
194
|
+
if (rows.length === 0) {
|
|
195
|
+
return { content: [{ type: "text", text: "保存済みパスワードはありません" }] };
|
|
196
|
+
}
|
|
197
|
+
const lines = [`保存済み一覧(${rows.length}件)`, ""];
|
|
198
|
+
for (const row of rows) {
|
|
199
|
+
lines.push(`- ${row.service}`);
|
|
200
|
+
lines.push(` ユーザー: ${row.username}`);
|
|
201
|
+
if (row.note)
|
|
202
|
+
lines.push(` メモ: ${row.note}`);
|
|
203
|
+
lines.push(` 更新日時: ${row.updated_at}`);
|
|
204
|
+
lines.push("");
|
|
205
|
+
}
|
|
206
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
207
|
+
}
|
|
208
|
+
case "delete_password": {
|
|
209
|
+
const { service } = args;
|
|
210
|
+
const check = db.prepare("SELECT 1 FROM vault_entries WHERE service = ?");
|
|
211
|
+
check.bind([service]);
|
|
212
|
+
const exists = check.step();
|
|
213
|
+
check.free();
|
|
214
|
+
if (!exists) {
|
|
215
|
+
return {
|
|
216
|
+
content: [{ type: "text", text: `「${service}」は見つかりません` }],
|
|
217
|
+
isError: true,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
db.run("DELETE FROM vault_entries WHERE service = ?", [service]);
|
|
221
|
+
saveDB(db);
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: "text", text: `「${service}」を削除しました` }],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
default:
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
229
|
+
isError: true,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: "text", text: `エラー: ${message}` }],
|
|
237
|
+
isError: true,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
async function main() {
|
|
242
|
+
const transport = new StdioServerTransport();
|
|
243
|
+
await server.connect(transport);
|
|
244
|
+
}
|
|
245
|
+
main().catch((error) => {
|
|
246
|
+
console.error("Server error:", error);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
});
|
package/lib/mcp-server/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const{Server:Server}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:StdioServerTransport}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:CallToolRequestSchema,ListToolsRequestSchema:ListToolsRequestSchema}=require("@modelcontextprotocol/sdk/types.js"),{getPromptFromPortal:getPromptFromPortal,reportViolation:reportViolation,listAvailableMcps:listAvailableMcps,getMcpInfo:getMcpInfo,callMcpTool:callMcpTool,deployUserMcp:deployUserMcp,listUserCreatedMcps:listUserCreatedMcps,deleteUserMcp:deleteUserMcp}=require("./portal-api-client.cjs"),{logger:logger}=require("../utils/logger"),{t:t,setLanguage:setLanguage}=require("../i18n"),path=require("path"),fs=require("fs"),os=require("os"),dotenvLocalPath=path.join(__dirname,"../../.env.local"),dotenvPath=path.join(__dirname,"../../.env");fs.existsSync(dotenvLocalPath)?(require("dotenv").config({path:dotenvLocalPath}),logger.debug("[MCP Server] Loaded .env.local")):fs.existsSync(dotenvPath)&&(require("dotenv").config({path:dotenvPath}),logger.debug("[MCP Server] Loaded .env"));const KEYWORD=process.env.BLUELAMP_KEYWORD||"BL-要件定義",TOKEN=process.env.BLUELAMP_TOKEN,PORTAL_URL=process.env.PORTAL_URL||"https://bluelamp-6clpzmy5pa-an.a.run.app",LANGUAGE=process.env.BLUELAMP_LANGUAGE||"ja";setLanguage(LANGUAGE);const LOG_DIR=path.join(os.homedir(),".bluelamp-vscode","logs"),LOG_FILE=path.join(LOG_DIR,"mcp.log");function log(e){const t=`[${(new Date).toISOString()}] ${e}\n`;fs.appendFileSync(LOG_FILE,t),logger.info(`[MCP Server] ${e}`)}fs.existsSync(LOG_DIR)||fs.mkdirSync(LOG_DIR,{recursive:!0}),log(`MCP Server started - Log file: ${LOG_FILE}`),log("Environment variables:"),log(` KEYWORD: ${KEYWORD}`),log(" TOKEN: "+(TOKEN?TOKEN.substring(0,20)+"...":"NOT SET")),log(` PORTAL_URL: ${PORTAL_URL}`),log(` LANGUAGE: ${LANGUAGE}`),log(` .env file exists: ${fs.existsSync(dotenvPath)}`);class BlueLampVSCodeServer{constructor(){this.server=new Server({name:"bluelamp",version:"1.0.0"},{capabilities:{tools:{}}}),this.setupHandlers()}setupHandlers(){this.server.setRequestHandler(ListToolsRequestSchema,async()=>{log("[MCP Server] ListTools request received");const e={type:"object",properties:{keyword:{type:"string",description:t("mcp.keywordDescription")}},required:["keyword"]},r={readOnlyHint:!0,idempotentHint:!0};return{tools:[{name:"load_knowledge",description:t("mcp.toolDescriptionLoadKnowledge"),annotations:r,inputSchema:e},{name:"inject_knowledge",description:t("mcp.toolDescriptionInjectKnowledge"),annotations:r,inputSchema:e},{name:"report_violation",description:t("mcp.toolDescriptionReportViolation"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{reason:{type:"string",description:t("mcp.violationReasonDescription")},severity:{type:"string",enum:["warning","serious","critical"],description:t("mcp.violationSeverityDescription")},userMessage:{type:"string",description:t("mcp.violationUserMessageDescription")}},required:["reason","severity"]}},{name:"list_available_mcps",description:t("mcp.toolDescriptionListMcps"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{category:{type:"string",enum:["productivity","development","communication","data","media","custom"],description:t("mcp.listMcpsCategoryDescription")},search:{type:"string",description:t("mcp.listMcpsSearchDescription")}},required:[]}},{name:"get_mcp_info",description:t("mcp.toolDescriptionGetMcpInfo"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:t("mcp.getMcpInfoIdDescription")}},required:["mcp_id"]}},{name:"call_tool",description:t("mcp.toolDescriptionCallTool"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:t("mcp.callToolMcpIdDescription")},tool_name:{type:"string",description:t("mcp.callToolNameDescription")},arguments:{type:"object",description:t("mcp.callToolArgsDescription")}},required:["mcp_id","tool_name"]}},{name:"create_user_mcp",description:t("mcp.toolDescriptionCreateUserMcp"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{name:{type:"string",description:t("mcp.createMcpNameDescription")},description:{type:"string",description:t("mcp.createMcpDescDescription")},tools:{type:"string",description:t("mcp.createMcpToolsDescription")},code:{type:"string",description:t("mcp.createMcpCodeDescription")},category:{type:"string",enum:["productivity","development","communication","data","media","custom"],description:t("mcp.createMcpCategoryDescription")},envVars:{type:"string",description:t("mcp.createMcpEnvVarsDescription")}},required:["name","description","code"]}},{name:"list_user_mcps",description:t("mcp.toolDescriptionListUserMcps"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{},required:[]}},{name:"delete_user_mcp",description:t("mcp.toolDescriptionDeleteUserMcp"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:t("mcp.deleteUserMcpIdDescription")}},required:["mcp_id"]}}]}}),this.server.setRequestHandler(CallToolRequestSchema,async e=>{const{name:r}=e.params;if("load_knowledge"===r||"inject_knowledge"===r){const{keyword:r}=e.params.arguments;log(`[MCP Server] load_knowledge called with keyword: ${r}`),log("[MCP Server] TOKEN: "+(TOKEN?TOKEN.substring(0,20)+"...":"missing")),log(`[MCP Server] PORTAL_URL: ${PORTAL_URL}`),log(`[MCP Server] LANGUAGE: ${LANGUAGE}`);try{const e=await getPromptFromPortal(r,TOKEN,LANGUAGE);if(!e||!e.success){const o=e?.error||t("mcp.knowledgeNotFound",{keyword:r});return log(`[MCP Server] Knowledge loading failed: ${o}`),{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:o})}`}],isError:!0}}const o="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${e.knowledge}${o}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}`}]}}catch(e){return log(`[MCP Server] Exception in load_knowledge: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.knowledgeLoadError",{error:e.message})}`}],isError:!0}}}if("report_violation"===r){const{reason:r,severity:o,userMessage:n}=e.params.arguments;log(`[MCP Server] report_violation called: severity=${o}, reason=${r}`);try{const e=await reportViolation(r,o,TOKEN,n);return e&&e.success?{content:[{type:"text",text:`🔒 ${e.message}`}]}:{content:[{type:"text",text:`❌ ${t("mcp.violationReportFailed",{error:e?.error||"Unknown"})}`}],isError:!0}}catch(e){return log(`[MCP Server] report_violation error: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.violationReportError",{error:e.message})}`}],isError:!0}}}if("list_available_mcps"===r){const{category:r,search:o}=e.params.arguments||{};log(`[MCP Server] list_available_mcps called: category=${r}, search=${o}`);try{const e=await listAvailableMcps({category:r,search:o},TOKEN,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${t("mcp.listMcpsFailed",{error:e?.error||"Unknown"})}`}],isError:!0};const n=e.data||[];let s=`📋 ${t("mcp.listMcpsTitle")}\n\n`;return 0===n.length?s+=t("mcp.listMcpsEmpty"):n.forEach(e=>{const t=e.tools?.length||0;s+=`**${e.name}** (${e.mcpId})\n`,s+=` ${e.description}\n`,s+=` カテゴリ: ${e.category} | ツール数: ${t} | アクセス: ${e.hasAccess?"✅":"❌"}\n\n`}),{content:[{type:"text",text:s}]}}catch(e){return log(`[MCP Server] list_available_mcps error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("get_mcp_info"===r){const{mcp_id:r}=e.params.arguments;log(`[MCP Server] get_mcp_info called: mcpId=${r}`);try{const e=await getMcpInfo(r,TOKEN,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${t("mcp.getMcpInfoFailed",{error:e?.error||"Unknown"})}`}],isError:!0};const o=e.data;let n=`🔧 **${o.name}** (${o.mcpId})\n\n`;return n+=`${o.description}\n\n`,n+=`カテゴリ: ${o.category}\n`,n+=`アクセス: ${o.hasAccess?"利用可能":"利用不可"}\n`,n+=`ステータス: ${o.status}\n\n`,o.tools&&o.tools.length>0&&(n+=`### ツール一覧 (${o.tools.length})\n\n`,o.tools.forEach(e=>{n+=`- **${e.name}**: ${e.description}\n`,e.inputSchema?.properties&&Object.entries(e.inputSchema.properties).forEach(([e,t])=>{n+=` - \`${e}\` (${t.type||"any"}): ${t.description||""}\n`})})),o.requiredCredentials&&o.requiredCredentials.length>0&&(n+="\n### 必要な認証情報\n",o.requiredCredentials.forEach(e=>{n+=`- **${e.displayName}** (${e.key}): ${e.required?"必須":"任意"}\n`})),{content:[{type:"text",text:n}]}}catch(e){return log(`[MCP Server] get_mcp_info error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("call_tool"===r){const{mcp_id:r,tool_name:o,arguments:n}=e.params.arguments;log(`[MCP Server] call_tool called: mcpId=${r}, tool=${o}`);try{const e=await callMcpTool(r,o,n||{},TOKEN);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${t("mcp.callToolFailed",{error:e?.error||"Unknown"})}`}],isError:!0};const s=e.data;return"string"==typeof s?{content:[{type:"text",text:s}]}:{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){return log(`[MCP Server] call_tool error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("create_user_mcp"===r){const r=e.params.arguments;log(`[MCP Server] create_user_mcp called: name=${r.name}`);try{let e=r.tools;if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=[]}let o=r.envVars;if("string"==typeof o)try{o=JSON.parse(o)}catch(e){o=void 0}const n={code:r.code,name:r.name,description:r.description||"",tools:e||[],category:r.category||"custom",envVars:o},s=await deployUserMcp(n,TOKEN);if(!s||!s.success)return{content:[{type:"text",text:`❌ ${s?.details?`${t("mcp.createMcpScanFailed")}\n${JSON.stringify(s.details,null,2)}`:t("mcp.createMcpFailed",{error:s?.error||"Unknown"})}`}],isError:!0};const i=s.data;return{content:[{type:"text",text:`✅ ${t("mcp.createMcpSuccess")}\n\n**MCP ID**: ${i.mcpId}\n**Endpoint**: ${i.endpoint}\n**有効化**: ${i.enabled?"自動有効化済み":"未有効化"}`}]}}catch(e){return log(`[MCP Server] create_user_mcp error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("list_user_mcps"===r){log("[MCP Server] list_user_mcps called");try{const e=await listUserCreatedMcps(TOKEN);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${e?.error||"Unknown"}`}],isError:!0};const r=e.data?.mcps||[];let o=`📋 ${t("mcp.listUserMcpsTitle")}\n\n`;return 0===r.length?o+=t("mcp.listUserMcpsEmpty"):r.forEach(e=>{o+=`**${e.name}** (${e.mcpId})\n`,o+=` ${e.description||""}\n`,o+=` カテゴリ: ${e.category} | ツール数: ${e.toolCount||0} | ステータス: ${e.status}\n`,e.endpoint&&(o+=` エンドポイント: ${e.endpoint}\n`),o+="\n"}),{content:[{type:"text",text:o}]}}catch(e){return log(`[MCP Server] list_user_mcps error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("delete_user_mcp"===r){const{mcp_id:r}=e.params.arguments;log(`[MCP Server] delete_user_mcp called: mcpId=${r}`);try{const e=await deleteUserMcp(r,TOKEN);return e&&e.success?{content:[{type:"text",text:`✅ ${t("mcp.deleteUserMcpSuccess",{mcpId:r})}`}]}:{content:[{type:"text",text:`❌ ${t("mcp.deleteUserMcpFailed",{error:e?.error||"Unknown"})}`}],isError:!0}}catch(e){return log(`[MCP Server] delete_user_mcp error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}return{content:[{type:"text",text:`Unknown tool: ${e.params.name}`}],isError:!0}})}async run(){const e=new StdioServerTransport;if(await this.server.connect(e),logger.info("BlueLamp VSCode MCP server started"),KEYWORD&&TOKEN){log(`[MCP Server] Auto-loading knowledge for keyword: ${KEYWORD} (lang: ${LANGUAGE})`);const e=await getPromptFromPortal(KEYWORD,TOKEN,LANGUAGE);e&&e.success?log(`[MCP Server] Auto-load successful: ${e.title}`):log(`[MCP Server] Auto-load failed for keyword: ${KEYWORD}`)}}}const server=new BlueLampVSCodeServer;server.run().catch(e=>logger.error(e.message));
|
|
2
|
+
const{Server:Server}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:StdioServerTransport}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:CallToolRequestSchema,ListToolsRequestSchema:ListToolsRequestSchema}=require("@modelcontextprotocol/sdk/types.js"),{getPromptFromPortal:getPromptFromPortal,listAvailableMcps:listAvailableMcps,getMcpInfo:getMcpInfo,callMcpTool:callMcpTool,deployUserMcp:deployUserMcp,listUserCreatedMcps:listUserCreatedMcps,deleteUserMcp:deleteUserMcp}=require("./portal-api-client.cjs"),{logger:logger}=require("../utils/logger"),{t:t,setLanguage:setLanguage}=require("../i18n"),path=require("path"),fs=require("fs"),os=require("os"),dotenvLocalPath=path.join(__dirname,"../../.env.local"),dotenvPath=path.join(__dirname,"../../.env");fs.existsSync(dotenvLocalPath)?(require("dotenv").config({path:dotenvLocalPath}),logger.debug("[MCP Server] Loaded .env.local")):fs.existsSync(dotenvPath)&&(require("dotenv").config({path:dotenvPath}),logger.debug("[MCP Server] Loaded .env"));const KEYWORD=process.env.BLUELAMP_KEYWORD||"BL-要件定義",TOKEN=process.env.BLUELAMP_TOKEN,PORTAL_URL=process.env.PORTAL_URL||"https://bluelamp-6clpzmy5pa-an.a.run.app",LANGUAGE=process.env.BLUELAMP_LANGUAGE||"ja";setLanguage(LANGUAGE);const LOG_DIR=path.join(os.homedir(),".bluelamp-vscode","logs"),LOG_FILE=path.join(LOG_DIR,"mcp.log");function log(e){const t=`[${(new Date).toISOString()}] ${e}\n`;fs.appendFileSync(LOG_FILE,t),logger.info(`[MCP Server] ${e}`)}fs.existsSync(LOG_DIR)||fs.mkdirSync(LOG_DIR,{recursive:!0}),log(`MCP Server started - Log file: ${LOG_FILE}`),log("Environment variables:"),log(` KEYWORD: ${KEYWORD}`),log(" TOKEN: "+(TOKEN?TOKEN.substring(0,20)+"...":"NOT SET")),log(` PORTAL_URL: ${PORTAL_URL}`),log(` LANGUAGE: ${LANGUAGE}`),log(` .env file exists: ${fs.existsSync(dotenvPath)}`);class BlueLampVSCodeServer{constructor(){this.server=new Server({name:"bluelamp",version:"1.0.0"},{capabilities:{tools:{}}}),this.setupHandlers()}setupHandlers(){this.server.setRequestHandler(ListToolsRequestSchema,async()=>{log("[MCP Server] ListTools request received");const e={type:"object",properties:{keyword:{type:"string",description:t("mcp.keywordDescription")}},required:["keyword"]};return{tools:[{name:"load_knowledge",description:t("mcp.toolDescriptionLoadKnowledge"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:e},{name:"list_available_mcps",description:t("mcp.toolDescriptionListMcps"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{category:{type:"string",enum:["productivity","development","communication","data","media","custom"],description:t("mcp.listMcpsCategoryDescription")},search:{type:"string",description:t("mcp.listMcpsSearchDescription")}},required:[]}},{name:"get_mcp_info",description:t("mcp.toolDescriptionGetMcpInfo"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:t("mcp.getMcpInfoIdDescription")}},required:["mcp_id"]}},{name:"call_tool",description:t("mcp.toolDescriptionCallTool"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:t("mcp.callToolMcpIdDescription")},tool_name:{type:"string",description:t("mcp.callToolNameDescription")},arguments:{type:"object",description:t("mcp.callToolArgsDescription")}},required:["mcp_id","tool_name"]}},{name:"create_user_mcp",description:t("mcp.toolDescriptionCreateUserMcp"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{name:{type:"string",description:t("mcp.createMcpNameDescription")},description:{type:"string",description:t("mcp.createMcpDescDescription")},tools:{type:"string",description:t("mcp.createMcpToolsDescription")},code:{type:"string",description:t("mcp.createMcpCodeDescription")},category:{type:"string",enum:["productivity","development","communication","data","media","custom"],description:t("mcp.createMcpCategoryDescription")},envVars:{type:"string",description:t("mcp.createMcpEnvVarsDescription")}},required:["name","description","code"]}},{name:"list_user_mcps",description:t("mcp.toolDescriptionListUserMcps"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{},required:[]}},{name:"delete_user_mcp",description:t("mcp.toolDescriptionDeleteUserMcp"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:t("mcp.deleteUserMcpIdDescription")}},required:["mcp_id"]}}]}}),this.server.setRequestHandler(CallToolRequestSchema,async e=>{const{name:r}=e.params;if("load_knowledge"===r){const{keyword:r}=e.params.arguments;log(`[MCP Server] load_knowledge called with keyword: ${r}`),log("[MCP Server] TOKEN: "+(TOKEN?TOKEN.substring(0,20)+"...":"missing")),log(`[MCP Server] PORTAL_URL: ${PORTAL_URL}`),log(`[MCP Server] LANGUAGE: ${LANGUAGE}`);try{const e=await getPromptFromPortal(r,TOKEN,LANGUAGE);if(!e||!e.success){const o=e?.error||t("mcp.knowledgeNotFound",{keyword:r});return log(`[MCP Server] Knowledge loading failed: ${o}`),{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:o})}`}],isError:!0}}const o="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${e.knowledge}${o}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}`}]}}catch(e){return log(`[MCP Server] Exception in load_knowledge: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.knowledgeLoadError",{error:e.message})}`}],isError:!0}}}if("list_available_mcps"===r){const{category:r,search:o}=e.params.arguments||{};log(`[MCP Server] list_available_mcps called: category=${r}, search=${o}`);try{const e=await listAvailableMcps({category:r,search:o},TOKEN,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${t("mcp.listMcpsFailed",{error:e?.error||"Unknown"})}`}],isError:!0};const n=e.data||[];let s=`📋 ${t("mcp.listMcpsTitle")}\n\n`;return 0===n.length?s+=t("mcp.listMcpsEmpty"):n.forEach(e=>{const t=e.tools?.length||0;s+=`**${e.name}** (${e.mcpId})\n`,s+=` ${e.description}\n`,s+=` カテゴリ: ${e.category} | ツール数: ${t} | アクセス: ${e.hasAccess?"✅":"❌"}\n\n`}),{content:[{type:"text",text:s}]}}catch(e){return log(`[MCP Server] list_available_mcps error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("get_mcp_info"===r){const{mcp_id:r}=e.params.arguments;log(`[MCP Server] get_mcp_info called: mcpId=${r}`);try{const e=await getMcpInfo(r,TOKEN,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${t("mcp.getMcpInfoFailed",{error:e?.error||"Unknown"})}`}],isError:!0};const o=e.data;let n=`🔧 **${o.name}** (${o.mcpId})\n\n`;return n+=`${o.description}\n\n`,n+=`カテゴリ: ${o.category}\n`,n+=`アクセス: ${o.hasAccess?"利用可能":"利用不可"}\n`,n+=`ステータス: ${o.status}\n\n`,o.tools&&o.tools.length>0&&(n+=`### ツール一覧 (${o.tools.length})\n\n`,o.tools.forEach(e=>{n+=`- **${e.name}**: ${e.description}\n`,e.inputSchema?.properties&&Object.entries(e.inputSchema.properties).forEach(([e,t])=>{n+=` - \`${e}\` (${t.type||"any"}): ${t.description||""}\n`})})),o.requiredCredentials&&o.requiredCredentials.length>0&&(n+="\n### 必要な認証情報\n",o.requiredCredentials.forEach(e=>{n+=`- **${e.displayName}** (${e.key}): ${e.required?"必須":"任意"}\n`})),{content:[{type:"text",text:n}]}}catch(e){return log(`[MCP Server] get_mcp_info error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("call_tool"===r){const{mcp_id:r,tool_name:o,arguments:n}=e.params.arguments;log(`[MCP Server] call_tool called: mcpId=${r}, tool=${o}`);try{const e=await callMcpTool(r,o,n||{},TOKEN);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${t("mcp.callToolFailed",{error:e?.error||"Unknown"})}`}],isError:!0};const s=e.data;return"string"==typeof s?{content:[{type:"text",text:s}]}:{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){return log(`[MCP Server] call_tool error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("create_user_mcp"===r){const r=e.params.arguments;log(`[MCP Server] create_user_mcp called: name=${r.name}`);try{let e=r.tools;if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=[]}let o=r.envVars;if("string"==typeof o)try{o=JSON.parse(o)}catch(e){o=void 0}const n={code:r.code,name:r.name,description:r.description||"",tools:e||[],category:r.category||"custom",envVars:o},s=await deployUserMcp(n,TOKEN);if(!s||!s.success)return{content:[{type:"text",text:`❌ ${s?.details?`${t("mcp.createMcpScanFailed")}\n${JSON.stringify(s.details,null,2)}`:t("mcp.createMcpFailed",{error:s?.error||"Unknown"})}`}],isError:!0};const c=s.data;return{content:[{type:"text",text:`✅ ${t("mcp.createMcpSuccess")}\n\n**MCP ID**: ${c.mcpId}\n**Endpoint**: ${c.endpoint}\n**有効化**: ${c.enabled?"自動有効化済み":"未有効化"}`}]}}catch(e){return log(`[MCP Server] create_user_mcp error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("list_user_mcps"===r){log("[MCP Server] list_user_mcps called");try{const e=await listUserCreatedMcps(TOKEN);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${e?.error||"Unknown"}`}],isError:!0};const r=e.data?.mcps||[];let o=`📋 ${t("mcp.listUserMcpsTitle")}\n\n`;return 0===r.length?o+=t("mcp.listUserMcpsEmpty"):r.forEach(e=>{o+=`**${e.name}** (${e.mcpId})\n`,o+=` ${e.description||""}\n`,o+=` カテゴリ: ${e.category} | ツール数: ${e.toolCount||0} | ステータス: ${e.status}\n`,e.endpoint&&(o+=` エンドポイント: ${e.endpoint}\n`),o+="\n"}),{content:[{type:"text",text:o}]}}catch(e){return log(`[MCP Server] list_user_mcps error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("delete_user_mcp"===r){const{mcp_id:r}=e.params.arguments;log(`[MCP Server] delete_user_mcp called: mcpId=${r}`);try{const e=await deleteUserMcp(r,TOKEN);return e&&e.success?{content:[{type:"text",text:`✅ ${t("mcp.deleteUserMcpSuccess",{mcpId:r})}`}]}:{content:[{type:"text",text:`❌ ${t("mcp.deleteUserMcpFailed",{error:e?.error||"Unknown"})}`}],isError:!0}}catch(e){return log(`[MCP Server] delete_user_mcp error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}return{content:[{type:"text",text:`Unknown tool: ${e.params.name}`}],isError:!0}})}async run(){const e=new StdioServerTransport;if(await this.server.connect(e),logger.info("BlueLamp VSCode MCP server started"),KEYWORD&&TOKEN){log(`[MCP Server] Auto-loading knowledge for keyword: ${KEYWORD} (lang: ${LANGUAGE})`);const e=await getPromptFromPortal(KEYWORD,TOKEN,LANGUAGE);e&&e.success?log(`[MCP Server] Auto-load successful: ${e.title}`):log(`[MCP Server] Auto-load failed for keyword: ${KEYWORD}`)}}}const server=new BlueLampVSCodeServer;server.run().catch(e=>logger.error(e.message));
|