bluelamp 6.6.28 → 6.6.29

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 +1 @@
1
- "use strict";const fs=require("fs"),path=require("path"),{STATUS:STATUS,REASON:REASON,compareVersions:compareVersions,readVersionFromPackageFiles:readVersionFromPackageFiles}=require("./mcp-version-advisor.cjs");function shouldAutoUpdate({decision:e,notice:r,alreadyTried:o=!1}){return e&&!0===e.ready?Array.isArray(e.prune)&&0!==e.prune.length?o?{update:!1,reason:"already-tried"}:r&&r.status===STATUS.NOTIFY&&r.reason===REASON.UPDATE_AVAILABLE?{update:!0,reason:"prune-on-outdated-disk"}:{update:!1,reason:`version-${r?r.reason:"unknown"}`}:{update:!1,reason:"no-prune"}:{update:!1,reason:"child-not-ready"}}function buildAutoUpdateNoticeText(e){const r=Array.isArray(e)?e:[];if(0===r.length)return null;const o=["━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━","📦 MCP の自動更新(この機体の disk が古く、口が一覧から消えるところだったため上げました)"];for(const e of r)o.push(` - ${e.mcpId}: ${e.from} → ${e.to}(消えるはずだった口: ${e.prunedWouldBe} 本)`);return o.join("\n")}const LOCK_STALE_MS=12e5,LOCK_WAIT_MS=12e4,LOCK_POLL_MS=500;function lockPathFor(e,r){return path.join(e,`.${r}.update.lock`)}function pidAlive(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function tryAcquireLock(e,{now:r=Date.now(),staleMs:o=12e5,isAlive:t=pidAlive}={}){fs.mkdirSync(path.dirname(e),{recursive:!0});for(let n=0;n<2;n+=1){try{const o=fs.openSync(e,"wx");return fs.writeSync(o,JSON.stringify({pid:process.pid,at:r})),fs.closeSync(o),!0}catch(e){if("EEXIST"!==e.code)throw e}let n=null;try{n=JSON.parse(fs.readFileSync(e,"utf8"))}catch(e){n=null}let a=r;try{a=fs.statSync(e).mtimeMs}catch(e){continue}if(!(r-(n&&Number.isFinite(n.at)?n.at:a)>o||n&&Number.isInteger(n.pid)&&!t(n.pid)))return!1;try{fs.unlinkSync(e)}catch(e){}}return!1}function releaseLock(e){try{fs.unlinkSync(e)}catch(e){}}async function acquireLock(e,{waitMs:r=12e4,pollMs:o=500,...t}={}){const n=Date.now()+r;for(;;){if(tryAcquireLock(e,{...t,now:Date.now()}))return!0;if(Date.now()>=n)return!1;await new Promise(e=>setTimeout(e,o))}}async function updateMcpOnDisk(e){const r=lockPathFor(e.installRoot,e.mcpId);let o=!1;try{if(o=await acquireLock(r,e.lock),!o)return{ok:!1,reason:"lock-timeout"};const t=e.readLocalVersion();if(0===compareVersions(t,e.catalogVersion))return{ok:!0,from:t,to:t,by:"other"};const n=await e.download();if(!n||!n.success||!n.data)return{ok:!1,reason:`download-failed: ${n&&n.error||"Unknown"}`};const a=n.data;if("cloud_proxy"===(a.packageSource&&a.packageSource.hostingType))return{ok:!1,reason:"download-is-cloud-proxy"};const c=readVersionFromPackageFiles(a.files,e.entryPoint||a.packageSource&&a.packageSource.entryPoint);if(0!==compareVersions(c,e.catalogVersion))return{ok:!1,reason:`version-mismatch: downloaded=${c} catalog=${e.catalogVersion}`};const i=path.join(e.installRoot,e.mcpId),s=await e.extract(i,a);if(!s||!s.ok){const e=s&&s.error?s.error.message:"Unknown";return{ok:!1,reason:`extract-failed(${s&&s.stage}): ${e}`}}return{ok:!0,from:t,to:c,by:"self"}}catch(e){return{ok:!1,reason:`error: ${e.message}`}}finally{o&&releaseLock(r)}}module.exports={shouldAutoUpdate:shouldAutoUpdate,buildAutoUpdateNoticeText:buildAutoUpdateNoticeText,updateMcpOnDisk:updateMcpOnDisk,tryAcquireLock:tryAcquireLock,acquireLock:acquireLock,releaseLock:releaseLock,lockPathFor:lockPathFor,LOCK_STALE_MS:12e5,LOCK_WAIT_MS:12e4};
1
+ "use strict";const fs=require("fs"),path=require("path"),{STATUS:STATUS,REASON:REASON,compareVersions:compareVersions,readVersionFromPackageFiles:readVersionFromPackageFiles}=require("./mcp-version-advisor.cjs");function shouldAutoUpdate({decision:e,notice:r,alreadyTried:o=!1}){return e&&!0===e.ready?Array.isArray(e.prune)&&0!==e.prune.length?o?{update:!1,reason:"already-tried"}:r&&r.status===STATUS.NOTIFY&&r.reason===REASON.UPDATE_AVAILABLE?{update:!0,reason:"prune-on-outdated-disk"}:{update:!1,reason:`version-${r?r.reason:"unknown"}`}:{update:!1,reason:"no-prune"}:{update:!1,reason:"child-not-ready"}}function buildAutoUpdateNoticeText(e){const r=Array.isArray(e)?e:[];if(0===r.length)return null;const o=["━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━","📦 MCP の自動更新(この機体の disk が古いか未展開で、口が使えないところだったため上げました)"];for(const e of r)null==e.from?o.push(` - ${e.mcpId}: 未展開 → ${e.to} を展開しました`):o.push(` - ${e.mcpId}: ${e.from} → ${e.to}(消えるはずだった口: ${e.prunedWouldBe} 本)`);return o.join("\n")}function needsDiskEntry(e){return!(e&&Array.isArray(e.args)&&e.args.length>0)}function buildNotDeployedText(e,r){return`"${e}" はこの機体に展開されていません(~/.claude/mcp-servers/${e} に実体が無い)。自動で展開できませんでした(${r})。install_from_store(mcp_id: "${e}") を撃つと入ります。`}const LOCK_STALE_MS=12e5,LOCK_WAIT_MS=12e4,LOCK_POLL_MS=500;function lockPathFor(e,r){return path.join(e,`.${r}.update.lock`)}function pidAlive(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function tryAcquireLock(e,{now:r=Date.now(),staleMs:o=12e5,isAlive:t=pidAlive}={}){fs.mkdirSync(path.dirname(e),{recursive:!0});for(let n=0;n<2;n+=1){try{const o=fs.openSync(e,"wx");return fs.writeSync(o,JSON.stringify({pid:process.pid,at:r})),fs.closeSync(o),!0}catch(e){if("EEXIST"!==e.code)throw e}let n=null;try{n=JSON.parse(fs.readFileSync(e,"utf8"))}catch(e){n=null}let a=r;try{a=fs.statSync(e).mtimeMs}catch(e){continue}if(!(r-(n&&Number.isFinite(n.at)?n.at:a)>o||n&&Number.isInteger(n.pid)&&!t(n.pid)))return!1;try{fs.unlinkSync(e)}catch(e){}}return!1}function releaseLock(e){try{fs.unlinkSync(e)}catch(e){}}async function acquireLock(e,{waitMs:r=12e4,pollMs:o=500,...t}={}){const n=Date.now()+r;for(;;){if(tryAcquireLock(e,{...t,now:Date.now()}))return!0;if(Date.now()>=n)return!1;await new Promise(e=>setTimeout(e,o))}}async function updateMcpOnDisk(e){const r=lockPathFor(e.installRoot,e.mcpId);let o=!1;try{if(o=await acquireLock(r,e.lock),!o)return{ok:!1,reason:"lock-timeout"};const t=e.readLocalVersion();if(0===compareVersions(t,e.catalogVersion))return{ok:!0,from:t,to:t,by:"other"};const n=await e.download();if(!n||!n.success||!n.data)return{ok:!1,reason:`download-failed: ${n&&n.error||"Unknown"}`};const a=n.data;if("cloud_proxy"===(a.packageSource&&a.packageSource.hostingType))return{ok:!1,reason:"download-is-cloud-proxy"};const s=readVersionFromPackageFiles(a.files,e.entryPoint||a.packageSource&&a.packageSource.entryPoint);if(0!==compareVersions(s,e.catalogVersion))return{ok:!1,reason:`version-mismatch: downloaded=${s} catalog=${e.catalogVersion}`};const i=path.join(e.installRoot,e.mcpId),c=await e.extract(i,a);if(!c||!c.ok){const e=c&&c.error?c.error.message:"Unknown";return{ok:!1,reason:`extract-failed(${c&&c.stage}): ${e}`}}return{ok:!0,from:t,to:s,by:"self"}}catch(e){return{ok:!1,reason:`error: ${e.message}`}}finally{o&&releaseLock(r)}}module.exports={shouldAutoUpdate:shouldAutoUpdate,buildAutoUpdateNoticeText:buildAutoUpdateNoticeText,needsDiskEntry:needsDiskEntry,buildNotDeployedText:buildNotDeployedText,updateMcpOnDisk:updateMcpOnDisk,tryAcquireLock:tryAcquireLock,acquireLock:acquireLock,releaseLock:releaseLock,lockPathFor:lockPathFor,LOCK_STALE_MS:12e5,LOCK_WAIT_MS:12e4};
@@ -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,getGatewayConfig:getGatewayConfig,searchMcpServices:searchMcpServices,installMcpService:installMcpService,setServiceDeactivated:setServiceDeactivated,uninstallMcpService:uninstallMcpService,updateMcpEnv:updateMcpEnv,callMcpToolProxy:callMcpToolProxy}=require("./portal-api-client.cjs"),{requireFromPackage:requireFromPackage}=require("../requireFromPackage.cjs"),{logger:logger}=requireFromPackage(require,"../../utils/logger"),{t:t,setLanguage:setLanguage}=requireFromPackage(require,"../../i18n");requireFromPackage(require,"../../lib/connectResilience").applyConnectResilience(require("node:net"));{const{SYS_PROMPT_ENV:e,isOwnSysPromptPath:t}=requireFromPackage(require,"../../lib/sysPromptHandoff"),r=process.env[e];if(t(r,require("node:os").tmpdir()))try{require("node:fs").unlinkSync(r)}catch{}}const{describeTokenForDebug:describeTokenForDebug}=requireFromPackage(require,"../../lib/debugRedaction"),{collectUpdateNotices:collectUpdateNotices,buildUpdateNoticeText:buildUpdateNoticeText,evaluateUpdateNotice:evaluateUpdateNotice,readLocalPackageVersion:readLocalPackageVersion}=require("./mcp-version-advisor.cjs"),{shouldAutoUpdate:shouldAutoUpdate,buildAutoUpdateNoticeText:buildAutoUpdateNoticeText,updateMcpOnDisk:updateMcpOnDisk}=require("./auto-update.cjs"),{extractDownloadedPackage:extractDownloadedPackage,formatInstallFailure:formatInstallFailure}=require("./install-package.cjs"),{formatMcpStoreListing:formatMcpStoreListing}=require("./store-listing.cjs"),spawn=require("cross-spawn"),{StringDecoder:StringDecoder}=require("string_decoder"),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-要件定義",{TokenManager:TokenManager}=requireFromPackage(require,"../../auth/TokenManager");function resolveToken(){return TokenManager.loadTokenStringSync()}const PORTAL_URL_ENV=process.env.BLUELAMP_PORTAL_API_BASE_URL||process.env.PORTAL_URL||"",isDevEnv=PORTAL_URL_ENV.includes("localhost"),PORTAL_URL=PORTAL_URL_ENV||(isDevEnv?"http://localhost:8430/api/bluelamp":"https://api.motohi.jp/api/bluelamp"),LANGUAGE=process.env.MUSUHI_LANGUAGE||"ja",CATALOG_CACHE_FILE=path.join(os.homedir(),".bluelamp-vscode","gateway-catalog.json"),CATALOG_CACHE_VERSION=1,CHILD_READY_TIMEOUT_MS=3e4,CHILD_NOT_READY_TEXT="起動中です。数秒後に再試行してください";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?"set":"unset")),log(` TOKEN (起動時スナップショット): ${describeTokenForDebug(resolveToken())}`),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}`)}const EXIT_CLOSE_GRACE_MS=2e3;class ChildMcpProcess{constructor(e,t,r,o){this.serviceId=e,this.command=t,this.args=(r||[]).map(e=>"string"==typeof e?e.replace(/^~\//,os.homedir()+"/"):e),this.env={...process.env,...o},this.process=null,this.tools=[],this.requestId=0,this.pending=new Map,this.buffer="",this.stdoutDecoder=new StringDecoder("utf8"),this.stderrDecoder=new StringDecoder("utf8"),this.stderrTail="",this._decodersEnded=!1}async start(){return new Promise((e,t)=>{let r=!1;const o=(e,t)=>{r||(r=!0,clearTimeout(i),e(t))},i=setTimeout(()=>{o(t,new Error(`Timeout: "${this.serviceId}" failed to start within 90s`)),this.stop()},9e4);try{this.process=spawn(this.command,this.args,{env:this.env,stdio:["pipe","pipe","pipe"]}),this.process.on("error",e=>{o(t,new Error(`Spawn error for "${this.serviceId}": ${e.message}`))}),this.process.stdin.on("error",e=>{log(`[ChildMcp:${this.serviceId}:stdin] ${e.message}`)}),this.process.on("close",()=>{this._flushDecoders("close")});const r=(e,r)=>{clearTimeout(this._exitCloseTimer);const i=this.stderrTail.trim();o(t,new Error(`"${this.serviceId}" exited before ready (code=${e}, signal=${r})${i?` | stderr: ${i}`:""}`)),this.pending.forEach(e=>e.reject(new Error("Process exited"))),this.pending.clear()};this.process.on("exit",(e,t)=>{log(`[ChildMcp] "${this.serviceId}" exited (code=${e}, signal=${t})`),this._exitCloseTimer=setTimeout(()=>r(e,t),2e3)}),this.process.on("close",(e,t)=>r(e,t)),this.process.on("close",()=>{if(this._decodersEnded)return;this._decodersEnded=!0;const e=this.stdoutDecoder.end(),t=this.stderrDecoder.end();e&&(this.buffer+=e,log(`[ChildMcp] "${this.serviceId}" stdout に半端なバイトが ${Buffer.byteLength(e)} バイト残っていた(子が多バイト文字の途中で終了)⇒ buffer へ流した。⛔ これは「壊れた文字」であって「失われた文字」ではない`)),t&&log(`[ChildMcp] "${this.serviceId}" stderr に半端なバイトが ${Buffer.byteLength(t)} バイト残っていた ⇒ ${JSON.stringify(t)}`)}),this.process.stderr.on("data",e=>{const t=this.stderrDecoder.write(e);this.stderrTail=sliceTailPreservingSurrogates(this.stderrTail+t,500),log(`[ChildMcp:${this.serviceId}:stderr] ${t.trim()}`)}),this.process.stdout.on("data",e=>{this.buffer+=this.stdoutDecoder.write(e),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 r=t.tools||[];this.tools=r.map(e=>({...e,name:`${this.serviceId}__${e.name}`,_originalName:e.name})),log(`[ChildMcp] "${this.serviceId}" ready: ${this.tools.length} tools`),o(e,this.tools)}).catch(e=>{o(t,e)})}catch(e){o(t,e)}})}async callTool(e,t){return await this._sendRequest("tools/call",{name:e,arguments:t})}_flushDecoders(e){for(const[t,r]of[["stdout",this.stdoutDecoder],["stderr",this.stderrDecoder]]){let o="";try{o=r.end()}catch(e){log(`[ChildMcp:${this.serviceId}:${t}] decoder.end() が落ちた: ${e.message}`);continue}o&&log(`[ChildMcp:${this.serviceId}:${t}] 🚨 未完の多バイト列を ${e} 時に検出: ${JSON.stringify(o)}(${o.length} 文字・⛔ 黙って捨てていない)`)}}stop(){clearTimeout(this._exitCloseTimer),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,o)=>{const i=++this.requestId,s=JSON.stringify({jsonrpc:"2.0",id:i,method:e,params:t})+"\n";this.pending.set(i,{resolve:r,reject:o});try{this.process.stdin.write(s)}catch(e){this.pending.delete(i),o(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,this.starting=new Map}async activateExternal(e,t){const r=new ChildMcpProcess(e,t.command,t.args||[],t.env||{}),o=await r.start();return this.children.set(e,{type:"external",process:r,tools:o,service:t}),o}beginActivateExternal(e,t){const r=this.children.get(e);if(r)return Promise.resolve({ok:!0,tools:r.tools||[]});const o=this.starting.get(e);if(o)return o;const i=this.activateExternal(e,t).then(e=>({ok:!0,tools:e}),e=>({ok:!1,error:e})).then(t=>(this.starting.delete(e),t));return this.starting.set(e,i),i}isStarting(e){return this.starting.has(e)}async waitForReady(e,t){if(this.children.has(e))return{ready:!0};const r=this.starting.get(e);if(!r)return{ready:!1,reason:"unknown"};let o=null;const i=new Promise(e=>{o=setTimeout(()=>e({timedOut:!0}),t),"function"==typeof o.unref&&o.unref()});try{const e=await Promise.race([r,i]);return e.timedOut?{ready:!1,reason:"timeout"}:e.ok?{ready:!0}:{ready:!1,reason:"failed",error:e.error}}finally{o&&clearTimeout(o)}}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,o]of this.children)if("external"===o.type){const r=o.tools.find(t=>t.name===e);if(r)return{found:!0,result:await o.process.callTool(r._originalName,t)}}return{found:!1}}getActiveServiceIds(){return[...this.children.keys()]}}class BlueLampServer{constructor(){this.childManager=new ChildMcpManager,this.updateNoticeText=null,this._autoUpdating=new Map,this._autoUpdateTried=new Set,this._autoUpdateRecords=[],this._childSettling=new Map,this._catalogTools=new Map,this._cloudProxyTools=new Map,this._catalogServices=new Map,this._needChildForSchema=new Set,this._catalogFromCache=!1,this._configRefresh=null,this.server=new Server({name:"bluelamp",version:"2.0.0"},{capabilities:{tools:{listChanged:!0}}}),this.setupHandlers()}checkAuth(){return resolveToken()?{ok:!0}:{ok:!1,error:"BlueLamp にログインしてください。ターミナルで `bluelamp1` を一度実行すると ~/.musuhi/portal-token.enc が作成されます。"}}buildToolList(){return buildGatewayToolList([{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:"deactivate_service で一覧から外した MCP を、道具一覧に戻します(deactivate を戻すだけ)。権限のある MCP は最初から一覧に出ているので、通常このツールを撃つ必要はありません。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"},always_on:{type:"boolean",description:"後方互換のため受け付けますが、一覧の可否には使いません(無視されます)"}},required:["service_id"]}},{name:"deactivate_service",description:"指定した MCP を道具一覧から外します(deactivated=true)。次の接続でも一覧に出なくなります。権限は変わらないので activate_service でいつでも戻せます。",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:{},required:[]}},{name:"install_from_store",description:"MCP ストアから MCP を取得してこの機体に展開し、env と導入版をサーバに記録します。browse_mcp_store で見つけた mcp_id を指定してください。道具一覧に出るかどうかは権限で決まるので、権限の無い MCP をこれで一覧に出すことはできません(自分で作った MCP・共有された MCP は権限に含まれるため、何もしなくても一覧に出ます)。",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"]}}],this._catalogTools,this.childManager.getActiveTools())}applyCatalog(e,t={}){const{catalog:r,cloudProxy:o,warnings:i,needChildForSchema:s}=buildCatalogRegistry(e);this._catalogTools=r,this._cloudProxyTools=o,this._catalogServices=new Map,this._needChildForSchema=new Set(s),this._catalogFromCache=!0===t.fromCache;for(const t of Array.isArray(e)?e:[])t&&t.mcpId&&this._catalogServices.set(t.mcpId,t);let n=0;for(const e of i)"no-input-schema"===e.reason?(n+=e.count,log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}): ${e.count}/${e.total} tools have no inputSchema — 一覧には出す(空 schema)。子を起こして実 schema で上書きする`)):log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}) skipped: ${e.reason}`);return n>0&&log(`[catalog] inputSchema 欠損の合計: ${n} 本 / ${this._needChildForSchema.size} service(一覧には出す。子を起こして実 schema で上書きする)`),log(`[catalog] ${r.size} tools registered from ${this._catalogServices.size} services (cloud_proxy: ${o.size})`),r}registerServiceInCatalog(e){const{catalog:t,cloudProxy:r,warnings:o}=buildCatalogRegistry([e]);for(const[e,r]of t)this._catalogTools.set(e,r);for(const[e,t]of r)this._cloudProxyTools.set(e,t);for(const e of o)log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}) skipped: ${e.reason}`);return e&&e.mcpId&&this._catalogServices.set(e.mcpId,e),t.size}unregisterServiceFromCatalog(e){const t=`${e}__`;let r=0;for(const e of[...this._catalogTools.keys()])e.startsWith(t)&&(this._catalogTools.delete(e),this._cloudProxyTools.delete(e),r++);return r}buildChildService(e,t){const r=path.join(os.homedir(),".claude","mcp-servers",e),o=t.entryPoint||"dist/index.js";return{name:t.name,description:t.description,type:"external",command:t.command||"node",args:t.args&&t.args.length?t.args:[path.join(r,o)],env:t.env||{}}}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:o}=e.params;log(`[CallTool] ${r}`);const i=resolveToken();if("load_knowledge"===r){const{keyword:e}=o;log("[load_knowledge] called");try{const r=await getPromptFromPortal(e,i,LANGUAGE);if(!r||!r.success){const o=r?.error||t("mcp.knowledgeNotFound",{keyword:e});return{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.":"",s=[this.updateNoticeText,buildAutoUpdateNoticeText(this._autoUpdateRecords)].filter(Boolean).map(e=>`\n\n${e}`).join("");return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${r.knowledge}${o}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}${s}`}]}}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(!i)return{content:[{type:"text",text:"❌ 未認証です。MCPカタログの検索にはログインが必要です。"}],isError:!0};let t=[];try{const e=await searchMcpServices(o.query,i);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:`🔍 「${o.query}」に一致するサービスはありません。\n\n別のキーワードで試すか、browse_mcp_storeでカタログを確認してください。`}]};let r=`🔍 「${o.query}」の検索結果(${t.length}件)\n\n`;for(const e of t.slice(0,10)){const t=e.id,o=e.service,i=this.childManager.isActive(t),s=!0===o.deactivated,n=e.isInstalled?"":" [未インストール]",a=s?" [一覧から外し中]":"";r+=`${s?"🚫":i?"🟢":"⚪"} **${o.name}**${a}${n} (id: \`${t}\`)\n`,r+=` ${o.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}=o,r=await getGatewayConfig(i);if(!r.success)return{content:[{type:"text",text:`❌ サーバー設定の取得に失敗: ${r.error||"Unknown"}`}],isError:!0};const s=(r.data?.services||[]).find(e=>e.mcpId===t);if(!s)return{content:[{type:"text",text:`❌ サービス "${t}" はあなたの権限の中にありません(権限のある MCP は何もしなくても一覧に出ます)。`}],isError:!0};const n=await setServiceDeactivated(t,!1,i);if(!n.success)return{content:[{type:"text",text:`❌ "${t}" を一覧に戻せませんでした: ${n.error||"Unknown"}`}],isError:!0};log(`[activate] "${t}" deactivated=false (一覧に戻した)`);const a=this.registerServiceInCatalog({...s,deactivated:!1});if(0===a)return{content:[{type:"text",text:`❌ "${t}" には目録にツール定義がありません(tools が空)。`}],isError:!0};if("cloud_proxy"===s.hostingType){log(`[activate] "${t}" (cloud_proxy) → ${a} proxy tools registered`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${s.name}" を一覧に戻しました(cloud_proxy ツール${a}個)`}]}}if(this.childManager.isActive(t)){try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${t}" を一覧に戻しました(子プロセスは既に起動済み)。`}]}}log(`[activate] "${t}" (${s.hostingType}) → ${a} catalog tools restored. 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:`✅ "${s.name}" を一覧に戻しました(ツール${a}個)。最初に呼ばれた時に自動で起動します。`}]}}if("deactivate_service"===r){const{service_id:e}=o;if(!i)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const t=await setServiceDeactivated(e,!0,i);if(!t.success)return{content:[{type:"text",text:`❌ "${e}" を一覧から外せませんでした: ${t.error||"Unknown"}`}],isError:!0};log(`[deactivate] "${e}" deactivated=true (一覧から外した)`);const r=this.unregisterServiceFromCatalog(e);r>0&&log(`[deactivate] "${e}" → ${r} catalog tools removed`),this._catalogServices.delete(e);let s=!1;if(this.childManager.isActive(e)&&(this.childManager.deactivate(e),log(`[deactivate] "${e}" child process stopped`),s=!0),r>0||s)try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[deactivate] notification error: ${e.message}`)}let n=`✅ "${e}" を道具一覧から外しました(deactivated=true)。`;return r>0&&(n+=` ツール${r}個を一覧から削除しました。`),s&&(n+=" 子プロセスを停止しました。"),n+=" 次の接続でも一覧に出ません。権限は変わっていないので activate_service で戻せます。",{content:[{type:"text",text:n}]}}if("restart_service"===r){const{service_id:e}=o,t=this.childManager.children.get(e),r=t?.service;if(!r)return isCloudProxyService(this._cloudProxyTools,e)?{content:[{type:"text",text:`ℹ️ "${e}" は cloud_proxy 方式のため、ローカルの再起動対象ではありません(子プロセスを持ちません)。\nサーバ側のツール更新は deploy で全利用者に反映されるので、再起動は不要です。\nツール一覧を取り直したいだけなら deactivate_service → activate_service を実行してください。`}]}:{content:[{type:"text",text:`❌ "${e}" の子プロセスは現在動いていません。道具を呼べば自動で起動します(restart は起動済みの子にだけ効きます)。`}],isError:!0};let s,n=r;if(i)try{const t=await getGatewayConfig(i);if(t.success){const o=(t.data?.services||[]).find(t=>t.mcpId===e);o&&o.env&&(n={...r,env:o.env},log(`[restart] "${e}" env refreshed from server (keys=${Object.keys(o.env).join(",")||"none"})`))}}catch(e){log(`[restart] env refresh failed (using cached): ${e.message}`)}this.childManager.deactivate(e),log(`[restart] "${e}" stopped`);try{s=await this.childManager.activateExternal(e,n)}catch(t){return log(`[restart] Failed "${e}": ${t.message}`),{content:[{type:"text",text:`❌ "${e}" の再起動に失敗しました: ${t.message}`}],isError:!0}}log(`[restart] "${e}" → ${s.length} tools. Sending list_changed.`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[restart] notification error: ${e.message}`)}const a=s.map(e=>e.name).join(", ");return{content:[{type:"text",text:`🔄 "${n.name}" を再起動しました(ツール${s.length}個)\n\nツール: ${a||"(なし)"}`}]}}if("unregister_service"===r){const{service_id:e}=o;if(!i)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};this.childManager.isActive(e)&&(this.childManager.deactivate(e),log(`[unregister] "${e}" stopped`));const t=await uninstallMcpService(e,i);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}=o;if(!i)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const s=r||{},n=await installMcpService(t,!1,i,s);return n.success?(log(`[register] "${t}" installed on server`),{content:[{type:"text",text:`✅ サービス "${t}" をサーバーに登録しました。\n\n次のステップ:\n- save_mcp_env で APIキー等を設定\n- 次の接続で道具一覧に出ます(activate は不要です)`}]}):{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}=o;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,i);if(!e.success)return{content:[{type:"text",text:`❌ サーバー保存失敗: ${e.error}`}],isError:!0};const o=Object.keys(r).join(", ");return e.warning?{content:[{type:"text",text:`🔐 環境変数をサーバーに保存しました\n\nサービス: ${t}\n保存キー: ${o}\n\n${e.warning}`}]}:{content:[{type:"text",text:`🔐 環境変数をサーバーに保存しました\n\nサービス: ${t}\n保存キー: ${o}\n\n※ ローカルには保存されません。次回起動時にサーバーから自動取得されます。`}]}}catch(e){return log(`[save_mcp_env] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("browse_mcp_store"===r){log("[browse_mcp_store]");try{const e=await listAvailableMcps({},i,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ MCPカタログの取得に失敗: ${e?.error||"Unknown"}`}],isError:!0};const t=e.data||[];return 0===t.length?{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]}:{content:[{type:"text",text:formatMcpStoreListing(t)}]}}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}=o;log(`[install_from_store] mcp_id=${e}`);try{const t=await downloadMcpPackage(e,i);if(!t||!t.success)return{content:[{type:"text",text:`❌ MCPダウンロードに失敗: ${t?.error||"Unknown"}`}],isError:!0};const r=t.data,o=r.packageSource||{},s=r.files||[];let n=o.hostingType||o.hosting&&o.hosting.type||null;if(i){let t=!1;try{const r=await getGatewayConfig(i);if(r.success){const o=(r.data?.services||[]).find(t=>t.mcpId===e);o&&(t=!0===o.alwaysOn,n||(n=o.hostingType))}}catch(e){}const r=await installMcpService(e,t,i);if(!r.success)return{content:[{type:"text",text:`❌ サーバー登録失敗: ${r.error||"Unknown"}`}],isError:!0};log(`[install_from_store] Server sync OK: ${e} (alwaysOn=${t})`)}if("cloud_proxy"===n){log(`[install_from_store] "${e}" is cloud_proxy, skipping local extract`);let t=`✅ MCP「${o.name||e}」をインストールしました(cloud_proxy)\n\n`;return t+="☁ クラウド実行のためローカル展開は不要です\n",o.installNotes&&(t+=`\n📝 ${o.installNotes}\n`),t+="\n権限に含まれていれば、次の接続で道具一覧に出ます(activate_service を撃つ必要はありません)。",{content:[{type:"text",text:t}]}}const a=path.join(os.homedir(),".claude","mcp-servers",e),c=await extractDownloadedPackage({mcpId:e,installDir:a,pkg:r});if("empty"===c.stage)return{content:[{type:"text",text:"❌ MCPパッケージにファイルが含まれていません。"}],isError:!0};if(!c.ok)return log(`[install_from_store] ${e} failed at ${c.stage}: ${c.error.message}`),{content:[{type:"text",text:formatInstallFailure(o.name||e,c)}],isError:!0};log(`[install_from_store] ${s.length} files written to ${a}`);let l=`✅ MCP「${o.name||e}」をインストールしました\n\n`;l+=`📁 インストール先: ${a}\n`,l+=`📄 ファイル数: ${s.length}\n`,o.installNotes&&(l+=`\n📝 ${o.installNotes}\n`);const d=(o.requiredEnvVars||[]).filter(e=>!process.env[e]);return d.length>0&&(l+="\n⚠️ 以下の環境変数を save_mcp_env で設定してください:\n",d.forEach(e=>{l+=` - ${e}\n`})),l+="\n権限に含まれていれば、次の接続で道具一覧に出ます(activate_service を撃つ必要はありません)。",{content:[{type:"text",text:l}]}}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,o,i);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}}}{const e=(this._catalogTools.get(r)||{}).mcpId||String(r).split("__")[0];if(this._childSettling.has(e)&&!await this.waitForChildSettled(e,r))return{content:[{type:"text",text:CHILD_NOT_READY_TEXT}],isError:!0}}try{const{found:e,result:t}=await this.childManager.callTool(r,o);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}}const s=this._catalogTools.get(r);if(s&&"cloud_proxy"!==s.hostingType){const e=s.mcpId;this._catalogFromCache&&this._configRefresh&&(log(`[callTool] "${e}" は目録キャッシュ由来。起動仕様の到着を待つ。`),await this._configRefresh),this.startChildOnDemand(e);const t=await this.childManager.waitForReady(e,3e4);if(!t.ready){if("failed"===t.reason){const r=t.error?.message||String(t.error||"Unknown");return log(`[callTool] "${e}" failed to start: ${r}`),{content:[{type:"text",text:`❌ "${e}" の起動に失敗しました: ${r}`}],isError:!0}}return"timeout"===t.reason?(log(`[callTool] "${e}" not ready within 30000ms for "${r}".`),{content:[{type:"text",text:CHILD_NOT_READY_TEXT}],isError:!0}):(log(`[callTool] "${e}" could not be started for "${r}" (reason=${t.reason}).`),{content:[{type:"text",text:`❌ "${e}" を起動できませんでした(設定が見つかりません)。`}],isError:!0})}if(!await this.waitForChildSettled(e,r))return{content:[{type:"text",text:CHILD_NOT_READY_TEXT}],isError:!0};try{const{found:t,result:i}=await this.childManager.callTool(r,o);if(t)return i&&i.content?i:{content:[{type:"text",text:String(i)}]};log(`[callTool] "${r}" is in catalog but child "${e}" does not have it.`)}catch(e){return log(`[callTool] Error proxying "${r}" after ready: ${e.message}`),{content:[{type:"text",text:`❌ ツール実行エラー: ${e.message}`}],isError:!0}}}return{content:[{type:"text",text:`❌ Unknown tool: ${r}`}],isError:!0}})}startChildrenForMissingSchema(){let e=0;for(const t of this._needChildForSchema){const r=this._catalogServices.get(t);if(r)if("cloud_proxy"!==r.hostingType){if(!this.childManager.isActive(t)&&!this.childManager.isStarting(t)){log(`[schemaChild] "${t}" (${r.hostingType}) を起こして実 schema を取りに行く`);try{this.startChildOnDemand(t),e++}catch(e){log(`[schemaChild] "${t}" の起動に失敗: ${e.message}(⛔ 席は落とさない)`)}}}else log(`[schemaChild] "${t}" は cloud_proxy のため子を起こせない。schema 欠損の道具は一覧に出ない。`)}return log(`[schemaChild] ${e} service(=目録に schema が無い道具を持つ service の数)`),e}async loadCatalog(e){if(!e)return log("[catalog] No token, starting with no catalog tools."),null;let t=[];try{const r=await getGatewayConfig(e);if(!r.success)return log(`[catalog] Server config failed: ${r.error||"Unknown"}. Starting with no services.`),null;t=r.data?.services||[],log(`[catalog] Server config: ${t.length} services`),this.applyCatalog(t)}catch(e){return log(`[catalog] Server config error: ${e.message}. Starting with no services.`),null}try{const e=collectUpdateNotices(t);this.updateNoticeText=buildUpdateNoticeText(e);for(const t of e)"not-applicable"!==t.status&&log(`[versionCheck] ${t.mcpId}: ${t.status} (${t.reason}) catalog=${t.catalogVersion} local=${t.localVersion}`)}catch(e){log(`[versionCheck] skipped: ${e.message}`)}return t}startChildOnDemand(e){const t=this._catalogServices.get(e);if(!t||!0===t.deactivated)return null;if("cloud_proxy"===t.hostingType)return null;if(this.childManager.isActive(e))return null;const r=this.childManager.isStarting(e),o=this.childManager.beginActivateExternal(e,this.buildChildService(e,t));if(r||log(`[startChild] "${e}" (${t.hostingType}) starting on demand...`),!this._childSettling.has(e)){const t=o.then(t=>this.reconcileChildWithCatalog(e,t)).catch(t=>log(`[startChild] "${e}" post-start error: ${t.message}`)).finally(()=>{this._childSettling.get(e)===t&&this._childSettling.delete(e)});this._childSettling.set(e,t)}return o}async waitForChildSettled(e,t){const r=this._childSettling.get(e);if(!r)return!0;let o=null;const i=new Promise(e=>{o=setTimeout(()=>e(!1),3e4),"function"==typeof o.unref&&o.unref()});try{const o=await Promise.race([r.then(()=>!0),i]);return o||log(`[callTool] "${e}" の突き合わせ(自動更新を含む)が 30000ms で済まない → "${t}" は転送しない`),o}finally{o&&clearTimeout(o)}}async reconcileChildWithCatalog(e,t,r={}){const o=decideCatalogReconciliation(this._catalogTools,e,t);if(this._autoUpdating.has(e)&&!r.fromAutoUpdate)return log(`[autoUpdate] "${e}" は更新中 → この突き合わせは見送る`),o;if(!r.fromAutoUpdate){const r=this.evaluateVersionOnDisk(e);if(shouldAutoUpdate({decision:o,notice:r,alreadyTried:this._autoUpdateTried.has(e)}).update){this._autoUpdateTried.add(e);const i=this.autoUpdateAndRestart(e,t,o,r);this._autoUpdating.set(e,i);try{return await i}finally{this._autoUpdating.delete(e)}}}if(!o.ready){const r=t&&t.error?`: ${t.error.message||t.error}`:"";return log(`[versionDrift] "${e}" not ready (${o.reason})${r} → 剪定も通知もしない`),o}if(!o.notify)return log(`[versionDrift] "${e}" matches catalog (${t.tools.length} tools) → no list_changed`),o;for(const e of o.prune)this._catalogTools.delete(e),this._cloudProxyTools.delete(e);const i=o.diff;log(`[versionDrift] "${e}" differs from catalog: +${i.added.length} -${i.removed.length} ~${i.changed.length} → list_changed`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[versionDrift] notification error: ${e.message}`)}return o}evaluateVersionOnDisk(e){const t=this._catalogServices.get(e);return t?evaluateUpdateNotice({mcpId:e,hostingType:t.hostingType,catalogVersion:t.packageVersion,localVersion:readLocalPackageVersion(e,{entryPoint:t.entryPoint})}):null}async autoUpdateAndRestart(e,t,r,o){const i=this._catalogServices.get(e),s=path.join(os.homedir(),".claude","mcp-servers"),n=r.prune.length;log(`[autoUpdate] "${e}" disk=${o.localVersion} catalog=${o.catalogVersion} で ${n} 本が剪定される → 目録の版へ上げる`);const a=await updateMcpOnDisk({mcpId:e,installRoot:s,catalogVersion:o.catalogVersion,entryPoint:i&&i.entryPoint,readLocalVersion:()=>readLocalPackageVersion(e,{installRoot:s,entryPoint:i&&i.entryPoint}),download:()=>downloadMcpPackage(e,resolveToken()),extract:(t,r)=>extractDownloadedPackage({mcpId:e,installDir:t,pkg:r})});if(!a.ok)return log(`[autoUpdate] "${e}" 上げられなかった(${a.reason})→ 今の版のまま剪定する`),this.reconcileChildWithCatalog(e,t,{fromAutoUpdate:!0});this.childManager.deactivate(e);const c=await this.childManager.beginActivateExternal(e,this.buildChildService(e,i));return c.ok?(log(`[autoUpdate] ${e} ${a.from} → ${a.to}(剪定されるはずだった口: ${n} 本)${"other"===a.by?"・同じ機体の別の席が上げた版を使う":""}`),this._autoUpdateRecords.push({mcpId:e,from:a.from,to:a.to,prunedWouldBe:n}),this.reconcileChildWithCatalog(e,c,{fromAutoUpdate:!0})):(log(`[autoUpdate] "${e}" ${a.from} → ${a.to} の後、子が起きなかった: ${c.error&&c.error.message}`),this.reconcileChildWithCatalog(e,c,{fromAutoUpdate:!0}))}persistCatalogCache(e,t){return!!e&&writeCatalogCache(CATALOG_CACHE_FILE,e,catalogCacheFingerprint(t),log)}toolListSignature(){return stableJson(this.buildToolList().map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema})))}async refreshCatalogInBackground(e){const t=this.toolListSignature();let r;try{r=await this.loadCatalog(e)}catch(e){return log(`[catalogRefresh] failed, keeping cached catalog: ${e.message}`),{notified:!1,reason:"failed"}}if(t===this.toolListSignature())return log("[catalogRefresh] server catalog matches cache → no list_changed"),this.persistCatalogCache(r,e),{notified:!1,reason:"same"};log("[catalogRefresh] server catalog differs from cache → list_changed");try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[catalogRefresh] notification error: ${e.message}`)}return this.persistCatalogCache(r,e),{notified:!0,reason:"diff"}}async run(){const e=resolveToken(),t=catalogCacheFingerprint(e),r=readCatalogCache(CATALOG_CACHE_FILE,t,log);let o=null;r?(this.applyCatalog(r,{fromCache:!0}),log(`[catalogCache] ${this._catalogTools.size} tools from cache (HTTP は待たない)`)):(log("[catalogCache] no usable cache, waiting for server config"),o=await this.loadCatalog(e));const i=new StdioServerTransport;await this.server.connect(i),log(`[Gateway] BlueLamp Gateway v2.0.0 started. (${this._catalogTools.size} catalog tools available at connect)`),this.persistCatalogCache(o,e),r?(this._configRefresh=this.refreshCatalogInBackground(e).catch(e=>(log(`[catalogRefresh] unexpected: ${e.message}`),{notified:!1,reason:"failed"})),this._configRefresh.then(()=>this.startChildrenForMissingSchema()).catch(e=>log(`[schemaChild] deferred start failed: ${e.message}`))):this.startChildrenForMissingSchema();const s=resolveToken();if(KEYWORD&&s){log("[Gateway] Auto-loading knowledge");const e=await getPromptFromPortal(KEYWORD,s,LANGUAGE);e&&e.success?log(`[Gateway] Auto-load successful: ${e.title}`):log("[Gateway] Auto-load failed")}}}function buildCatalogRegistry(e){const t=new Map,r=new Map,o=[],i=[];for(const s of Array.isArray(e)?e:[]){if(!s||"string"!=typeof s.mcpId||!s.mcpId)continue;const e=s.hostingType||null;if(!0===s.deactivated)continue;const n=Array.isArray(s.tools)?s.tools:[];if(0===n.length){o.push({mcpId:s.mcpId,hostingType:e,reason:"no-tools"});continue}const a=[];for(const i of n){if(!i||"string"!=typeof i.name||!i.name){o.push({mcpId:s.mcpId,hostingType:e,reason:"unnamed-tool"});continue}i.inputSchema&&"object"==typeof i.inputSchema||a.push(i.name);const n=`${s.mcpId}__${i.name}`,c={name:n,mcpId:s.mcpId,toolName:i.name,hostingType:e,description:i.description,inputSchema:i.inputSchema};t.set(n,c),"cloud_proxy"===e&&r.set(n,{mcpId:s.mcpId,toolName:i.name,description:i.description,inputSchema:i.inputSchema})}a.length>0&&(o.push({mcpId:s.mcpId,hostingType:e,reason:"no-input-schema",count:a.length,total:n.length,tools:a}),i.push(s.mcpId))}return{catalog:t,cloudProxy:r,warnings:o,needChildForSchema:i}}function catalogEntryToTool(e){return{name:e.name,description:e.description||`[${e.mcpId}] ${e.toolName}`,inputSchema:e.inputSchema||{type:"object",properties:{}}}}function buildGatewayToolList(e,t,r){const o=new Map;for(const t of Array.isArray(e)?e:[])t&&t.name&&o.set(t.name,t);if(t&&"function"==typeof t.values)for(const e of t.values())o.has(e.name)||o.set(e.name,catalogEntryToTool(e));for(const e of Array.isArray(r)?r:[])e&&e.name&&o.set(e.name,e);return[...o.values()]}function sanitizeServicesForCache(e){return(Array.isArray(e)?e:[]).filter(e=>e&&"string"==typeof e.mcpId&&e.mcpId).map(e=>({mcpId:e.mcpId,name:e.name,description:e.description,hostingType:e.hostingType||null,deactivated:!0===e.deactivated,tools:(Array.isArray(e.tools)?e.tools:[]).filter(e=>e&&"string"==typeof e.name&&e.name).map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}))}))}function catalogCacheFingerprint(e){return e?require("crypto").createHash("sha256").update(String(e)).digest("hex").slice(0,16):null}function sliceTailPreservingSurrogates(e,t){const r=String(null==e?"":e);if(r.length<=t)return r;const o=r.slice(-t),i=o.charCodeAt(0);return i>=56320&&i<=57343?o.slice(1):o}function stableJson(e){return void 0===e?"null":null===e||"object"!=typeof e?JSON.stringify(e):Array.isArray(e)?`[${e.map(stableJson).join(",")}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${stableJson(e[t])}`).join(",")}}`}function diffChildToolsAgainstCatalog(e,t,r){const o=`${t}__`,i=new Set;if(e&&"function"==typeof e.keys)for(const t of e.keys())"string"==typeof t&&t.startsWith(o)&&i.add(t);const s=[],n=[],a=new Set;for(const t of Array.isArray(r)?r:[]){if(!t||!t.name)continue;a.add(t.name);const r=e&&"function"==typeof e.get?e.get(t.name):void 0;r?stableJson(r.description)===stableJson(t.description)&&stableJson(r.inputSchema)===stableJson(t.inputSchema)||n.push(t.name):s.push(t.name)}const c=[...i].filter(e=>!a.has(e));return{added:s,removed:c,changed:n,hasDiff:s.length+c.length+n.length>0}}function decideCatalogReconciliation(e,t,r){if(!r||!0!==r.ok||!Array.isArray(r.tools))return{ready:!1,prune:[],notify:!1,reason:r&&r.reason||"not-ready",diff:null};const o=diffChildToolsAgainstCatalog(e,t,r.tools);return{ready:!0,prune:o.removed,notify:o.hasDiff,reason:"ready",diff:o}}function readCatalogCache(e,t,r){const o=r||(()=>{});if(!t)return null;let i,s;try{i=fs.readFileSync(e,"utf8")}catch(e){return"ENOENT"!==e.code&&o(`[catalogCache] read failed: ${e.message}`),null}try{s=JSON.parse(i)}catch(e){return o(`[catalogCache] broken JSON, ignoring: ${e.message}`),null}return s&&1===s.version?s.tokenFingerprint!==t?(o("[catalogCache] fingerprint mismatch (口座が違う), ignoring"),null):Array.isArray(s.services)?s.services:(o("[catalogCache] services が配列でない, ignoring"),null):(o(`[catalogCache] version mismatch (${s&&s.version}), ignoring`),null)}function writeCatalogCache(e,t,r,o){const i=o||(()=>{});if(!r)return!1;try{fs.mkdirSync(path.dirname(e),{recursive:!0});const o={version:1,tokenFingerprint:r,savedAt:(new Date).toISOString(),services:sanitizeServicesForCache(t)},i=`${e}.${process.pid}.tmp`;return fs.writeFileSync(i,JSON.stringify(o),{mode:384}),fs.renameSync(i,e),!0}catch(e){return i(`[catalogCache] write failed: ${e.message}`),!1}}function isCloudProxyService(e,t){if(!e||"function"!=typeof e.keys)return!1;const r=`${t}__`;for(const t of e.keys())if("string"==typeof t&&t.startsWith(r))return!0;return!1}if(module.exports={ChildMcpProcess:ChildMcpProcess,ChildMcpManager:ChildMcpManager,BlueLampServer:BlueLampServer,isCloudProxyService:isCloudProxyService,buildCatalogRegistry:buildCatalogRegistry,buildGatewayToolList:buildGatewayToolList,catalogEntryToTool:catalogEntryToTool,diffChildToolsAgainstCatalog:diffChildToolsAgainstCatalog,decideCatalogReconciliation:decideCatalogReconciliation,sanitizeServicesForCache:sanitizeServicesForCache,catalogCacheFingerprint:catalogCacheFingerprint,readCatalogCache:readCatalogCache,writeCatalogCache:writeCatalogCache,CATALOG_CACHE_FILE:CATALOG_CACHE_FILE,CATALOG_CACHE_VERSION:1,stableJson:stableJson,sliceTailPreservingSurrogates:sliceTailPreservingSurrogates,CHILD_READY_TIMEOUT_MS:3e4,CHILD_NOT_READY_TEXT:CHILD_NOT_READY_TEXT},require.main===module){const e=new BlueLampServer;let t=!1;const r=r=>{if(!t){t=!0;try{log(`[Gateway] Shutdown (${r}) - cleaning up children...`)}catch(e){}if(e&&e.childManager&&e.childManager.children)for(const[t,r]of e.childManager.children)try{try{log(`[Gateway] Stopping child: ${t}`)}catch(e){}r&&"external"===r.type&&r.process&&"function"==typeof r.process.stop&&r.process.stop()}catch(e){}setTimeout(()=>process.exit(0),200).unref()}};process.on("SIGTERM",()=>r("SIGTERM")),process.on("SIGINT",()=>r("SIGINT")),process.on("SIGHUP",()=>r("SIGHUP")),process.stdin.on("end",()=>r("stdin-end")),process.stdin.on("close",()=>r("stdin-close")),process.on("exit",()=>{if(e&&e.childManager&&e.childManager.children)for(const[t,r]of e.childManager.children)try{r&&r.process&&r.process.process&&"function"==typeof r.process.process.kill&&r.process.process.kill("SIGKILL")}catch(e){}}),e.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,setServiceDeactivated:setServiceDeactivated,uninstallMcpService:uninstallMcpService,updateMcpEnv:updateMcpEnv,callMcpToolProxy:callMcpToolProxy}=require("./portal-api-client.cjs"),{requireFromPackage:requireFromPackage}=require("../requireFromPackage.cjs"),{logger:logger}=requireFromPackage(require,"../../utils/logger"),{t:t,setLanguage:setLanguage}=requireFromPackage(require,"../../i18n");requireFromPackage(require,"../../lib/connectResilience").applyConnectResilience(require("node:net"));{const{SYS_PROMPT_ENV:e,isOwnSysPromptPath:t}=requireFromPackage(require,"../../lib/sysPromptHandoff"),o=process.env[e];if(t(o,require("node:os").tmpdir()))try{require("node:fs").unlinkSync(o)}catch{}}const{describeTokenForDebug:describeTokenForDebug}=requireFromPackage(require,"../../lib/debugRedaction"),{collectUpdateNotices:collectUpdateNotices,buildUpdateNoticeText:buildUpdateNoticeText,evaluateUpdateNotice:evaluateUpdateNotice,readLocalPackageVersion:readLocalPackageVersion}=require("./mcp-version-advisor.cjs"),{shouldAutoUpdate:shouldAutoUpdate,buildAutoUpdateNoticeText:buildAutoUpdateNoticeText,updateMcpOnDisk:updateMcpOnDisk,needsDiskEntry:needsDiskEntry,buildNotDeployedText:buildNotDeployedText}=require("./auto-update.cjs"),{extractDownloadedPackage:extractDownloadedPackage,formatInstallFailure:formatInstallFailure}=require("./install-package.cjs"),{formatMcpStoreListing:formatMcpStoreListing}=require("./store-listing.cjs"),spawn=require("cross-spawn"),{StringDecoder:StringDecoder}=require("string_decoder"),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-要件定義",{TokenManager:TokenManager}=requireFromPackage(require,"../../auth/TokenManager");function resolveToken(){return TokenManager.loadTokenStringSync()}const PORTAL_URL_ENV=process.env.BLUELAMP_PORTAL_API_BASE_URL||process.env.PORTAL_URL||"",isDevEnv=PORTAL_URL_ENV.includes("localhost"),PORTAL_URL=PORTAL_URL_ENV||(isDevEnv?"http://localhost:8430/api/bluelamp":"https://api.motohi.jp/api/bluelamp"),LANGUAGE=process.env.MUSUHI_LANGUAGE||"ja",CATALOG_CACHE_FILE=path.join(os.homedir(),".bluelamp-vscode","gateway-catalog.json"),CATALOG_CACHE_VERSION=1,CHILD_READY_TIMEOUT_MS=3e4,CHILD_NOT_READY_TEXT="起動中です。数秒後に再試行してください";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?"set":"unset")),log(` TOKEN (起動時スナップショット): ${describeTokenForDebug(resolveToken())}`),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}`)}const EXIT_CLOSE_GRACE_MS=2e3;class ChildMcpProcess{constructor(e,t,o,r){this.serviceId=e,this.command=t,this.args=(o||[]).map(e=>"string"==typeof e?e.replace(/^~\//,os.homedir()+"/"):e),this.env={...process.env,...r},this.process=null,this.tools=[],this.requestId=0,this.pending=new Map,this.buffer="",this.stdoutDecoder=new StringDecoder("utf8"),this.stderrDecoder=new StringDecoder("utf8"),this.stderrTail="",this._decodersEnded=!1}async start(){return new Promise((e,t)=>{let o=!1;const r=(e,t)=>{o||(o=!0,clearTimeout(i),e(t))},i=setTimeout(()=>{r(t,new Error(`Timeout: "${this.serviceId}" failed to start within 90s`)),this.stop()},9e4);try{this.process=spawn(this.command,this.args,{env:this.env,stdio:["pipe","pipe","pipe"]}),this.process.on("error",e=>{r(t,new Error(`Spawn error for "${this.serviceId}": ${e.message}`))}),this.process.stdin.on("error",e=>{log(`[ChildMcp:${this.serviceId}:stdin] ${e.message}`)}),this.process.on("close",()=>{this._flushDecoders("close")});const o=(e,o)=>{clearTimeout(this._exitCloseTimer);const i=this.stderrTail.trim();r(t,new Error(`"${this.serviceId}" exited before ready (code=${e}, signal=${o})${i?` | stderr: ${i}`:""}`)),this.pending.forEach(e=>e.reject(new Error("Process exited"))),this.pending.clear()};this.process.on("exit",(e,t)=>{log(`[ChildMcp] "${this.serviceId}" exited (code=${e}, signal=${t})`),this._exitCloseTimer=setTimeout(()=>o(e,t),2e3)}),this.process.on("close",(e,t)=>o(e,t)),this.process.on("close",()=>{if(this._decodersEnded)return;this._decodersEnded=!0;const e=this.stdoutDecoder.end(),t=this.stderrDecoder.end();e&&(this.buffer+=e,log(`[ChildMcp] "${this.serviceId}" stdout に半端なバイトが ${Buffer.byteLength(e)} バイト残っていた(子が多バイト文字の途中で終了)⇒ buffer へ流した。⛔ これは「壊れた文字」であって「失われた文字」ではない`)),t&&log(`[ChildMcp] "${this.serviceId}" stderr に半端なバイトが ${Buffer.byteLength(t)} バイト残っていた ⇒ ${JSON.stringify(t)}`)}),this.process.stderr.on("data",e=>{const t=this.stderrDecoder.write(e);this.stderrTail=sliceTailPreservingSurrogates(this.stderrTail+t,500),log(`[ChildMcp:${this.serviceId}:stderr] ${t.trim()}`)}),this.process.stdout.on("data",e=>{this.buffer+=this.stdoutDecoder.write(e),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 o=t.tools||[];this.tools=o.map(e=>({...e,name:`${this.serviceId}__${e.name}`,_originalName:e.name})),log(`[ChildMcp] "${this.serviceId}" ready: ${this.tools.length} tools`),r(e,this.tools)}).catch(e=>{r(t,e)})}catch(e){r(t,e)}})}async callTool(e,t){return await this._sendRequest("tools/call",{name:e,arguments:t})}_flushDecoders(e){for(const[t,o]of[["stdout",this.stdoutDecoder],["stderr",this.stderrDecoder]]){let r="";try{r=o.end()}catch(e){log(`[ChildMcp:${this.serviceId}:${t}] decoder.end() が落ちた: ${e.message}`);continue}r&&log(`[ChildMcp:${this.serviceId}:${t}] 🚨 未完の多バイト列を ${e} 時に検出: ${JSON.stringify(r)}(${r.length} 文字・⛔ 黙って捨てていない)`)}}stop(){clearTimeout(this._exitCloseTimer),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((o,r)=>{const i=++this.requestId,s=JSON.stringify({jsonrpc:"2.0",id:i,method:e,params:t})+"\n";this.pending.set(i,{resolve:o,reject:r});try{this.process.stdin.write(s)}catch(e){this.pending.delete(i),r(new Error(`Write error: ${e.message}`))}})}_sendNotification(e,t){const o=JSON.stringify({jsonrpc:"2.0",method:e,params:t})+"\n";try{this.process.stdin.write(o)}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,this.starting=new Map}async activateExternal(e,t){const o=new ChildMcpProcess(e,t.command,t.args||[],t.env||{}),r=await o.start();return this.children.set(e,{type:"external",process:o,tools:r,service:t}),r}beginActivateExternal(e,t,o=null){const r=this.children.get(e);if(r)return Promise.resolve({ok:!0,tools:r.tools||[]});const i=this.starting.get(e);if(i)return i;const s=Promise.resolve().then(()=>o?o():null).then(()=>this.activateExternal(e,t)).then(e=>({ok:!0,tools:e}),e=>({ok:!1,error:e})).then(t=>(this.starting.delete(e),t));return this.starting.set(e,s),s}isStarting(e){return this.starting.has(e)}async waitForReady(e,t){if(this.children.has(e))return{ready:!0};const o=this.starting.get(e);if(!o)return{ready:!1,reason:"unknown"};let r=null;const i=new Promise(e=>{r=setTimeout(()=>e({timedOut:!0}),t),"function"==typeof r.unref&&r.unref()});try{const e=await Promise.race([o,i]);return e.timedOut?{ready:!1,reason:"timeout"}:e.ok?{ready:!0}:{ready:!1,reason:"failed",error:e.error}}finally{r&&clearTimeout(r)}}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[o,r]of this.children)if("external"===r.type){const o=r.tools.find(t=>t.name===e);if(o)return{found:!0,result:await r.process.callTool(o._originalName,t)}}return{found:!1}}getActiveServiceIds(){return[...this.children.keys()]}}class BlueLampServer{constructor(){this.childManager=new ChildMcpManager,this.updateNoticeText=null,this._autoUpdating=new Map,this._autoUpdateTried=new Set,this._autoUpdateRecords=[],this._childSettling=new Map,this._catalogTools=new Map,this._cloudProxyTools=new Map,this._catalogServices=new Map,this._needChildForSchema=new Set,this._catalogFromCache=!1,this._configRefresh=null,this.server=new Server({name:"bluelamp",version:"2.0.0"},{capabilities:{tools:{listChanged:!0}}}),this.setupHandlers()}checkAuth(){return resolveToken()?{ok:!0}:{ok:!1,error:"BlueLamp にログインしてください。ターミナルで `bluelamp1` を一度実行すると ~/.musuhi/portal-token.enc が作成されます。"}}buildToolList(){return buildGatewayToolList([{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:"deactivate_service で一覧から外した MCP を、道具一覧に戻します(deactivate を戻すだけ)。権限のある MCP は最初から一覧に出ているので、通常このツールを撃つ必要はありません。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"},always_on:{type:"boolean",description:"後方互換のため受け付けますが、一覧の可否には使いません(無視されます)"}},required:["service_id"]}},{name:"deactivate_service",description:"指定した MCP を道具一覧から外します(deactivated=true)。次の接続でも一覧に出なくなります。権限は変わらないので activate_service でいつでも戻せます。",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:{},required:[]}},{name:"install_from_store",description:"MCP ストアから MCP を取得してこの機体に展開し、env と導入版をサーバに記録します。browse_mcp_store で見つけた mcp_id を指定してください。道具一覧に出るかどうかは権限で決まるので、権限の無い MCP をこれで一覧に出すことはできません(自分で作った MCP・共有された MCP は権限に含まれるため、何もしなくても一覧に出ます)。",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"]}}],this._catalogTools,this.childManager.getActiveTools())}applyCatalog(e,t={}){const{catalog:o,cloudProxy:r,warnings:i,needChildForSchema:s}=buildCatalogRegistry(e);this._catalogTools=o,this._cloudProxyTools=r,this._catalogServices=new Map,this._needChildForSchema=new Set(s),this._catalogFromCache=!0===t.fromCache;for(const t of Array.isArray(e)?e:[])t&&t.mcpId&&this._catalogServices.set(t.mcpId,t);let n=0;for(const e of i)"no-input-schema"===e.reason?(n+=e.count,log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}): ${e.count}/${e.total} tools have no inputSchema — 一覧には出す(空 schema)。子を起こして実 schema で上書きする`)):log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}) skipped: ${e.reason}`);return n>0&&log(`[catalog] inputSchema 欠損の合計: ${n} 本 / ${this._needChildForSchema.size} service(一覧には出す。子を起こして実 schema で上書きする)`),log(`[catalog] ${o.size} tools registered from ${this._catalogServices.size} services (cloud_proxy: ${r.size})`),o}registerServiceInCatalog(e){const{catalog:t,cloudProxy:o,warnings:r}=buildCatalogRegistry([e]);for(const[e,o]of t)this._catalogTools.set(e,o);for(const[e,t]of o)this._cloudProxyTools.set(e,t);for(const e of r)log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}) skipped: ${e.reason}`);return e&&e.mcpId&&this._catalogServices.set(e.mcpId,e),t.size}unregisterServiceFromCatalog(e){const t=`${e}__`;let o=0;for(const e of[...this._catalogTools.keys()])e.startsWith(t)&&(this._catalogTools.delete(e),this._cloudProxyTools.delete(e),o++);return o}buildChildService(e,t){const o=path.join(os.homedir(),".claude","mcp-servers",e),r=t.entryPoint||"dist/index.js";return{name:t.name,description:t.description,type:"external",command:t.command||"node",args:t.args&&t.args.length?t.args:[path.join(o,r)],env:t.env||{}}}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:o,arguments:r}=e.params;log(`[CallTool] ${o}`);const i=resolveToken();if("load_knowledge"===o){const{keyword:e}=r;log("[load_knowledge] called");try{const o=await getPromptFromPortal(e,i,LANGUAGE);if(!o||!o.success){const r=o?.error||t("mcp.knowledgeNotFound",{keyword:e});return{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:r})}`}],isError:!0}}const r="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"",s=[this.updateNoticeText,buildAutoUpdateNoticeText(this._autoUpdateRecords)].filter(Boolean).map(e=>`\n\n${e}`).join("");return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${o.knowledge}${r}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}${s}`}]}}catch(e){return log(`[load_knowledge] Exception: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.knowledgeLoadError",{error:e.message})}`}],isError:!0}}}if("search_mcp"===o){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};if(!i)return{content:[{type:"text",text:"❌ 未認証です。MCPカタログの検索にはログインが必要です。"}],isError:!0};let t=[];try{const e=await searchMcpServices(r.query,i);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:`🔍 「${r.query}」に一致するサービスはありません。\n\n別のキーワードで試すか、browse_mcp_storeでカタログを確認してください。`}]};let o=`🔍 「${r.query}」の検索結果(${t.length}件)\n\n`;for(const e of t.slice(0,10)){const t=e.id,r=e.service,i=this.childManager.isActive(t),s=!0===r.deactivated,n=e.isInstalled?"":" [未インストール]",a=s?" [一覧から外し中]":"";o+=`${s?"🚫":i?"🟢":"⚪"} **${r.name}**${a}${n} (id: \`${t}\`)\n`,o+=` ${r.description}\n\n`}return o+="一覧に戻す: activate_service | ストアから導入: install_from_store",{content:[{type:"text",text:o}]}}if("activate_service"===o){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t}=r,o=await getGatewayConfig(i);if(!o.success)return{content:[{type:"text",text:`❌ サーバー設定の取得に失敗: ${o.error||"Unknown"}`}],isError:!0};const s=(o.data?.services||[]).find(e=>e.mcpId===t);if(!s)return{content:[{type:"text",text:`❌ サービス "${t}" はあなたの権限の中にありません(権限のある MCP は何もしなくても一覧に出ます)。`}],isError:!0};const n=await setServiceDeactivated(t,!1,i);if(!n.success)return{content:[{type:"text",text:`❌ "${t}" を一覧に戻せませんでした: ${n.error||"Unknown"}`}],isError:!0};log(`[activate] "${t}" deactivated=false (一覧に戻した)`);const a=this.registerServiceInCatalog({...s,deactivated:!1});if(0===a)return{content:[{type:"text",text:`❌ "${t}" には目録にツール定義がありません(tools が空)。`}],isError:!0};if("cloud_proxy"===s.hostingType){log(`[activate] "${t}" (cloud_proxy) → ${a} proxy tools registered`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${s.name}" を一覧に戻しました(cloud_proxy ツール${a}個)`}]}}if(this.childManager.isActive(t)){try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${t}" を一覧に戻しました(子プロセスは既に起動済み)。`}]}}log(`[activate] "${t}" (${s.hostingType}) → ${a} catalog tools restored. 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:`✅ "${s.name}" を一覧に戻しました(ツール${a}個)。最初に呼ばれた時に自動で起動します。`}]}}if("deactivate_service"===o){const{service_id:e}=r;if(!i)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const t=await setServiceDeactivated(e,!0,i);if(!t.success)return{content:[{type:"text",text:`❌ "${e}" を一覧から外せませんでした: ${t.error||"Unknown"}`}],isError:!0};log(`[deactivate] "${e}" deactivated=true (一覧から外した)`);const o=this.unregisterServiceFromCatalog(e);o>0&&log(`[deactivate] "${e}" → ${o} catalog tools removed`),this._catalogServices.delete(e);let s=!1;if(this.childManager.isActive(e)&&(this.childManager.deactivate(e),log(`[deactivate] "${e}" child process stopped`),s=!0),o>0||s)try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[deactivate] notification error: ${e.message}`)}let n=`✅ "${e}" を道具一覧から外しました(deactivated=true)。`;return o>0&&(n+=` ツール${o}個を一覧から削除しました。`),s&&(n+=" 子プロセスを停止しました。"),n+=" 次の接続でも一覧に出ません。権限は変わっていないので activate_service で戻せます。",{content:[{type:"text",text:n}]}}if("restart_service"===o){const{service_id:e}=r,t=this.childManager.children.get(e),o=t?.service;if(!o)return isCloudProxyService(this._cloudProxyTools,e)?{content:[{type:"text",text:`ℹ️ "${e}" は cloud_proxy 方式のため、ローカルの再起動対象ではありません(子プロセスを持ちません)。\nサーバ側のツール更新は deploy で全利用者に反映されるので、再起動は不要です。\nツール一覧を取り直したいだけなら deactivate_service → activate_service を実行してください。`}]}:{content:[{type:"text",text:`❌ "${e}" の子プロセスは現在動いていません。道具を呼べば自動で起動します(restart は起動済みの子にだけ効きます)。`}],isError:!0};let s,n=o;if(i)try{const t=await getGatewayConfig(i);if(t.success){const r=(t.data?.services||[]).find(t=>t.mcpId===e);r&&r.env&&(n={...o,env:r.env},log(`[restart] "${e}" env refreshed from server (keys=${Object.keys(r.env).join(",")||"none"})`))}}catch(e){log(`[restart] env refresh failed (using cached): ${e.message}`)}this.childManager.deactivate(e),log(`[restart] "${e}" stopped`);try{s=await this.childManager.activateExternal(e,n)}catch(t){return log(`[restart] Failed "${e}": ${t.message}`),{content:[{type:"text",text:`❌ "${e}" の再起動に失敗しました: ${t.message}`}],isError:!0}}log(`[restart] "${e}" → ${s.length} tools. Sending list_changed.`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[restart] notification error: ${e.message}`)}const a=s.map(e=>e.name).join(", ");return{content:[{type:"text",text:`🔄 "${n.name}" を再起動しました(ツール${s.length}個)\n\nツール: ${a||"(なし)"}`}]}}if("unregister_service"===o){const{service_id:e}=r;if(!i)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};this.childManager.isActive(e)&&(this.childManager.deactivate(e),log(`[unregister] "${e}" stopped`));const t=await uninstallMcpService(e,i);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"===o){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,env:o}=r;if(!i)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const s=o||{},n=await installMcpService(t,!1,i,s);return n.success?(log(`[register] "${t}" installed on server`),{content:[{type:"text",text:`✅ サービス "${t}" をサーバーに登録しました。\n\n次のステップ:\n- save_mcp_env で APIキー等を設定\n- 次の接続で道具一覧に出ます(activate は不要です)`}]}):{content:[{type:"text",text:`❌ "${t}" の登録に失敗しました: ${n.error||"Unknown"}\n\n※ MCP はサーバーカタログに登録されている必要があります。browse_mcp_store で確認してください。`}],isError:!0}}if("save_mcp_env"===o){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,env:o}=r;if(!t||!o||0===Object.keys(o).length)return{content:[{type:"text",text:"❌ service_id と env(キー/値のペア)が必要です"}],isError:!0};log(`[save_mcp_env] ${t}: ${Object.keys(o).join(", ")}`);try{const e=await updateMcpEnv(t,o,i);if(!e.success)return{content:[{type:"text",text:`❌ サーバー保存失敗: ${e.error}`}],isError:!0};const r=Object.keys(o).join(", ");return e.warning?{content:[{type:"text",text:`🔐 環境変数をサーバーに保存しました\n\nサービス: ${t}\n保存キー: ${r}\n\n${e.warning}`}]}:{content:[{type:"text",text:`🔐 環境変数をサーバーに保存しました\n\nサービス: ${t}\n保存キー: ${r}\n\n※ ローカルには保存されません。次回起動時にサーバーから自動取得されます。`}]}}catch(e){return log(`[save_mcp_env] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("browse_mcp_store"===o){log("[browse_mcp_store]");try{const e=await listAvailableMcps({},i,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ MCPカタログの取得に失敗: ${e?.error||"Unknown"}`}],isError:!0};const t=e.data||[];return 0===t.length?{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]}:{content:[{type:"text",text:formatMcpStoreListing(t)}]}}catch(e){return log(`[browse_mcp_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("install_from_store"===o){const{mcp_id:e}=r;log(`[install_from_store] mcp_id=${e}`);try{const t=await downloadMcpPackage(e,i);if(!t||!t.success)return{content:[{type:"text",text:`❌ MCPダウンロードに失敗: ${t?.error||"Unknown"}`}],isError:!0};const o=t.data,r=o.packageSource||{},s=o.files||[];let n=r.hostingType||r.hosting&&r.hosting.type||null;if(i){let t=!1;try{const o=await getGatewayConfig(i);if(o.success){const r=(o.data?.services||[]).find(t=>t.mcpId===e);r&&(t=!0===r.alwaysOn,n||(n=r.hostingType))}}catch(e){}const o=await installMcpService(e,t,i);if(!o.success)return{content:[{type:"text",text:`❌ サーバー登録失敗: ${o.error||"Unknown"}`}],isError:!0};log(`[install_from_store] Server sync OK: ${e} (alwaysOn=${t})`)}if("cloud_proxy"===n){log(`[install_from_store] "${e}" is cloud_proxy, skipping local extract`);let t=`✅ MCP「${r.name||e}」をインストールしました(cloud_proxy)\n\n`;return t+="☁ クラウド実行のためローカル展開は不要です\n",r.installNotes&&(t+=`\n📝 ${r.installNotes}\n`),t+="\n権限に含まれていれば、次の接続で道具一覧に出ます(activate_service を撃つ必要はありません)。",{content:[{type:"text",text:t}]}}const a=path.join(os.homedir(),".claude","mcp-servers",e),c=await extractDownloadedPackage({mcpId:e,installDir:a,pkg:o});if("empty"===c.stage)return{content:[{type:"text",text:"❌ MCPパッケージにファイルが含まれていません。"}],isError:!0};if(!c.ok)return log(`[install_from_store] ${e} failed at ${c.stage}: ${c.error.message}`),{content:[{type:"text",text:formatInstallFailure(r.name||e,c)}],isError:!0};log(`[install_from_store] ${s.length} files written to ${a}`);let l=`✅ MCP「${r.name||e}」をインストールしました\n\n`;l+=`📁 インストール先: ${a}\n`,l+=`📄 ファイル数: ${s.length}\n`,r.installNotes&&(l+=`\n📝 ${r.installNotes}\n`);const d=(r.requiredEnvVars||[]).filter(e=>!process.env[e]);return d.length>0&&(l+="\n⚠️ 以下の環境変数を save_mcp_env で設定してください:\n",d.forEach(e=>{l+=` - ${e}\n`})),l+="\n権限に含まれていれば、次の接続で道具一覧に出ます(activate_service を撃つ必要はありません)。",{content:[{type:"text",text:l}]}}catch(e){return log(`[install_from_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if(this._cloudProxyTools&&this._cloudProxyTools.has(o)){const e=this._cloudProxyTools.get(o);log(`[callTool] Cloud proxy: ${o} → ${e.mcpId}/${e.toolName}`);try{const t=await callMcpToolProxy(e.mcpId,e.toolName,r,i);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 "${o}": ${e.message}`),{content:[{type:"text",text:`❌ クラウドプロキシエラー: ${e.message}`}],isError:!0}}}{const e=(this._catalogTools.get(o)||{}).mcpId||String(o).split("__")[0];if(this._childSettling.has(e)&&!await this.waitForChildSettled(e,o))return{content:[{type:"text",text:CHILD_NOT_READY_TEXT}],isError:!0}}try{const{found:e,result:t}=await this.childManager.callTool(o,r);if(e)return t&&t.content?t:{content:[{type:"text",text:String(t)}]}}catch(e){return log(`[callTool] Error proxying "${o}": ${e.message}`),{content:[{type:"text",text:`❌ ツール実行エラー: ${e.message}`}],isError:!0}}const s=this._catalogTools.get(o);if(s&&"cloud_proxy"!==s.hostingType){const e=s.mcpId;this._catalogFromCache&&this._configRefresh&&(log(`[callTool] "${e}" は目録キャッシュ由来。起動仕様の到着を待つ。`),await this._configRefresh),this.startChildOnDemand(e,{deploy:!0});const t=await this.childManager.waitForReady(e,3e4);if(!t.ready){if("failed"===t.reason){const o=t.error?.message||String(t.error||"Unknown");return log(`[callTool] "${e}" failed to start: ${o}`),{content:[{type:"text",text:`❌ "${e}" の起動に失敗しました: ${o}`}],isError:!0}}return"timeout"===t.reason?(log(`[callTool] "${e}" not ready within 30000ms for "${o}".`),{content:[{type:"text",text:CHILD_NOT_READY_TEXT}],isError:!0}):(log(`[callTool] "${e}" could not be started for "${o}" (reason=${t.reason}).`),{content:[{type:"text",text:`❌ "${e}" を起動できませんでした(設定が見つかりません)。`}],isError:!0})}if(!await this.waitForChildSettled(e,o))return{content:[{type:"text",text:CHILD_NOT_READY_TEXT}],isError:!0};try{const{found:t,result:i}=await this.childManager.callTool(o,r);if(t)return i&&i.content?i:{content:[{type:"text",text:String(i)}]};log(`[callTool] "${o}" is in catalog but child "${e}" does not have it.`)}catch(e){return log(`[callTool] Error proxying "${o}" after ready: ${e.message}`),{content:[{type:"text",text:`❌ ツール実行エラー: ${e.message}`}],isError:!0}}}return{content:[{type:"text",text:`❌ Unknown tool: ${o}`}],isError:!0}})}startChildrenForMissingSchema(){let e=0;for(const t of this._needChildForSchema){const o=this._catalogServices.get(t);if(o)if("cloud_proxy"!==o.hostingType){if(!this.childManager.isActive(t)&&!this.childManager.isStarting(t)){log(`[schemaChild] "${t}" (${o.hostingType}) を起こして実 schema を取りに行く`);try{this.startChildOnDemand(t),e++}catch(e){log(`[schemaChild] "${t}" の起動に失敗: ${e.message}(⛔ 席は落とさない)`)}}}else log(`[schemaChild] "${t}" は cloud_proxy のため子を起こせない。schema 欠損の道具は一覧に出ない。`)}return log(`[schemaChild] ${e} service(=目録に schema が無い道具を持つ service の数)`),e}async loadCatalog(e){if(!e)return log("[catalog] No token, starting with no catalog tools."),null;let t=[];try{const o=await getGatewayConfig(e);if(!o.success)return log(`[catalog] Server config failed: ${o.error||"Unknown"}. Starting with no services.`),null;t=o.data?.services||[],log(`[catalog] Server config: ${t.length} services`),this.applyCatalog(t)}catch(e){return log(`[catalog] Server config error: ${e.message}. Starting with no services.`),null}try{const e=collectUpdateNotices(t);this.updateNoticeText=buildUpdateNoticeText(e);for(const t of e)"not-applicable"!==t.status&&log(`[versionCheck] ${t.mcpId}: ${t.status} (${t.reason}) catalog=${t.catalogVersion} local=${t.localVersion}`)}catch(e){log(`[versionCheck] skipped: ${e.message}`)}return t}startChildOnDemand(e,{deploy:t=!1}={}){const o=this._catalogServices.get(e);if(!o||!0===o.deactivated)return null;if("cloud_proxy"===o.hostingType)return null;if(this.childManager.isActive(e))return null;const r=this.childManager.isStarting(e),i=this.childManager.beginActivateExternal(e,this.buildChildService(e,o),()=>this.ensureDeployedOnDisk(e,o,{deploy:t}));if(r||log(`[startChild] "${e}" (${o.hostingType}) starting on demand...`),!this._childSettling.has(e)){const t=i.then(t=>this.reconcileChildWithCatalog(e,t)).catch(t=>log(`[startChild] "${e}" post-start error: ${t.message}`)).finally(()=>{this._childSettling.get(e)===t&&this._childSettling.delete(e)});this._childSettling.set(e,t)}return i}async waitForChildSettled(e,t){const o=this._childSettling.get(e);if(!o)return!0;let r=null;const i=new Promise(e=>{r=setTimeout(()=>e(!1),3e4),"function"==typeof r.unref&&r.unref()});try{const r=await Promise.race([o.then(()=>!0),i]);return r||log(`[callTool] "${e}" の突き合わせ(自動更新を含む)が 30000ms で済まない → "${t}" は転送しない`),r}finally{r&&clearTimeout(r)}}async reconcileChildWithCatalog(e,t,o={}){const r=decideCatalogReconciliation(this._catalogTools,e,t);if(this._autoUpdating.has(e)&&!o.fromAutoUpdate)return log(`[autoUpdate] "${e}" は更新中 → この突き合わせは見送る`),r;if(!o.fromAutoUpdate){const o=this.evaluateVersionOnDisk(e);if(shouldAutoUpdate({decision:r,notice:o,alreadyTried:this._autoUpdateTried.has(e)}).update){this._autoUpdateTried.add(e);const i=this.autoUpdateAndRestart(e,t,r,o);this._autoUpdating.set(e,i);try{return await i}finally{this._autoUpdating.delete(e)}}}if(!r.ready){const o=t&&t.error?`: ${t.error.message||t.error}`:"";return log(`[versionDrift] "${e}" not ready (${r.reason})${o} → 剪定も通知もしない`),r}if(!r.notify)return log(`[versionDrift] "${e}" matches catalog (${t.tools.length} tools) → no list_changed`),r;for(const e of r.prune)this._catalogTools.delete(e),this._cloudProxyTools.delete(e);const i=r.diff;log(`[versionDrift] "${e}" differs from catalog: +${i.added.length} -${i.removed.length} ~${i.changed.length} → list_changed`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[versionDrift] notification error: ${e.message}`)}return r}async ensureDeployedOnDisk(e,t,{deploy:o}){if(!needsDiskEntry(t))return;const r=path.join(os.homedir(),".claude","mcp-servers"),i=path.join(r,e,t.entryPoint||"dist/index.js");if(fs.existsSync(i))return;if(!o)throw new Error(buildNotDeployedText(e,"道具が呼ばれるまで展開しない"));if(!t.packageVersion)throw new Error(buildNotDeployedText(e,"目録に版が無い"));log(`[deploy] "${e}" は disk に実体が無い(${i})→ 目録の版 ${t.packageVersion} で展開する`);const s=await updateMcpOnDisk({mcpId:e,installRoot:r,catalogVersion:t.packageVersion,entryPoint:t.entryPoint,readLocalVersion:()=>fs.existsSync(i)?readLocalPackageVersion(e,{installRoot:r,entryPoint:t.entryPoint}):null,download:()=>downloadMcpPackage(e,resolveToken()),extract:(t,o)=>extractDownloadedPackage({mcpId:e,installDir:t,pkg:o})});if(!s.ok||!fs.existsSync(i)){const o=s.ok?`展開の後も ${t.entryPoint||"dist/index.js"} が無い`:s.reason;throw log(`[deploy] "${e}" 展開できなかった(${o})`),new Error(buildNotDeployedText(e,o))}log(`[deploy] "${e}" 未展開 → ${s.to} を展開した${"other"===s.by?"(同じ機体の別の席が展開した版を使う)":""}`),this._autoUpdateRecords.push({mcpId:e,from:null,to:s.to,prunedWouldBe:0})}evaluateVersionOnDisk(e){const t=this._catalogServices.get(e);return t?evaluateUpdateNotice({mcpId:e,hostingType:t.hostingType,catalogVersion:t.packageVersion,localVersion:readLocalPackageVersion(e,{entryPoint:t.entryPoint})}):null}async autoUpdateAndRestart(e,t,o,r){const i=this._catalogServices.get(e),s=path.join(os.homedir(),".claude","mcp-servers"),n=o.prune.length;log(`[autoUpdate] "${e}" disk=${r.localVersion} catalog=${r.catalogVersion} で ${n} 本が剪定される → 目録の版へ上げる`);const a=await updateMcpOnDisk({mcpId:e,installRoot:s,catalogVersion:r.catalogVersion,entryPoint:i&&i.entryPoint,readLocalVersion:()=>readLocalPackageVersion(e,{installRoot:s,entryPoint:i&&i.entryPoint}),download:()=>downloadMcpPackage(e,resolveToken()),extract:(t,o)=>extractDownloadedPackage({mcpId:e,installDir:t,pkg:o})});if(!a.ok)return log(`[autoUpdate] "${e}" 上げられなかった(${a.reason})→ 今の版のまま剪定する`),this.reconcileChildWithCatalog(e,t,{fromAutoUpdate:!0});this.childManager.deactivate(e);const c=await this.childManager.beginActivateExternal(e,this.buildChildService(e,i));return c.ok?(log(`[autoUpdate] ${e} ${a.from} → ${a.to}(剪定されるはずだった口: ${n} 本)${"other"===a.by?"・同じ機体の別の席が上げた版を使う":""}`),this._autoUpdateRecords.push({mcpId:e,from:a.from,to:a.to,prunedWouldBe:n}),this.reconcileChildWithCatalog(e,c,{fromAutoUpdate:!0})):(log(`[autoUpdate] "${e}" ${a.from} → ${a.to} の後、子が起きなかった: ${c.error&&c.error.message}`),this.reconcileChildWithCatalog(e,c,{fromAutoUpdate:!0}))}persistCatalogCache(e,t){return!!e&&writeCatalogCache(CATALOG_CACHE_FILE,e,catalogCacheFingerprint(t),log)}toolListSignature(){return stableJson(this.buildToolList().map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema})))}async refreshCatalogInBackground(e){const t=this.toolListSignature();let o;try{o=await this.loadCatalog(e)}catch(e){return log(`[catalogRefresh] failed, keeping cached catalog: ${e.message}`),{notified:!1,reason:"failed"}}if(t===this.toolListSignature())return log("[catalogRefresh] server catalog matches cache → no list_changed"),this.persistCatalogCache(o,e),{notified:!1,reason:"same"};log("[catalogRefresh] server catalog differs from cache → list_changed");try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[catalogRefresh] notification error: ${e.message}`)}return this.persistCatalogCache(o,e),{notified:!0,reason:"diff"}}async run(){const e=resolveToken(),t=catalogCacheFingerprint(e),o=readCatalogCache(CATALOG_CACHE_FILE,t,log);let r=null;o?(this.applyCatalog(o,{fromCache:!0}),log(`[catalogCache] ${this._catalogTools.size} tools from cache (HTTP は待たない)`)):(log("[catalogCache] no usable cache, waiting for server config"),r=await this.loadCatalog(e));const i=new StdioServerTransport;await this.server.connect(i),log(`[Gateway] BlueLamp Gateway v2.0.0 started. (${this._catalogTools.size} catalog tools available at connect)`),this.persistCatalogCache(r,e),o?(this._configRefresh=this.refreshCatalogInBackground(e).catch(e=>(log(`[catalogRefresh] unexpected: ${e.message}`),{notified:!1,reason:"failed"})),this._configRefresh.then(()=>this.startChildrenForMissingSchema()).catch(e=>log(`[schemaChild] deferred start failed: ${e.message}`))):this.startChildrenForMissingSchema();const s=resolveToken();if(KEYWORD&&s){log("[Gateway] Auto-loading knowledge");const e=await getPromptFromPortal(KEYWORD,s,LANGUAGE);e&&e.success?log(`[Gateway] Auto-load successful: ${e.title}`):log("[Gateway] Auto-load failed")}}}function buildCatalogRegistry(e){const t=new Map,o=new Map,r=[],i=[];for(const s of Array.isArray(e)?e:[]){if(!s||"string"!=typeof s.mcpId||!s.mcpId)continue;const e=s.hostingType||null;if(!0===s.deactivated)continue;const n=Array.isArray(s.tools)?s.tools:[];if(0===n.length){r.push({mcpId:s.mcpId,hostingType:e,reason:"no-tools"});continue}const a=[];for(const i of n){if(!i||"string"!=typeof i.name||!i.name){r.push({mcpId:s.mcpId,hostingType:e,reason:"unnamed-tool"});continue}i.inputSchema&&"object"==typeof i.inputSchema||a.push(i.name);const n=`${s.mcpId}__${i.name}`,c={name:n,mcpId:s.mcpId,toolName:i.name,hostingType:e,description:i.description,inputSchema:i.inputSchema};t.set(n,c),"cloud_proxy"===e&&o.set(n,{mcpId:s.mcpId,toolName:i.name,description:i.description,inputSchema:i.inputSchema})}a.length>0&&(r.push({mcpId:s.mcpId,hostingType:e,reason:"no-input-schema",count:a.length,total:n.length,tools:a}),i.push(s.mcpId))}return{catalog:t,cloudProxy:o,warnings:r,needChildForSchema:i}}function catalogEntryToTool(e){return{name:e.name,description:e.description||`[${e.mcpId}] ${e.toolName}`,inputSchema:e.inputSchema||{type:"object",properties:{}}}}function buildGatewayToolList(e,t,o){const r=new Map;for(const t of Array.isArray(e)?e:[])t&&t.name&&r.set(t.name,t);if(t&&"function"==typeof t.values)for(const e of t.values())r.has(e.name)||r.set(e.name,catalogEntryToTool(e));for(const e of Array.isArray(o)?o:[])e&&e.name&&r.set(e.name,e);return[...r.values()]}function sanitizeServicesForCache(e){return(Array.isArray(e)?e:[]).filter(e=>e&&"string"==typeof e.mcpId&&e.mcpId).map(e=>({mcpId:e.mcpId,name:e.name,description:e.description,hostingType:e.hostingType||null,deactivated:!0===e.deactivated,tools:(Array.isArray(e.tools)?e.tools:[]).filter(e=>e&&"string"==typeof e.name&&e.name).map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}))}))}function catalogCacheFingerprint(e){return e?require("crypto").createHash("sha256").update(String(e)).digest("hex").slice(0,16):null}function sliceTailPreservingSurrogates(e,t){const o=String(null==e?"":e);if(o.length<=t)return o;const r=o.slice(-t),i=r.charCodeAt(0);return i>=56320&&i<=57343?r.slice(1):r}function stableJson(e){return void 0===e?"null":null===e||"object"!=typeof e?JSON.stringify(e):Array.isArray(e)?`[${e.map(stableJson).join(",")}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${stableJson(e[t])}`).join(",")}}`}function diffChildToolsAgainstCatalog(e,t,o){const r=`${t}__`,i=new Set;if(e&&"function"==typeof e.keys)for(const t of e.keys())"string"==typeof t&&t.startsWith(r)&&i.add(t);const s=[],n=[],a=new Set;for(const t of Array.isArray(o)?o:[]){if(!t||!t.name)continue;a.add(t.name);const o=e&&"function"==typeof e.get?e.get(t.name):void 0;o?stableJson(o.description)===stableJson(t.description)&&stableJson(o.inputSchema)===stableJson(t.inputSchema)||n.push(t.name):s.push(t.name)}const c=[...i].filter(e=>!a.has(e));return{added:s,removed:c,changed:n,hasDiff:s.length+c.length+n.length>0}}function decideCatalogReconciliation(e,t,o){if(!o||!0!==o.ok||!Array.isArray(o.tools))return{ready:!1,prune:[],notify:!1,reason:o&&o.reason||"not-ready",diff:null};const r=diffChildToolsAgainstCatalog(e,t,o.tools);return{ready:!0,prune:r.removed,notify:r.hasDiff,reason:"ready",diff:r}}function readCatalogCache(e,t,o){const r=o||(()=>{});if(!t)return null;let i,s;try{i=fs.readFileSync(e,"utf8")}catch(e){return"ENOENT"!==e.code&&r(`[catalogCache] read failed: ${e.message}`),null}try{s=JSON.parse(i)}catch(e){return r(`[catalogCache] broken JSON, ignoring: ${e.message}`),null}return s&&1===s.version?s.tokenFingerprint!==t?(r("[catalogCache] fingerprint mismatch (口座が違う), ignoring"),null):Array.isArray(s.services)?s.services:(r("[catalogCache] services が配列でない, ignoring"),null):(r(`[catalogCache] version mismatch (${s&&s.version}), ignoring`),null)}function writeCatalogCache(e,t,o,r){const i=r||(()=>{});if(!o)return!1;try{fs.mkdirSync(path.dirname(e),{recursive:!0});const r={version:1,tokenFingerprint:o,savedAt:(new Date).toISOString(),services:sanitizeServicesForCache(t)},i=`${e}.${process.pid}.tmp`;return fs.writeFileSync(i,JSON.stringify(r),{mode:384}),fs.renameSync(i,e),!0}catch(e){return i(`[catalogCache] write failed: ${e.message}`),!1}}function isCloudProxyService(e,t){if(!e||"function"!=typeof e.keys)return!1;const o=`${t}__`;for(const t of e.keys())if("string"==typeof t&&t.startsWith(o))return!0;return!1}if(module.exports={ChildMcpProcess:ChildMcpProcess,ChildMcpManager:ChildMcpManager,BlueLampServer:BlueLampServer,isCloudProxyService:isCloudProxyService,buildCatalogRegistry:buildCatalogRegistry,buildGatewayToolList:buildGatewayToolList,catalogEntryToTool:catalogEntryToTool,diffChildToolsAgainstCatalog:diffChildToolsAgainstCatalog,decideCatalogReconciliation:decideCatalogReconciliation,sanitizeServicesForCache:sanitizeServicesForCache,catalogCacheFingerprint:catalogCacheFingerprint,readCatalogCache:readCatalogCache,writeCatalogCache:writeCatalogCache,CATALOG_CACHE_FILE:CATALOG_CACHE_FILE,CATALOG_CACHE_VERSION:1,stableJson:stableJson,sliceTailPreservingSurrogates:sliceTailPreservingSurrogates,CHILD_READY_TIMEOUT_MS:3e4,CHILD_NOT_READY_TEXT:CHILD_NOT_READY_TEXT},require.main===module){const e=new BlueLampServer;let t=!1;const o=o=>{if(!t){t=!0;try{log(`[Gateway] Shutdown (${o}) - cleaning up children...`)}catch(e){}if(e&&e.childManager&&e.childManager.children)for(const[t,o]of e.childManager.children)try{try{log(`[Gateway] Stopping child: ${t}`)}catch(e){}o&&"external"===o.type&&o.process&&"function"==typeof o.process.stop&&o.process.stop()}catch(e){}setTimeout(()=>process.exit(0),200).unref()}};process.on("SIGTERM",()=>o("SIGTERM")),process.on("SIGINT",()=>o("SIGINT")),process.on("SIGHUP",()=>o("SIGHUP")),process.stdin.on("end",()=>o("stdin-end")),process.stdin.on("close",()=>o("stdin-close")),process.on("exit",()=>{if(e&&e.childManager&&e.childManager.children)for(const[t,o]of e.childManager.children)try{o&&o.process&&o.process.process&&"function"==typeof o.process.process.kill&&o.process.process.kill("SIGKILL")}catch(e){}}),e.run().catch(e=>{log(`[Gateway] Fatal: ${e.message}`),process.exit(1)})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluelamp",
3
- "version": "6.6.28",
3
+ "version": "6.6.29",
4
4
  "description": "BlueLamp CLI: 旧 bluelampportal/mcp の auth / 200+ bluelamp1-N binary / postinstall scripts を TypeScript 化して monorepo に統合したパッケージ。",
5
5
  "keywords": [
6
6
  "bluelamp",