bluelamp 5.1.0 → 5.3.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/lib/i18n/ja.json CHANGED
@@ -5,17 +5,17 @@
5
5
  "undefinedCommand": "未定義のコマンド: {commandName}",
6
6
  "customSlotConfig": "カスタムスロット設定: slot{slotNumber} → \"{keyword}\"",
7
7
  "slotConfigError": "スロット設定取得エラー、デフォルトを使用: {error}",
8
- "brandTitle": "む す ひ — 産霊",
8
+ "brandTitle": "B l u e L a m p",
9
9
  "commandInfo": "コマンド: {commandName}{slotInfo}",
10
10
  "debugMode": "デバッグモード: 有効",
11
11
  "logFile": "ログファイル: {path}",
12
12
  "promptInfo": "プロンプト: {keyword}",
13
- "initialPromptTitle": "さあ、産み出そう",
14
- "initialPromptLoadContext": "このセッションではむすひMCPの参考資料 \"{keyword}\" が利用可能です。\nload_knowledge ツールで \"{keyword}\" を読み込んでから始めてください。",
13
+ "initialPromptTitle": "さあ始めましょう",
14
+ "initialPromptLoadContext": "このセッションではBlueLamp MCPの参考資料 \"{keyword}\" が利用可能です。\nload_knowledge ツールで \"{keyword}\" を読み込んでから始めてください。",
15
15
  "initialPromptKeywordNote": "MCPツールがまだ読み込まれていない場合は、少し待ってから再度お試しください。",
16
16
  "initialPromptReady": "",
17
17
  "launchingClaude": "Claude 起動中...",
18
- "mcpServer": "MCP Server: むすひ",
18
+ "mcpServer": "MCP Server: BlueLamp",
19
19
  "knowledge": "Knowledge: {keyword}",
20
20
  "debugInfo": "デバッグ情報:",
21
21
  "debugCommand": "コマンド: {command}",
@@ -26,14 +26,14 @@
26
26
  "debugEnvPortal": "環境変数 PORTAL_URL: {url}",
27
27
  "debugConfigContent": "MCP設定ファイル内容:",
28
28
  "claudeStartError": "Claude 起動エラー: {error}",
29
- "sessionEnd": "む す ひ セッション終了",
30
- "shuttingDown": "む す ひ を終了しています...",
29
+ "sessionEnd": "BlueLamp セッション終了",
30
+ "shuttingDown": "BlueLamp を終了しています...",
31
31
  "unexpectedError": "予期しないエラー: {error}",
32
- "authActivated": "むすひ に接続しました",
33
- "extendedMode": "むすひ に接続しました"
32
+ "authActivated": "BlueLamp に接続しました",
33
+ "extendedMode": "BlueLamp に接続しました"
34
34
  },
35
35
  "auth": {
36
- "portalAuthTitle": "む す ひ ─ 認証",
36
+ "portalAuthTitle": "BlueLamp ─ 認証",
37
37
  "checkingSavedToken": "保存されたトークンを確認中...",
38
38
  "validationResult": "検証結果:",
39
39
  "existingTokenValid": "既存のトークンが有効です",
@@ -54,7 +54,7 @@
54
54
  "roleLabel": "ロール: {role}",
55
55
  "usingCachedToken": "保存済みトークンを使用",
56
56
  "firstLoginComplete": "初回ログイン完了",
57
- "logoutTitle": "むすひ VSCode - ログアウト",
57
+ "logoutTitle": "BlueLamp - ログアウト",
58
58
  "confirmLogout": "ログイン情報を削除しますか? (y/N): ",
59
59
  "logoutSuccess": "ログイン情報を削除しました",
60
60
  "logoutNotFound": "ログイン情報が見つかりません",
@@ -89,11 +89,11 @@
89
89
  "knowledgeNotFound": "キーワード \"{keyword}\" に対応する知識が見つかりませんでした",
90
90
  "portalApiFailed": "Portal APIから知識を取得できませんでした: {error}",
91
91
  "knowledgeLoadError": "知識の読み込み中にエラーが発生しました: {error}",
92
- "knowledgeLoadSuccess": "むすひ 専門コンテキスト読み込み完了",
92
+ "knowledgeLoadSuccess": "BlueLamp 専門コンテキスト読み込み完了",
93
93
  "knowledgeApplied": "参考情報の読み込みが完了しました。上記の内容を参考にしてご支援ください。",
94
94
  "violationReportFailed": "違反報告に失敗しました: {error}",
95
95
  "violationReportError": "違反報告中にエラーが発生しました: {error}",
96
- "noAuthToken": "認証トークンが設定されていません。musuhi login コマンドでログインしてください。",
96
+ "noAuthToken": "認証トークンが設定されていません。bluelamp login コマンドでログインしてください。",
97
97
  "portalApiError": "Portal APIエラー (HTTP {status})",
98
98
  "apiReturnedError": "APIがエラーを返しました",
99
99
  "promptNotFoundForKeyword": "キーワード \"{keyword}\" に対応するプロンプトが見つかりませんでした",
@@ -6,13 +6,6 @@ import { google } from 'googleapis';
6
6
  import * as fs from 'node:fs';
7
7
  import * as path from 'node:path';
8
8
 
9
- // むすひ認証チェック
10
- const TOKEN_FILE_PATH = path.join(process.env.HOME || '/tmp', '.musuhi', 'portal-token.enc');
11
- if (!fs.existsSync(TOKEN_FILE_PATH)) {
12
- console.error('❌ むすひ認証が必要です。musuhiコマンドでログインしてください。');
13
- process.exit(1);
14
- }
15
-
16
9
  // --- Config ---
17
10
  const CONFIG_DIR = path.join(process.env.HOME || '/tmp', '.config', 'gmail-mcp');
18
11
  const TOKENS_DIR = path.join(CONFIG_DIR, 'tokens');
@@ -20,7 +13,11 @@ const CREDENTIALS_PATH = path.join(CONFIG_DIR, 'credentials.json');
20
13
  const ACCOUNTS_PATH = path.join(CONFIG_DIR, 'accounts.json');
21
14
  // Legacy single-token path (for migration)
22
15
  const LEGACY_TOKEN_PATH = path.join(CONFIG_DIR, 'token.json');
23
- const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
16
+ const SCOPES = [
17
+ 'https://www.googleapis.com/auth/gmail.readonly',
18
+ 'https://www.googleapis.com/auth/gmail.send',
19
+ 'https://www.googleapis.com/auth/gmail.modify'
20
+ ];
24
21
  const DEFAULT_TEXT_BODY_LIMIT = 10000;
25
22
  // --- Account discovery ---
26
23
  function getAccounts() {
@@ -269,6 +266,38 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
269
266
  required: ['account', 'message_id', 'save_path'],
270
267
  },
271
268
  },
269
+ {
270
+ name: 'gmail_create_draft',
271
+ description: 'Create a Gmail draft message',
272
+ inputSchema: {
273
+ type: 'object',
274
+ properties: {
275
+ account: { type: 'string', description: 'Email account to send from' },
276
+ to: { type: 'string', description: 'Recipient email address' },
277
+ subject: { type: 'string', description: 'Email subject' },
278
+ body: { type: 'string', description: 'Email body (plain text)' },
279
+ cc: { type: 'string', description: 'CC email addresses (optional)' },
280
+ bcc: { type: 'string', description: 'BCC email addresses (optional)' },
281
+ },
282
+ required: ['account', 'to', 'subject', 'body'],
283
+ },
284
+ },
285
+ {
286
+ name: 'gmail_send',
287
+ description: 'Send an email via Gmail',
288
+ inputSchema: {
289
+ type: 'object',
290
+ properties: {
291
+ account: { type: 'string', description: 'Email account to send from' },
292
+ to: { type: 'string', description: 'Recipient email address' },
293
+ subject: { type: 'string', description: 'Email subject' },
294
+ body: { type: 'string', description: 'Email body (plain text)' },
295
+ cc: { type: 'string', description: 'CC email addresses (optional)' },
296
+ bcc: { type: 'string', description: 'BCC email addresses (optional)' },
297
+ },
298
+ required: ['account', 'to', 'subject', 'body'],
299
+ },
300
+ },
272
301
  {
273
302
  name: 'batch_search_receipts',
274
303
  description: 'Search for multiple receipts at once. Provide vendor/date/amount items and search all accounts.',
@@ -467,6 +496,60 @@ ${htmlBody}
467
496
  fs.writeFileSync(save_path, fullHtml, 'utf-8');
468
497
  return { content: [{ type: 'text', text: `Saved email HTML (${fullHtml.length} chars) to ${save_path}` }] };
469
498
  }
499
+ case 'gmail_create_draft': {
500
+ const { account, to, subject, body, cc, bcc } = args;
501
+ const auth = getAuthenticatedClient(account);
502
+ if (!auth) {
503
+ return {
504
+ content: [{ type: 'text', text: `Account ${account} not authenticated. Use authorize_account first.` }],
505
+ isError: true,
506
+ };
507
+ }
508
+ const gmail = getGmail(auth);
509
+ const lines = [
510
+ `To: ${to}`,
511
+ ...(cc ? [`Cc: ${cc}`] : []),
512
+ ...(bcc ? [`Bcc: ${bcc}`] : []),
513
+ `Subject: ${subject}`,
514
+ 'Content-Type: text/plain; charset=utf-8',
515
+ '',
516
+ body,
517
+ ];
518
+ const raw = Buffer.from(lines.join('\r\n')).toString('base64')
519
+ .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
520
+ const draft = await gmail.users.drafts.create({
521
+ userId: 'me',
522
+ requestBody: { message: { raw } },
523
+ });
524
+ return { content: [{ type: 'text', text: `Draft created. ID: ${draft.data.id}` }] };
525
+ }
526
+ case 'gmail_send': {
527
+ const { account, to, subject, body, cc, bcc } = args;
528
+ const auth = getAuthenticatedClient(account);
529
+ if (!auth) {
530
+ return {
531
+ content: [{ type: 'text', text: `Account ${account} not authenticated. Use authorize_account first.` }],
532
+ isError: true,
533
+ };
534
+ }
535
+ const gmail = getGmail(auth);
536
+ const lines = [
537
+ `To: ${to}`,
538
+ ...(cc ? [`Cc: ${cc}`] : []),
539
+ ...(bcc ? [`Bcc: ${bcc}`] : []),
540
+ `Subject: ${subject}`,
541
+ 'Content-Type: text/plain; charset=utf-8',
542
+ '',
543
+ body,
544
+ ];
545
+ const raw = Buffer.from(lines.join('\r\n')).toString('base64')
546
+ .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
547
+ const sent = await gmail.users.messages.send({
548
+ userId: 'me',
549
+ requestBody: { raw },
550
+ });
551
+ return { content: [{ type: 'text', text: `Email sent. Message ID: ${sent.data.id}` }] };
552
+ }
470
553
  case 'batch_search_receipts': {
471
554
  const { items, date_tolerance_days = 7 } = args;
472
555
  const authenticatedAccounts = getAuthenticatedAccounts();
@@ -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,listAvailableMcps:listAvailableMcps,downloadMcpPackage:downloadMcpPackage}=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.MUSUHI_KEYWORD||"BL-要件定義",TOKEN=process.env.MUSUHI_TOKEN,isDevEnv="development"===process.env.NODE_ENV||process.env.PORTAL_URL&&process.env.PORTAL_URL.includes("localhost"),PORTAL_URL=process.env.PORTAL_URL||(isDevEnv?"http://localhost:8432":"https://motohi-api-235426778039.asia-northeast1.run.app"),LANGUAGE=process.env.MUSUHI_LANGUAGE||"ja";setLanguage(LANGUAGE);const LOG_DIR=path.join(os.homedir(),".musuhi","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 MusuhiVSCodeServer{constructor(){this.server=new Server({name:"musuhi",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_mcps",description:"インストール可能なMCP一覧を取得します。カタログからアクセス可能なMCPを表示します。",annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{category:{type:"string",description:"カテゴリでフィルタ(省略可)"}},required:[]}},{name:"install_mcp",description:"MCPをローカルにインストールします。list_mcpsで取得したmcpIdを指定してください。~/.claude/mcp-servers/ にファイルを展開し、~/.mcp.json に登録します。",annotations:{readOnlyHint:!1,idempotentHint:!0},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:'インストールするMCPのID(例: "my-prompts")'}},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 n=e?.error||t("mcp.knowledgeNotFound",{keyword:r});return log(`[MCP Server] Knowledge loading failed: ${n}`),{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:n})}`}],isError:!0}}const n="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${e.knowledge}${n}\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_mcps"===r){const{category:t}=e.params.arguments||{};log(`[MCP Server] list_mcps called: category=${t||"all"}`);try{const e=t?{category:t}:{},r=await listAvailableMcps(e,TOKEN,LANGUAGE);if(!r||!r.success)return{content:[{type:"text",text:`❌ MCPカタログの取得に失敗: ${r?.error||"Unknown"}`}],isError:!0};const n=r.data||[];if(0===n.length)return{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]};let o=`📦 インストール可能なMCP一覧 (${n.length}件)\n\n`;return n.forEach(e=>{const t=e.hasAccess?"✅":"🔒",r=e.isOfficial?" [公式]":"";o+=`${t} **${e.name}**${r}\n`,o+=` ID: ${e.mcpId}\n`,o+=` ${e.description}\n`,o+=` カテゴリ: ${e.category} | ツール数: ${e.toolCount}\n\n`}),o+='\nインストールするには: install_mcp(mcp_id: "MCP_ID")',{content:[{type:"text",text:o}]}}catch(e){return log(`[MCP Server] list_mcps error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("install_mcp"===r){const{mcp_id:t}=e.params.arguments;log(`[MCP Server] install_mcp called: mcp_id=${t}`);try{const e=await downloadMcpPackage(t,TOKEN);if(!e||!e.success)return{content:[{type:"text",text:`❌ MCPダウンロードに失敗: ${e?.error||"Unknown"}`}],isError:!0};const r=e.data,n=r.files||[];if(0===n.length)return{content:[{type:"text",text:"❌ MCPパッケージにファイルが含まれていません。"}],isError:!0};const o=path.join(os.homedir(),".claude","mcp-servers",t);fs.mkdirSync(o,{recursive:!0});for(const e of n){const t=path.join(o,e.path),r=path.dirname(t);fs.mkdirSync(r,{recursive:!0}),fs.writeFileSync(t,e.content,"utf-8")}log(`[MCP Server] install_mcp: ${n.length} files written to ${o}`);const s=r.packageSource||{};if(s.needsNpmInstall&&fs.existsSync(path.join(o,"package.json"))){log(`[MCP Server] install_mcp: running npm install in ${o}`);const{execSync:e}=require("child_process");try{e("npm install --production",{cwd:o,timeout:6e4,stdio:"pipe"}),log("[MCP Server] install_mcp: npm install completed")}catch(e){return log(`[MCP Server] install_mcp: npm install failed: ${e.message}`),{content:[{type:"text",text:`⚠️ ファイル展開は完了しましたが、npm installに失敗しました: ${e.message}\n手動で cd ${o} && npm install を実行してください。`}]}}}const i=path.join(os.homedir(),".mcp.json");let c={mcpServers:{}};if(fs.existsSync(i))try{c=JSON.parse(fs.readFileSync(i,"utf-8")),c.mcpServers||(c.mcpServers={})}catch(e){log("[MCP Server] install_mcp: failed to parse ~/.mcp.json, creating new")}const a=s.entryPoint||"lib/index.cjs",l=s.configTarget||"home-mcp";if("home-mcp"===l){c.mcpServers[t]={command:"node",args:[path.join(o,a)],env:{}};const e=s.requiredEnvVars||[];for(const r of e)c.mcpServers[t].env[r]=process.env[r]||`<SET_YOUR_${r}>`;fs.writeFileSync(i,JSON.stringify(c,null,2),"utf-8"),log("[MCP Server] install_mcp: registered in ~/.mcp.json")}let p=`✅ MCP「${s.name||t}」をインストールしました\n\n`;p+=`📁 インストール先: ${o}\n`,p+=`📄 ファイル数: ${n.length}\n`,"home-mcp"===l&&(p+="⚙️ ~/.mcp.json に登録済み\n"),s.installNotes&&(p+=`\n📝 ${s.installNotes}\n`);const d=s.requiredEnvVars||[];if(d.length>0){const e=d.filter(e=>!process.env[e]);e.length>0&&(p+="\n⚠️ 以下の環境変数を ~/.mcp.json で設定してください:\n",e.forEach(e=>{p+=` - ${e}\n`}))}return p+="\nClaude Codeを再起動すると使用可能になります。",{content:[{type:"text",text:p}]}}catch(e){return log(`[MCP Server] install_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("むすひ 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 MusuhiVSCodeServer;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,downloadMcpPackage:downloadMcpPackage}=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.MUSUHI_KEYWORD||"BL-要件定義",TOKEN=process.env.MUSUHI_TOKEN,isDevEnv="development"===process.env.NODE_ENV||process.env.PORTAL_URL&&process.env.PORTAL_URL.includes("localhost"),PORTAL_URL=process.env.PORTAL_URL||(isDevEnv?"http://localhost:8432":"https://motohi-api-235426778039.asia-northeast1.run.app"),LANGUAGE=process.env.MUSUHI_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 BlueLampServer{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_mcps",description:"インストール可能なMCP一覧を取得します。カタログからアクセス可能なMCPを表示します。",annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{category:{type:"string",description:"カテゴリでフィルタ(省略可)"}},required:[]}},{name:"install_mcp",description:"MCPをローカルにインストールします。list_mcpsで取得したmcpIdを指定してください。~/.claude/mcp-servers/ にファイルを展開し、~/.mcp.json に登録します。",annotations:{readOnlyHint:!1,idempotentHint:!0},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:'インストールするMCPのID(例: "my-prompts")'}},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 n=e?.error||t("mcp.knowledgeNotFound",{keyword:r});return log(`[MCP Server] Knowledge loading failed: ${n}`),{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:n})}`}],isError:!0}}const n="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${e.knowledge}${n}\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_mcps"===r){const{category:t}=e.params.arguments||{};log(`[MCP Server] list_mcps called: category=${t||"all"}`);try{const e=t?{category:t}:{},r=await listAvailableMcps(e,TOKEN,LANGUAGE);if(!r||!r.success)return{content:[{type:"text",text:`❌ MCPカタログの取得に失敗: ${r?.error||"Unknown"}`}],isError:!0};const n=r.data||[];if(0===n.length)return{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]};let o=`📦 インストール可能なMCP一覧 (${n.length}件)\n\n`;return n.forEach(e=>{const t=e.hasAccess?"✅":"🔒",r=e.isOfficial?" [公式]":"";o+=`${t} **${e.name}**${r}\n`,o+=` ID: ${e.mcpId}\n`,o+=` ${e.description}\n`,o+=` カテゴリ: ${e.category} | ツール数: ${e.toolCount}\n\n`}),o+='\nインストールするには: install_mcp(mcp_id: "MCP_ID")',{content:[{type:"text",text:o}]}}catch(e){return log(`[MCP Server] list_mcps error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("install_mcp"===r){const{mcp_id:t}=e.params.arguments;log(`[MCP Server] install_mcp called: mcp_id=${t}`);try{const e=await downloadMcpPackage(t,TOKEN);if(!e||!e.success)return{content:[{type:"text",text:`❌ MCPダウンロードに失敗: ${e?.error||"Unknown"}`}],isError:!0};const r=e.data,n=r.files||[];if(0===n.length)return{content:[{type:"text",text:"❌ MCPパッケージにファイルが含まれていません。"}],isError:!0};const o=path.join(os.homedir(),".claude","mcp-servers",t);fs.mkdirSync(o,{recursive:!0});for(const e of n){const t=path.join(o,e.path),r=path.dirname(t);fs.mkdirSync(r,{recursive:!0}),fs.writeFileSync(t,e.content,"utf-8")}log(`[MCP Server] install_mcp: ${n.length} files written to ${o}`);const s=r.packageSource||{};if(s.needsNpmInstall&&fs.existsSync(path.join(o,"package.json"))){log(`[MCP Server] install_mcp: running npm install in ${o}`);const{execSync:e}=require("child_process");try{e("npm install --production",{cwd:o,timeout:6e4,stdio:"pipe"}),log("[MCP Server] install_mcp: npm install completed")}catch(e){return log(`[MCP Server] install_mcp: npm install failed: ${e.message}`),{content:[{type:"text",text:`⚠️ ファイル展開は完了しましたが、npm installに失敗しました: ${e.message}\n手動で cd ${o} && npm install を実行してください。`}]}}}const a=path.join(os.homedir(),".mcp.json");let c={mcpServers:{}};if(fs.existsSync(a))try{c=JSON.parse(fs.readFileSync(a,"utf-8")),c.mcpServers||(c.mcpServers={})}catch(e){log("[MCP Server] install_mcp: failed to parse ~/.mcp.json, creating new")}const i=s.entryPoint||"lib/index.cjs",l=s.configTarget||"home-mcp";if("home-mcp"===l){c.mcpServers[t]={command:"node",args:[path.join(o,i)],env:{}};const e=s.requiredEnvVars||[];for(const r of e)c.mcpServers[t].env[r]=process.env[r]||`<SET_YOUR_${r}>`;fs.writeFileSync(a,JSON.stringify(c,null,2),"utf-8"),log("[MCP Server] install_mcp: registered in ~/.mcp.json")}let p=`✅ MCP「${s.name||t}」をインストールしました\n\n`;p+=`📁 インストール先: ${o}\n`,p+=`📄 ファイル数: ${n.length}\n`,"home-mcp"===l&&(p+="⚙️ ~/.mcp.json に登録済み\n"),s.installNotes&&(p+=`\n📝 ${s.installNotes}\n`);const d=s.requiredEnvVars||[];if(d.length>0){const e=d.filter(e=>!process.env[e]);e.length>0&&(p+="\n⚠️ 以下の環境変数を ~/.mcp.json で設定してください:\n",e.forEach(e=>{p+=` - ${e}\n`}))}return p+="\nClaude Codeを再起動すると使用可能になります。",{content:[{type:"text",text:p}]}}catch(e){return log(`[MCP Server] install_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 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 BlueLampServer;server.run().catch(e=>logger.error(e.message));