bluelamp 6.2.0 → 6.3.2

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.
@@ -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"),{spawn:spawn}=require("child_process"),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");fs.existsSync(LOG_DIR)||fs.mkdirSync(LOG_DIR,{recursive:!0});const REGISTRY_DIR=path.join(os.homedir(),".bluelamp"),REGISTRY_FILE=path.join(REGISTRY_DIR,"registry.json");function log(e){const t=`[${(new Date).toISOString()}] ${e}\n`;fs.appendFileSync(LOG_FILE,t),logger.info(`[MCP Server] ${e}`)}function loadRegistry(){if(!fs.existsSync(REGISTRY_FILE))return{};try{return JSON.parse(fs.readFileSync(REGISTRY_FILE,"utf-8"))}catch(e){return log(`[Registry] Parse error: ${e.message}`),{}}}function saveRegistry(e){fs.existsSync(REGISTRY_DIR)||fs.mkdirSync(REGISTRY_DIR,{recursive:!0}),fs.writeFileSync(REGISTRY_FILE,JSON.stringify(e,null,2)+"\n")}function searchServices(e,t){const r=e.toLowerCase().split(/\s+/),s=[];for(const[e,o]of Object.entries(t)){let t=0;const n=[e,o.name||"",o.description||"",...o.tags||[]].join(" ").toLowerCase();for(const e of r)n.includes(e)&&t++;t>0&&s.push({id:e,service:o,score:t})}return s.sort((e,t)=>t.score-e.score),s}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}`);class ChildMcpProcess{constructor(e,t,r,s){this.serviceId=e,this.command=t,this.args=(r||[]).map(e=>"string"==typeof e?e.replace(/^~\//,os.homedir()+"/"):e),this.env={...process.env,...s},this.process=null,this.tools=[],this.requestId=0,this.pending=new Map,this.buffer=""}async start(){return new Promise((e,t)=>{const r=setTimeout(()=>{t(new Error(`Timeout: "${this.serviceId}" failed to start within 30s`)),this.stop()},3e4);try{this.process=spawn(this.command,this.args,{env:this.env,stdio:["pipe","pipe","pipe"]}),this.process.on("error",e=>{clearTimeout(r),t(new Error(`Spawn error for "${this.serviceId}": ${e.message}`))}),this.process.on("exit",(e,t)=>{log(`[ChildMcp] "${this.serviceId}" exited (code=${e}, signal=${t})`),this.pending.forEach(e=>e.reject(new Error("Process exited"))),this.pending.clear()}),this.process.stderr.on("data",e=>{log(`[ChildMcp:${this.serviceId}:stderr] ${e.toString().trim()}`)}),this.process.stdout.on("data",e=>{this.buffer+=e.toString(),this._processBuffer()}),this._sendRequest("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"bluelamp-gateway",version:"1.0.0"}}).then(e=>(log(`[ChildMcp] "${this.serviceId}" initialized: ${JSON.stringify(e.serverInfo||{})}`),this._sendNotification("notifications/initialized",{}),this._sendRequest("tools/list",{}))).then(t=>{const s=t.tools||[];this.tools=s.map(e=>({...e,name:`${this.serviceId}__${e.name}`,_originalName:e.name})),log(`[ChildMcp] "${this.serviceId}" ready: ${this.tools.length} tools`),clearTimeout(r),e(this.tools)}).catch(e=>{clearTimeout(r),t(e)})}catch(e){clearTimeout(r),t(e)}})}async callTool(e,t){return await this._sendRequest("tools/call",{name:e,arguments:t})}stop(){this.process&&(this.process.kill(),this.process=null),this.pending.forEach(e=>e.reject(new Error("Process stopped"))),this.pending.clear()}_sendRequest(e,t){return new Promise((r,s)=>{const o=++this.requestId,n=JSON.stringify({jsonrpc:"2.0",id:o,method:e,params:t})+"\n";this.pending.set(o,{resolve:r,reject:s});try{this.process.stdin.write(n)}catch(e){this.pending.delete(o),s(new Error(`Write error: ${e.message}`))}})}_sendNotification(e,t){const r=JSON.stringify({jsonrpc:"2.0",method:e,params:t})+"\n";try{this.process.stdin.write(r)}catch(e){log(`[ChildMcp] Notification write error: ${e.message}`)}}_processBuffer(){const e=this.buffer.split("\n");this.buffer=e.pop();for(const t of e){const e=t.trim();if(e)try{const t=JSON.parse(e);if(t.id&&this.pending.has(t.id)){const e=this.pending.get(t.id);this.pending.delete(t.id),t.error?e.reject(new Error(`JSON-RPC error: ${JSON.stringify(t.error)}`)):e.resolve(t.result)}}catch(t){log(`[ChildMcp:${this.serviceId}] Parse error: ${t.message} | line: ${e.substring(0,200)}`)}}}}class ChildMcpManager{constructor(){this.children=new Map}async activateExternal(e,t){const r=new ChildMcpProcess(e,t.command,t.args||[],t.env||{}),s=await r.start();return this.children.set(e,{type:"external",process:r,tools:s}),s}deactivate(e){const t=this.children.get(e);t&&("external"===t.type&&t.process&&t.process.stop(),this.children.delete(e))}isActive(e){return this.children.has(e)}getActiveTools(){const e=[];for(const t of this.children.values())e.push(...t.tools||[]);return e}async callTool(e,t){for(const[r,s]of this.children)if("external"===s.type){const r=s.tools.find(t=>t.name===e);if(r)return{found:!0,result:await s.process.callTool(r._originalName,t)}}return{found:!1}}getActiveServiceIds(){return[...this.children.keys()]}}class BlueLampServer{constructor(){this.childManager=new ChildMcpManager,this.server=new Server({name:"bluelamp",version:"2.0.0"},{capabilities:{tools:{listChanged:!0}}}),this.setupHandlers()}checkAuth(){return TOKEN?{ok:!0}:{ok:!1,error:"MUSUHI_TOKEN が設定されていません。BlueLampにログインしてください。"}}buildToolList(){const e=[{name:"load_knowledge",description:t("mcp.toolDescriptionLoadKnowledge"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{keyword:{type:"string",description:t("mcp.keywordDescription")}},required:["keyword"]}},{name:"search_mcp",description:'キーワードでインストール済みMCPサービスを検索します。使いたい機能を自然言語で説明してください。(例: "ブラウザ操作", "メール送信", "画像生成")',inputSchema:{type:"object",properties:{query:{type:"string",description:"検索キーワード(自然言語OK)"}},required:["query"]}},{name:"activate_service",description:"MCPサービスを有効化してツールを追加します。search_mcpで見つけたservice_idを指定してください。always_on: trueで次回以降も自動的に有効化されます。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"},always_on:{type:"boolean",description:"trueにすると常時アクティブ(毎回自動起動)になります"}},required:["service_id"]}},{name:"deactivate_service",description:"MCPサービスの常時アクティブ(alwaysOn)設定を解除します。次回起動時から自動有効化されなくなります。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"}},required:["service_id"]}},{name:"register_service",description:"新しいMCPサービスをゲートウェイに登録します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:'サービスID(英数字、例: "microcms")'},name:{type:"string",description:"サービス表示名"},description:{type:"string",description:"サービスの説明"},tags:{type:"array",items:{type:"string"},description:"検索用タグ"},command:{type:"string",description:'実行コマンド("npx" or "node")'},args:{type:"array",items:{type:"string"},description:"コマンド引数"},env:{type:"object",description:"環境変数"}},required:["service_id","name","description","tags"]}},{name:"browse_mcp_store",description:"MCPストア(Portal)からインストール可能なMCP一覧を取得します。",inputSchema:{type:"object",properties:{category:{type:"string",description:"カテゴリでフィルタ(省略可)"}},required:[]}},{name:"install_from_store",description:"MCPストアからMCPをダウンロードしてインストールします。browse_mcp_storeで見つけたmcp_idを指定してください。",inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:"インストールするMCPのID"}},required:["mcp_id"]}}];return e.push(...this.childManager.getActiveTools()),e}setupHandlers(){this.server.setRequestHandler(ListToolsRequestSchema,async()=>{const e=this.buildToolList(),t=this.childManager.getActiveServiceIds();return log(`[ListTools] ${e.length} tools (active: ${t.join(", ")||"none"})`),{tools:e}}),this.server.setRequestHandler(CallToolRequestSchema,async e=>{const{name:r,arguments:s}=e.params;if(log(`[CallTool] ${r}`),"load_knowledge"===r){const{keyword:e}=s;log(`[load_knowledge] keyword: ${e}`);try{const r=await getPromptFromPortal(e,TOKEN,LANGUAGE);if(!r||!r.success){const s=r?.error||t("mcp.knowledgeNotFound",{keyword:e});return{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:s})}`}],isError:!0}}const s="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${r.knowledge}${s}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}`}]}}catch(e){return log(`[load_knowledge] Exception: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.knowledgeLoadError",{error:e.message})}`}],isError:!0}}}if("search_mcp"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const t=loadRegistry(),r=searchServices(s.query,t);if(0===r.length)return{content:[{type:"text",text:`🔍 「${s.query}」に一致するサービスはありません。\n\n別のキーワードで試すか、register_serviceで新しいサービスを登録してください。`}]};let o=`🔍 「${s.query}」の検索結果(${r.length}件)\n\n`;for(const{id:e,service:t}of r.slice(0,5)){const r=this.childManager.isActive(e),s=t.alwaysOn,n=s?" [常時アクティブ]":"";o+=`${s?"🔒":r?"🟢":"⚪"} **${t.name}**${n} (id: \`${e}\`)\n`,o+=` ${t.description}\n`,t.tags&&(o+=` タグ: ${t.tags.join(", ")}\n`),o+="\n"}return o+='有効化するには: activate_service(service_id: "...", always_on: true で常時アクティブ)',{content:[{type:"text",text:o}]}}if("activate_service"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,always_on:r}=s,o=loadRegistry(),n=o[t];if(!n)return{content:[{type:"text",text:`❌ サービス "${t}" は登録されていません。search_mcpで検索してください。`}],isError:!0};if(r&&(o[t].alwaysOn=!0,saveRegistry(o),log(`[activate] "${t}" set alwaysOn: true`)),this.childManager.isActive(t))return{content:[{type:"text",text:`ℹ️ "${t}" は既に有効です。${r?"(常時アクティブに設定しました)":""}`}]};let i;try{i=await this.childManager.activateExternal(t,n)}catch(e){return log(`[activate] Failed "${t}": ${e.message}`),{content:[{type:"text",text:`❌ "${t}" の起動に失敗しました: ${e.message}`}],isError:!0}}const a=i.map(e=>e.name).join(", "),c=r?" 🔒常時アクティブ":"";log(`[activate] "${t}" → ${i.length} tools${r?" (alwaysOn)":""}. Sending list_changed.`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${n.name}" を有効化しました${c}(ツール${i.length}個追加)\n\n追加ツール: ${a||"(なし)"}`}]}}if("deactivate_service"===r){const{service_id:e}=s,t=loadRegistry(),r=t[e];return r?r.alwaysOn?(t[e].alwaysOn=!1,saveRegistry(t),log(`[deactivate] "${e}" set alwaysOn: false`),{content:[{type:"text",text:`✅ "${e}" の常時アクティブを解除しました。次回起動時から自動有効化されません。`}]}):{content:[{type:"text",text:`ℹ️ "${e}" は常時アクティブではありません。変更の必要はありません。`}]}:{content:[{type:"text",text:`❌ サービス "${e}" は登録されていません。`}],isError:!0}}if("register_service"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,name:r,description:o,tags:n,command:i,args:a,env:c}=s,l=loadRegistry(),p=!!l[t];return l[t]={name:r,description:o,tags:n||[],type:"external",command:i||null,args:a||[],env:c||{}},saveRegistry(l),log(`[register] "${t}" ${p?"updated":"created"}`),{content:[{type:"text",text:`✅ サービス "${r}" を${p?"更新":"登録"}しました (id: ${t})\n\nタグ: ${(n||[]).join(", ")}\n\nsearch_mcp で検索可能になりました。`}]}}if("browse_mcp_store"===r){const{category:e}=s||{};log(`[browse_mcp_store] category=${e||"all"}`);try{const t=e?{category:e}:{},r=await listAvailableMcps(t,TOKEN,LANGUAGE);if(!r||!r.success)return{content:[{type:"text",text:`❌ MCPカタログの取得に失敗: ${r?.error||"Unknown"}`}],isError:!0};const s=r.data||[];if(0===s.length)return{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]};let o=`📦 MCPストア一覧 (${s.length}件)\n\n`;return s.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_from_store(mcp_id: "MCP_ID")',{content:[{type:"text",text:o}]}}catch(e){return log(`[browse_mcp_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("install_from_store"===r){const{mcp_id:e}=s;log(`[install_from_store] mcp_id=${e}`);try{const t=await downloadMcpPackage(e,TOKEN);if(!t||!t.success)return{content:[{type:"text",text:`❌ MCPダウンロードに失敗: ${t?.error||"Unknown"}`}],isError:!0};const r=t.data,s=r.files||[];if(0===s.length)return{content:[{type:"text",text:"❌ MCPパッケージにファイルが含まれていません。"}],isError:!0};const o=path.join(os.homedir(),".claude","mcp-servers",e);fs.mkdirSync(o,{recursive:!0});for(const e of s){const t=path.join(o,e.path);fs.mkdirSync(path.dirname(t),{recursive:!0}),fs.writeFileSync(t,e.content,"utf-8")}log(`[install_from_store] ${s.length} files written to ${o}`);const n=r.packageSource||{};if(n.needsNpmInstall&&fs.existsSync(path.join(o,"package.json"))){const{execSync:e}=require("child_process");try{e("npm install --production",{cwd:o,timeout:6e4,stdio:"pipe"})}catch(e){return{content:[{type:"text",text:`⚠️ ファイル展開完了、npm install失敗: ${e.message}`}]}}}const i=n.entryPoint||"lib/index.cjs",a=loadRegistry();a[e]={name:n.name||e,description:n.description||"",tags:n.tags||[],type:"external",command:"node",args:[path.join(o,i)],env:{}};const c=n.requiredEnvVars||[];for(const t of c)a[e].env[t]=process.env[t]||`<SET_YOUR_${t}>`;saveRegistry(a);let l=`✅ MCP「${n.name||e}」をインストールしました\n\n`;l+=`📁 インストール先: ${o}\n`,l+=`📄 ファイル数: ${s.length}\n`,l+="⚙️ registry.json に登録済み\n",n.installNotes&&(l+=`\n📝 ${n.installNotes}\n`);const p=c.filter(e=>!process.env[e]);return p.length>0&&(l+="\n⚠️ 以下の環境変数をregistry.jsonで設定してください:\n",p.forEach(e=>{l+=` - ${e}\n`})),l+=`\nactivate_service(service_id: "${e}") で有効化できます。`,{content:[{type:"text",text:l}]}}catch(e){return log(`[install_from_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}try{const{found:e,result:t}=await this.childManager.callTool(r,s);if(e)return t&&t.content?t:{content:[{type:"text",text:String(t)}]}}catch(e){return log(`[callTool] Error proxying "${r}": ${e.message}`),{content:[{type:"text",text:`❌ ツール実行エラー: ${e.message}`}],isError:!0}}return{content:[{type:"text",text:`❌ Unknown tool: ${r}`}],isError:!0}})}async autoActivate(){const e=loadRegistry();for(const[t,r]of Object.entries(e))if(r.autoActivate||r.alwaysOn){log(`[autoActivate] Activating "${t}" (${r.alwaysOn?"alwaysOn":"autoActivate"})...`);try{log(`[autoActivate] "${t}" ready: ${(await this.childManager.activateExternal(t,r)).length} tools`)}catch(e){log(`[autoActivate] "${t}" failed: ${e.message}`)}}}async run(){const e=new StdioServerTransport;if(await this.server.connect(e),log("[Gateway] BlueLamp Gateway v2.0.0 started."),await this.autoActivate(),this.childManager.getActiveServiceIds().length>0)try{await this.server.notification({method:"notifications/tools/list_changed"}),log("[Gateway] Initial list_changed sent after autoActivate.")}catch(e){log(`[Gateway] Initial list_changed error: ${e.message}`)}if(KEYWORD&&TOKEN){log(`[Gateway] Auto-loading knowledge for keyword: ${KEYWORD}`);const e=await getPromptFromPortal(KEYWORD,TOKEN,LANGUAGE);e&&e.success?log(`[Gateway] Auto-load successful: ${e.title}`):log(`[Gateway] Auto-load failed for keyword: ${KEYWORD}`)}}}const server=new BlueLampServer;server.run().catch(e=>{log(`[Gateway] Fatal: ${e.message}`),process.exit(1)});
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,getGatewayConfig:getGatewayConfig,searchMcpServices:searchMcpServices,installMcpService:installMcpService,setServiceAlwaysOn:setServiceAlwaysOn,uninstallMcpService:uninstallMcpService,updateMcpEnv:updateMcpEnv,callMcpToolProxy:callMcpToolProxy}=require("./portal-api-client.cjs"),{logger:logger}=require("../utils/logger"),{t:t,setLanguage:setLanguage}=require("../i18n"),{spawn:spawn}=require("child_process"),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}`);const LEGACY_REGISTRY=path.join(os.homedir(),".bluelamp","registry.json");if(fs.existsSync(LEGACY_REGISTRY))try{fs.unlinkSync(LEGACY_REGISTRY),log(`[startup] Legacy registry.json removed: ${LEGACY_REGISTRY}`)}catch(e){log(`[startup] Failed to remove legacy registry.json: ${e.message}`)}class ChildMcpProcess{constructor(e,t,r,s){this.serviceId=e,this.command=t,this.args=(r||[]).map(e=>"string"==typeof e?e.replace(/^~\//,os.homedir()+"/"):e),this.env={...process.env,...s},this.process=null,this.tools=[],this.requestId=0,this.pending=new Map,this.buffer=""}async start(){return new Promise((e,t)=>{const r=setTimeout(()=>{t(new Error(`Timeout: "${this.serviceId}" failed to start within 30s`)),this.stop()},3e4);try{this.process=spawn(this.command,this.args,{env:this.env,stdio:["pipe","pipe","pipe"]}),this.process.on("error",e=>{clearTimeout(r),t(new Error(`Spawn error for "${this.serviceId}": ${e.message}`))}),this.process.on("exit",(e,t)=>{log(`[ChildMcp] "${this.serviceId}" exited (code=${e}, signal=${t})`),this.pending.forEach(e=>e.reject(new Error("Process exited"))),this.pending.clear()}),this.process.stderr.on("data",e=>{log(`[ChildMcp:${this.serviceId}:stderr] ${e.toString().trim()}`)}),this.process.stdout.on("data",e=>{this.buffer+=e.toString(),this._processBuffer()}),this._sendRequest("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"bluelamp-gateway",version:"1.0.0"}}).then(e=>(log(`[ChildMcp] "${this.serviceId}" initialized: ${JSON.stringify(e.serverInfo||{})}`),this._sendNotification("notifications/initialized",{}),this._sendRequest("tools/list",{}))).then(t=>{const s=t.tools||[];this.tools=s.map(e=>({...e,name:`${this.serviceId}__${e.name}`,_originalName:e.name})),log(`[ChildMcp] "${this.serviceId}" ready: ${this.tools.length} tools`),clearTimeout(r),e(this.tools)}).catch(e=>{clearTimeout(r),t(e)})}catch(e){clearTimeout(r),t(e)}})}async callTool(e,t){return await this._sendRequest("tools/call",{name:e,arguments:t})}stop(){this.process&&(this.process.kill(),this.process=null),this.pending.forEach(e=>e.reject(new Error("Process stopped"))),this.pending.clear()}_sendRequest(e,t){return new Promise((r,s)=>{const o=++this.requestId,n=JSON.stringify({jsonrpc:"2.0",id:o,method:e,params:t})+"\n";this.pending.set(o,{resolve:r,reject:s});try{this.process.stdin.write(n)}catch(e){this.pending.delete(o),s(new Error(`Write error: ${e.message}`))}})}_sendNotification(e,t){const r=JSON.stringify({jsonrpc:"2.0",method:e,params:t})+"\n";try{this.process.stdin.write(r)}catch(e){log(`[ChildMcp] Notification write error: ${e.message}`)}}_processBuffer(){const e=this.buffer.split("\n");this.buffer=e.pop();for(const t of e){const e=t.trim();if(e)try{const t=JSON.parse(e);if(t.id&&this.pending.has(t.id)){const e=this.pending.get(t.id);this.pending.delete(t.id),t.error?e.reject(new Error(`JSON-RPC error: ${JSON.stringify(t.error)}`)):e.resolve(t.result)}}catch(t){log(`[ChildMcp:${this.serviceId}] Parse error: ${t.message} | line: ${e.substring(0,200)}`)}}}}class ChildMcpManager{constructor(){this.children=new Map}async activateExternal(e,t){const r=new ChildMcpProcess(e,t.command,t.args||[],t.env||{}),s=await r.start();return this.children.set(e,{type:"external",process:r,tools:s,service:t}),s}deactivate(e){const t=this.children.get(e);t&&("external"===t.type&&t.process&&t.process.stop(),this.children.delete(e))}isActive(e){return this.children.has(e)}getActiveTools(){const e=[];for(const t of this.children.values())e.push(...t.tools||[]);return e}async callTool(e,t){for(const[r,s]of this.children)if("external"===s.type){const r=s.tools.find(t=>t.name===e);if(r)return{found:!0,result:await s.process.callTool(r._originalName,t)}}return{found:!1}}getActiveServiceIds(){return[...this.children.keys()]}}class BlueLampServer{constructor(){this.childManager=new ChildMcpManager,this.server=new Server({name:"bluelamp",version:"2.0.0"},{capabilities:{tools:{listChanged:!0}}}),this.setupHandlers()}checkAuth(){return TOKEN?{ok:!0}:{ok:!1,error:"MUSUHI_TOKEN が設定されていません。BlueLampにログインしてください。"}}buildToolList(){const e=[{name:"load_knowledge",description:t("mcp.toolDescriptionLoadKnowledge"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{keyword:{type:"string",description:t("mcp.keywordDescription")}},required:["keyword"]}},{name:"search_mcp",description:'キーワードでインストール済みMCPサービスを検索します。使いたい機能を自然言語で説明してください。(例: "ブラウザ操作", "メール送信", "画像生成")',inputSchema:{type:"object",properties:{query:{type:"string",description:"検索キーワード(自然言語OK)"}},required:["query"]}},{name:"activate_service",description:"MCPサービスを有効化してツールを追加します。search_mcpで見つけたservice_idを指定してください。always_on: trueで次回以降も自動的に有効化されます。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"},always_on:{type:"boolean",description:"trueにすると常時アクティブ(毎回自動起動)になります"}},required:["service_id"]}},{name:"deactivate_service",description:"MCPサービスの常時アクティブ(alwaysOn)設定を解除します。次回起動時から自動有効化されなくなります。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"}},required:["service_id"]}},{name:"restart_service",description:"MCPサービスのプロセスを再起動します。コード更新や設定変更の反映に使用します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"}},required:["service_id"]}},{name:"unregister_service",description:"MCPサービスをレジストリから完全に削除します。プロセスも停止します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"削除するサービスID"}},required:["service_id"]}},{name:"register_service",description:"新しいMCPサービスをゲートウェイに登録します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:'サービスID(英数字、例: "microcms")'},name:{type:"string",description:"サービス表示名"},description:{type:"string",description:"サービスの説明"},tags:{type:"array",items:{type:"string"},description:"検索用タグ"},command:{type:"string",description:'実行コマンド("npx" or "node")'},args:{type:"array",items:{type:"string"},description:"コマンド引数"},env:{type:"object",description:"環境変数"}},required:["service_id","name","description","tags"]}},{name:"browse_mcp_store",description:"MCPストア(Portal)からインストール可能なMCP一覧を取得します。",inputSchema:{type:"object",properties:{category:{type:"string",description:"カテゴリでフィルタ(省略可)"}},required:[]}},{name:"install_from_store",description:"MCPストアからMCPをダウンロードしてインストールします。browse_mcp_storeで見つけたmcp_idを指定してください。",inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:"インストールするMCPのID"}},required:["mcp_id"]}},{name:"save_mcp_env",description:"MCPの環境変数(APIキー等)をサーバーに安全に保存します。ローカルには保存されません。次回起動時にサーバーから自動取得されます。解約時はアクセスできなくなります。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"MCP サービスID(例: heygen, gemini-image)"},env:{type:"object",description:'環境変数のキーと値(例: {"HEYGEN_API_KEY": "sk_xxx"})',additionalProperties:{type:"string"}}},required:["service_id","env"]}}];if(e.push(...this.childManager.getActiveTools()),this._cloudProxyTools)for(const[t,r]of this._cloudProxyTools)e.push({name:t,description:r.description||`[${r.mcpId}] ${r.toolName}`,inputSchema:r.inputSchema||{type:"object",properties:{}}});return e}setupHandlers(){this.server.setRequestHandler(ListToolsRequestSchema,async()=>{const e=this.buildToolList(),t=this.childManager.getActiveServiceIds();return log(`[ListTools] ${e.length} tools (active: ${t.join(", ")||"none"})`),{tools:e}}),this.server.setRequestHandler(CallToolRequestSchema,async e=>{const{name:r,arguments:s}=e.params;if(log(`[CallTool] ${r}`),"load_knowledge"===r){const{keyword:e}=s;log(`[load_knowledge] keyword: ${e}`);try{const r=await getPromptFromPortal(e,TOKEN,LANGUAGE);if(!r||!r.success){const s=r?.error||t("mcp.knowledgeNotFound",{keyword:e});return{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:s})}`}],isError:!0}}const s="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${r.knowledge}${s}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}`}]}}catch(e){return log(`[load_knowledge] Exception: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.knowledgeLoadError",{error:e.message})}`}],isError:!0}}}if("search_mcp"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};if(!TOKEN)return{content:[{type:"text",text:"❌ 未認証です。MCPカタログの検索にはログインが必要です。"}],isError:!0};let t=[];try{const e=await searchMcpServices(s.query,TOKEN);if(!e.success)return{content:[{type:"text",text:`❌ MCPカタログ検索に失敗: ${e.error||"Unknown"}`}],isError:!0};t=(e.data||[]).map(e=>({id:e.mcp_id,service:{name:e.name,description:e.description,tags:[],alwaysOn:e.alwaysOn||!1},isInstalled:e.isInstalled||!1}))}catch(e){return log(`[search_mcp] Server search failed: ${e.message}`),{content:[{type:"text",text:`❌ MCPカタログ検索に失敗: ${e.message}`}],isError:!0}}if(0===t.length)return{content:[{type:"text",text:`🔍 「${s.query}」に一致するサービスはありません。\n\n別のキーワードで試すか、browse_mcp_storeでカタログを確認してください。`}]};let r=`🔍 「${s.query}」の検索結果(${t.length}件)\n\n`;for(const e of t.slice(0,10)){const t=e.id,s=e.service,o=this.childManager.isActive(t),n=s.alwaysOn,i=e.isInstalled?"":" [未インストール]",c=n?" [常時アクティブ]":"";r+=`${n?"🔒":o?"🟢":"⚪"} **${s.name}**${c}${i} (id: \`${t}\`)\n`,r+=` ${s.description}\n\n`}return r+="有効化: activate_service | インストール: install_from_store",{content:[{type:"text",text:r}]}}if("activate_service"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,always_on:r}=s,o=await getGatewayConfig(TOKEN);if(!o.success)return{content:[{type:"text",text:`❌ サーバー設定の取得に失敗: ${o.error||"Unknown"}`}],isError:!0};const n=(o.data?.services||[]).find(e=>e.mcpId===t);if(!n)return{content:[{type:"text",text:`❌ サービス "${t}" はインストールされていません。install_from_storeでインストールしてください。`}],isError:!0};if(r&&(setServiceAlwaysOn(t,!0,TOKEN).catch(e=>log(`[activate] Server sync failed: ${e.message}`)),log(`[activate] "${t}" set alwaysOn: true`)),this.childManager.isActive(t))return{content:[{type:"text",text:`ℹ️ "${t}" は既に有効です。${r?"(常時アクティブに設定しました)":""}`}]};const i=path.join(os.homedir(),".claude","mcp-servers",t),c=n.entryPoint||"dist/index.js",a={name:n.name,description:n.description,type:"external",command:n.command||"node",args:n.args&&n.args.length?n.args:[path.join(i,c)],env:n.env||{}};let l;try{l=await this.childManager.activateExternal(t,a)}catch(e){return log(`[activate] Failed "${t}": ${e.message}`),{content:[{type:"text",text:`❌ "${t}" の起動に失敗しました: ${e.message}`}],isError:!0}}const p=l.map(e=>e.name).join(", "),d=r?" 🔒常時アクティブ":"";log(`[activate] "${t}" → ${l.length} tools${r?" (alwaysOn)":""}. Sending list_changed.`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${a.name}" を有効化しました${d}(ツール${l.length}個追加)\n\n追加ツール: ${p||"(なし)"}`}]}}if("deactivate_service"===r){const{service_id:e}=s;if(!TOKEN)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const t=await setServiceAlwaysOn(e,!1,TOKEN);return t.success?(log(`[deactivate] "${e}" set alwaysOn: false`),{content:[{type:"text",text:`✅ "${e}" の常時アクティブを解除しました。次回起動時から自動有効化されません。`}]}):{content:[{type:"text",text:`❌ "${e}" の解除に失敗: ${t.error||"Unknown"}`}],isError:!0}}if("restart_service"===r){const{service_id:e}=s,t=this.childManager.children.get(e),r=t?.service;if(!r)return{content:[{type:"text",text:`❌ "${e}" は現在アクティブではありません。activate_service で有効化してください。`}],isError:!0};let o;this.childManager.deactivate(e),log(`[restart] "${e}" stopped`);try{o=await this.childManager.activateExternal(e,r)}catch(t){return log(`[restart] Failed "${e}": ${t.message}`),{content:[{type:"text",text:`❌ "${e}" の再起動に失敗しました: ${t.message}`}],isError:!0}}log(`[restart] "${e}" → ${o.length} tools. Sending list_changed.`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[restart] notification error: ${e.message}`)}const n=o.map(e=>e.name).join(", ");return{content:[{type:"text",text:`🔄 "${r.name}" を再起動しました(ツール${o.length}個)\n\nツール: ${n||"(なし)"}`}]}}if("unregister_service"===r){const{service_id:e}=s;if(!TOKEN)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};this.childManager.isActive(e)&&(this.childManager.deactivate(e),log(`[unregister] "${e}" stopped`));const t=await uninstallMcpService(e,TOKEN);if(!t.success)return{content:[{type:"text",text:`❌ "${e}" のサーバー削除に失敗: ${t.error||"Unknown"}`}],isError:!0};log(`[unregister] "${e}" uninstalled from server`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[unregister] notification error: ${e.message}`)}return{content:[{type:"text",text:`🗑️ "${e}" を削除しました。プロセスも停止済みです。`}]}}if("register_service"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,env:r}=s;if(!TOKEN)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const o=r||{},n=await installMcpService(t,!1,TOKEN,o);return n.success?(log(`[register] "${t}" installed on server`),{content:[{type:"text",text:`✅ サービス "${t}" をサーバーに登録しました。\n\n次のステップ:\n- save_mcp_env で APIキー等を設定\n- activate_service で有効化`}]}):{content:[{type:"text",text:`❌ "${t}" の登録に失敗しました: ${n.error||"Unknown"}\n\n※ MCP はサーバーカタログに登録されている必要があります。browse_mcp_store で確認してください。`}],isError:!0}}if("save_mcp_env"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,env:r}=s;if(!t||!r||0===Object.keys(r).length)return{content:[{type:"text",text:"❌ service_id と env(キー/値のペア)が必要です"}],isError:!0};log(`[save_mcp_env] ${t}: ${Object.keys(r).join(", ")}`);try{const e=await updateMcpEnv(t,r,TOKEN);return e.success?{content:[{type:"text",text:`🔐 環境変数をサーバーに保存しました\n\nサービス: ${t}\n保存キー: ${Object.keys(r).join(", ")}\n\n※ ローカルには保存されません。次回起動時にサーバーから自動取得されます。`}]}:{content:[{type:"text",text:`❌ サーバー保存失敗: ${e.error}`}],isError:!0}}catch(e){return log(`[save_mcp_env] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("browse_mcp_store"===r){const{category:e}=s||{};log(`[browse_mcp_store] category=${e||"all"}`);try{const t=e?{category:e}:{},r=await listAvailableMcps(t,TOKEN,LANGUAGE);if(!r||!r.success)return{content:[{type:"text",text:`❌ MCPカタログの取得に失敗: ${r?.error||"Unknown"}`}],isError:!0};const s=r.data||[];if(0===s.length)return{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]};let o=`📦 MCPストア一覧 (${s.length}件)\n\n`;return s.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_from_store(mcp_id: "MCP_ID")',{content:[{type:"text",text:o}]}}catch(e){return log(`[browse_mcp_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("install_from_store"===r){const{mcp_id:e}=s;log(`[install_from_store] mcp_id=${e}`);try{const t=await downloadMcpPackage(e,TOKEN);if(!t||!t.success)return{content:[{type:"text",text:`❌ MCPダウンロードに失敗: ${t?.error||"Unknown"}`}],isError:!0};const r=t.data,s=r.files||[];if(0===s.length)return{content:[{type:"text",text:"❌ MCPパッケージにファイルが含まれていません。"}],isError:!0};const o=path.join(os.homedir(),".claude","mcp-servers",e);fs.mkdirSync(o,{recursive:!0});for(const e of s){const t=path.join(o,e.path);fs.mkdirSync(path.dirname(t),{recursive:!0}),fs.writeFileSync(t,e.content,"utf-8")}log(`[install_from_store] ${s.length} files written to ${o}`);const n=r.packageSource||{};if(n.needsNpmInstall&&fs.existsSync(path.join(o,"package.json"))){const{execSync:e}=require("child_process");try{e("npm install --production",{cwd:o,timeout:6e4,stdio:"pipe"})}catch(e){return{content:[{type:"text",text:`⚠️ ファイル展開完了、npm install失敗: ${e.message}`}]}}}if(TOKEN){const t=await installMcpService(e,!1,TOKEN);if(!t.success)return{content:[{type:"text",text:`⚠️ ファイル展開完了、サーバー登録失敗: ${t.error||"Unknown"}`}],isError:!0};log(`[install_from_store] Server sync OK: ${e}`)}let i=`✅ MCP「${n.name||e}」をインストールしました\n\n`;i+=`📁 インストール先: ${o}\n`,i+=`📄 ファイル数: ${s.length}\n`,n.installNotes&&(i+=`\n📝 ${n.installNotes}\n`);const c=(n.requiredEnvVars||[]).filter(e=>!process.env[e]);return c.length>0&&(i+="\n⚠️ 以下の環境変数を save_mcp_env で設定してください:\n",c.forEach(e=>{i+=` - ${e}\n`})),i+=`\nactivate_service(service_id: "${e}") で有効化できます。`,{content:[{type:"text",text:i}]}}catch(e){return log(`[install_from_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if(this._cloudProxyTools&&this._cloudProxyTools.has(r)){const e=this._cloudProxyTools.get(r);log(`[callTool] Cloud proxy: ${r} → ${e.mcpId}/${e.toolName}`);try{const t=await callMcpToolProxy(e.mcpId,e.toolName,s,TOKEN);return t.success&&t.data?t.data.content?t.data:{content:[{type:"text",text:JSON.stringify(t.data,null,2)}]}:{content:[{type:"text",text:`❌ プロキシエラー: ${t.error||"Unknown"}`}],isError:!0}}catch(e){return log(`[callTool] Cloud proxy error "${r}": ${e.message}`),{content:[{type:"text",text:`❌ クラウドプロキシエラー: ${e.message}`}],isError:!0}}}try{const{found:e,result:t}=await this.childManager.callTool(r,s);if(e)return t&&t.content?t:{content:[{type:"text",text:String(t)}]}}catch(e){return log(`[callTool] Error proxying "${r}": ${e.message}`),{content:[{type:"text",text:`❌ ツール実行エラー: ${e.message}`}],isError:!0}}return{content:[{type:"text",text:`❌ Unknown tool: ${r}`}],isError:!0}})}async autoActivate(){if(!TOKEN)return void log("[autoActivate] No token, skipping auto-activation");let e=[];try{const t=await getGatewayConfig(TOKEN);if(!t.success)return void log(`[autoActivate] Server config failed: ${t.error||"Unknown"}. Starting with no services.`);e=t.data?.services||[],log(`[autoActivate] Server config: ${e.length} services`)}catch(e){return void log(`[autoActivate] Server config error: ${e.message}. Starting with no services.`)}for(const t of e)if(t.alwaysOn)if("cloud_proxy"===t.hostingType){const e=Array.isArray(t.tools)?t.tools:[];if(0===e.length){log(`[autoActivate] "${t.mcpId}" (cloud_proxy) has no tools, skipping`);continue}for(const r of e){const e=`${t.mcpId}__${r.name}`;this._cloudProxyTools=this._cloudProxyTools||new Map,this._cloudProxyTools.set(e,{mcpId:t.mcpId,toolName:r.name,description:r.description,inputSchema:r.inputSchema})}log(`[autoActivate] "${t.mcpId}" (cloud_proxy) → ${e.length} proxy tools registered`)}else{const e=path.join(os.homedir(),".claude","mcp-servers",t.mcpId),r=t.entryPoint||"dist/index.js",s={name:t.name,description:t.description,type:"external",command:t.command||"node",args:t.args&&t.args.length?t.args:[path.join(e,r)],env:t.env||{}};log(`[autoActivate] Activating "${t.mcpId}" (${t.hostingType})...`);try{const e=await this.childManager.activateExternal(t.mcpId,s);log(`[autoActivate] "${t.mcpId}" ready: ${e.length} tools`)}catch(e){log(`[autoActivate] "${t.mcpId}" failed: ${e.message}`)}}}async run(){const e=new StdioServerTransport;if(await this.server.connect(e),log("[Gateway] BlueLamp Gateway v2.0.0 started."),await this.autoActivate(),this.childManager.getActiveServiceIds().length>0)try{await this.server.notification({method:"notifications/tools/list_changed"}),log("[Gateway] Initial list_changed sent after autoActivate.")}catch(e){log(`[Gateway] Initial list_changed error: ${e.message}`)}if(KEYWORD&&TOKEN){log(`[Gateway] Auto-loading knowledge for keyword: ${KEYWORD}`);const e=await getPromptFromPortal(KEYWORD,TOKEN,LANGUAGE);e&&e.success?log(`[Gateway] Auto-load successful: ${e.title}`):log(`[Gateway] Auto-load failed for keyword: ${KEYWORD}`)}}}const server=new BlueLampServer;server.run().catch(e=>{log(`[Gateway] Fatal: ${e.message}`),process.exit(1)});