bluelamp 6.6.24 → 6.6.25

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
- "use strict";const crypto=require("node:crypto"),fs=require("node:fs"),os=require("node:os"),path=require("node:path"),VERSION=1,EVENT_TO_EV={SessionStart:"session_start",UserPromptSubmit:"prompt",PostToolUse:"tool",PostToolUseFailure:"tool_fail",Stop:"stop",StopFailure:"api_error",SubagentStop:"subagent_stop",SessionEnd:"session_end"},HOOK_EVENTS=Object.keys(EVENT_TO_EV),ERR_KIND=/^[a-z][a-z0-9_]{0,63}$/,SID_SHAPE=/^[A-Za-z0-9-]{1,128}$/,TOOL_SHAPE=/^[A-Za-z0-9_.:-]{1,200}$/,ROLE_SHAPE=/^[^\r\n]{1,200}$/,ANSI_SEQUENCE=/\x1b(?:\[[0-?]*[ -/]*[@-~]|[@-Z\\-_])?/g;function sha16(t){return crypto.createHash("sha256").update(t,"utf8").digest("hex").slice(0,16)}function normalizePrompt(t){return t.replace(ANSI_SEQUENCE,"").replace(/\s+/gu,"")}function footprintDirName(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function footprintFile(t,o,n){return path.join(t,".bluelamp","footprints",footprintDirName(o),`${n}.jsonl`)}function nonEmptyString(t){return"string"==typeof t&&t.length>0?t:null}function toFootprint(t,o,n){if(!t||"object"!=typeof t||Array.isArray(t))return null;const e=EVENT_TO_EV[t.hook_event_name];if(!e)return null;const r=nonEmptyString(t.session_id),s=nonEmptyString(t.cwd);if(!r||!SID_SHAPE.test(r)||!s)return null;const i={v:1,ts:n.toISOString(),sid:r,cwd:s,ev:e};if("session_start"===e){const t=nonEmptyString(o.BLUELAMP_FOOTPRINT_ROLE);t&&ROLE_SHAPE.test(t)&&(i.role=t)}else if("prompt"===e)"string"==typeof t.prompt&&(i.psha=sha16(t.prompt),i.pnsha=sha16(normalizePrompt(t.prompt)));else if("tool"===e||"tool_fail"===e){const o=nonEmptyString(t.tool_name);o&&TOOL_SHAPE.test(o)&&(i.tool=o),i.ok="tool"===e,"CronCreate"===o&&t.tool_input&&"boolean"==typeof t.tool_input.recurring&&(i.recurring=t.tool_input.recurring)}else if("api_error"===e){const o=nonEmptyString(t.error);o&&ERR_KIND.test(o)&&(i.err=o)}const a=nonEmptyString(o.MONSTER_ID);return a&&SID_SHAPE.test(a)&&(i.mid=a),i}function dirCwdOf(t,o){const n=o&&o.BLUELAMP_FOOTPRINT_DIR_CWD;return"string"==typeof n&&n.length>0?n:t.cwd}const SESSION_UUID=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,PROJECT_DIR_NAME=/^[A-Za-z0-9-]{1,4096}$/;function claudeConfigDirOf(t,o){const n=o&&o.CLAUDE_CONFIG_DIR;return"string"==typeof n&&n.length>0&&path.isAbsolute(n)?path.normalize(n):path.join(t,".claude")}function projectsRootOf(t){return path.join(t,"projects")}function toolResultsDirToClean(t,o){if(!t||"object"!=typeof t||Array.isArray(t))return null;if("SessionEnd"!==t.hook_event_name)return null;const n=t.session_id,e=t.transcript_path;if("string"!=typeof n||!SESSION_UUID.test(n))return null;if("string"!=typeof e||!path.isAbsolute(e)||path.normalize(e)!==e)return null;if(path.basename(e)!==`${n}.jsonl`)return null;const r=path.dirname(e);return path.dirname(r)!==projectsRootOf(o)?null:PROJECT_DIR_NAME.test(path.basename(r))?path.join(r,n,"tool-results"):null}function cleanToolResults(t,o){const n=path.dirname(o),e=path.dirname(n),r=fs.realpathSync(projectsRootOf(t));for(const t of[e,n,o]){const o=fs.lstatSync(t,{throwIfNoEntry:!1});if(!o||o.isSymbolicLink()||!o.isDirectory())return!1}const s=path.join(r,path.basename(e),path.basename(n),"tool-results");if(fs.realpathSync(o)!==s)return!1;fs.rmSync(o,{recursive:!0,force:!0});try{fs.rmdirSync(n)}catch{}return!0}function quoteForShell(t){return`"${t.replace(/(["\\$`])/g,"\\$1")}"`}function buildSettingsJson(t,o){const n=`${quoteForShell(t)} ${quoteForShell(o)}`,e={};for(const t of HOOK_EVENTS)e[t]=[{hooks:[{type:"command",command:n}]}];return JSON.stringify({hooks:e})}function appendFootprint(t,o,n={}){const e=footprintFile(t,dirCwdOf(o,n),o.sid);return fs.mkdirSync(path.dirname(e),{recursive:!0,mode:448}),fs.appendFileSync(e,`${JSON.stringify(o)}\n`,{encoding:"utf8",mode:384}),e}function main(){"--settings-json"===process.argv[2]&&(process.stdout.write(buildSettingsJson(process.execPath,__filename)),process.exit(0));try{const t=fs.readFileSync(0,"utf8"),o=JSON.parse(t);try{const t=toFootprint(o,process.env,new Date);t&&appendFootprint(os.homedir(),t,process.env)}catch{}const n=claudeConfigDirOf(os.homedir(),process.env),e=toolResultsDirToClean(o,n);e&&cleanToolResults(n,e)}catch{}process.exit(0)}module.exports={HOOK_EVENTS:HOOK_EVENTS,buildSettingsJson:buildSettingsJson,toFootprint:toFootprint,claudeConfigDirOf:claudeConfigDirOf,toolResultsDirToClean:toolResultsDirToClean,cleanToolResults:cleanToolResults,normalizePrompt:normalizePrompt,footprintDirName:footprintDirName,footprintFile:footprintFile,dirCwdOf:dirCwdOf,appendFootprint:appendFootprint},require.main===module&&main();
2
+ "use strict";const crypto=require("node:crypto"),fs=require("node:fs"),os=require("node:os"),path=require("node:path"),VERSION=1,EVENT_TO_EV={SessionStart:"session_start",UserPromptSubmit:"prompt",PostToolUse:"tool",PostToolUseFailure:"tool_fail",Stop:"stop",StopFailure:"api_error",SubagentStop:"subagent_stop",SessionEnd:"session_end"},HOOK_EVENTS=[...Object.keys(EVENT_TO_EV),"Notification","PreToolUse"],HOOK_MATCHERS={PreToolUse:"AskUserQuestion"},ERR_KIND=/^[a-z][a-z0-9_]{0,63}$/,SID_SHAPE=/^[A-Za-z0-9-]{1,128}$/,TOOL_SHAPE=/^[A-Za-z0-9_.:-]{1,200}$/,ROLE_SHAPE=/^[^\r\n]{1,200}$/,ANSI_SEQUENCE=/\x1b(?:\[[0-?]*[ -/]*[@-~]|[@-Z\\-_])?/g;function sha16(t){return crypto.createHash("sha256").update(t,"utf8").digest("hex").slice(0,16)}function normalizePrompt(t){return t.replace(ANSI_SEQUENCE,"").replace(/\s+/gu,"")}function footprintDirName(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function footprintFile(t,n,e){return path.join(t,".bluelamp","footprints",footprintDirName(n),`${e}.jsonl`)}function nonEmptyString(t){return"string"==typeof t&&t.length>0?t:null}function toFootprint(t,n,e){if(!t||"object"!=typeof t||Array.isArray(t))return null;const o=EVENT_TO_EV[t.hook_event_name];if(!o)return null;const r=nonEmptyString(t.session_id),i=nonEmptyString(t.cwd);if(!r||!SID_SHAPE.test(r)||!i)return null;const s={v:1,ts:e.toISOString(),sid:r,cwd:i,ev:o};if("session_start"===o){const t=nonEmptyString(n.BLUELAMP_FOOTPRINT_ROLE);t&&ROLE_SHAPE.test(t)&&(s.role=t)}else if("prompt"===o)"string"==typeof t.prompt&&(s.psha=sha16(t.prompt),s.pnsha=sha16(normalizePrompt(t.prompt)));else if("tool"===o||"tool_fail"===o){const n=nonEmptyString(t.tool_name);n&&TOOL_SHAPE.test(n)&&(s.tool=n),s.ok="tool"===o,"CronCreate"===n&&t.tool_input&&"boolean"==typeof t.tool_input.recurring&&(s.recurring=t.tool_input.recurring)}else if("api_error"===o){const n=nonEmptyString(t.error);n&&ERR_KIND.test(n)&&(s.err=n)}const a=nonEmptyString(n.MONSTER_ID);return a&&SID_SHAPE.test(a)&&(s.mid=a),s}function dirCwdOf(t,n){const e=n&&n.BLUELAMP_FOOTPRINT_DIR_CWD;return"string"==typeof e&&e.length>0?e:t.cwd}const SESSION_UUID=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,PROJECT_DIR_NAME=/^[A-Za-z0-9-]{1,4096}$/;function claudeConfigDirOf(t,n){const e=n&&n.CLAUDE_CONFIG_DIR;return"string"==typeof e&&e.length>0&&path.isAbsolute(e)?path.normalize(e):path.join(t,".claude")}function projectsRootOf(t){return path.join(t,"projects")}function toolResultsDirToClean(t,n){if(!t||"object"!=typeof t||Array.isArray(t))return null;if("SessionEnd"!==t.hook_event_name)return null;const e=t.session_id,o=t.transcript_path;if("string"!=typeof e||!SESSION_UUID.test(e))return null;if("string"!=typeof o||!path.isAbsolute(o)||path.normalize(o)!==o)return null;if(path.basename(o)!==`${e}.jsonl`)return null;const r=path.dirname(o);return path.dirname(r)!==projectsRootOf(n)?null:PROJECT_DIR_NAME.test(path.basename(r))?path.join(r,e,"tool-results"):null}function cleanToolResults(t,n){const e=path.dirname(n),o=path.dirname(e),r=fs.realpathSync(projectsRootOf(t));for(const t of[o,e,n]){const n=fs.lstatSync(t,{throwIfNoEntry:!1});if(!n||n.isSymbolicLink()||!n.isDirectory())return!1}const i=path.join(r,path.basename(o),path.basename(e),"tool-results");if(fs.realpathSync(n)!==i)return!1;fs.rmSync(n,{recursive:!0,force:!0});try{fs.rmdirSync(e)}catch{}return!0}const WAITING_HEAD_MAX=80,BELL_TTY_SHAPE=/^\/dev\/(?:ttys\d{1,4}|pts\/\d{1,4})$/,WAIT_NOTIFICATIONS={permission_prompt:"permission",idle_prompt:null};function waitingHead(t){if("string"!=typeof t)return null;const n=t.replace(ANSI_SEQUENCE,"").replace(/\s+/gu," ").trim();return n?Array.from(n).slice(0,80).join(""):null}function toWaitingUpdate(t,n,e){if(!t||"object"!=typeof t||Array.isArray(t))return null;if(!n||"vscode"!==n.TERM_PROGRAM||nonEmptyString(n.MONSTER_ID))return null;const o=nonEmptyString(t.session_id),r=nonEmptyString(t.cwd);if(!o||!SID_SHAPE.test(o)||!r)return null;const i=t.hook_event_name;if("SessionEnd"===i)return{kind:"clear",sid:o};let s,a=null,l=null;if("UserPromptSubmit"===i)s="working";else if("PostToolUse"===i||"PostToolUseFailure"===i)s="working",l=["reply","permission"];else if("Stop"===i)s="reply",a=waitingHead(t.last_assistant_message);else if("PreToolUse"===i&&"AskUserQuestion"===t.tool_name){s="reply";const n=t.tool_input&&Array.isArray(t.tool_input.questions)?t.tool_input.questions:[];a=waitingHead(n[0]&&n[0].question)}else{if("Notification"!==i||!Object.prototype.hasOwnProperty.call(WAIT_NOTIFICATIONS,t.notification_type))return null;if(s=WAIT_NOTIFICATIONS[t.notification_type],null===s)return{kind:"bell",sid:o};a=waitingHead(t.message)}const p={v:1,sid:o,cwd:r,ts:e.toISOString(),state:s},c=nonEmptyString(n.MUSUHI_VSCODE_KEYWORD);c&&ROLE_SHAPE.test(c)&&(p.launcher=c);const u=nonEmptyString(n.BLUELAMP_BELL_TTY);return u&&BELL_TTY_SHAPE.test(u)&&(p.tty=u),a&&(p.head=a),l?{kind:"put",row:p,bell:!1,onlyFrom:l}:{kind:"put",row:p,bell:"working"!==s,..."permission"===s?{soft:!0}:{}}}function waitingFile(t,n){return path.join(t,".bluelamp","waiting",`${n}.json`)}function readWaitingState(t){try{const n=JSON.parse(fs.readFileSync(t,"utf8"));return"string"==typeof n.state?n.state:null}catch{return null}}function applyWaitingUpdate(t,n){const e=waitingFile(t,"put"===n.kind?n.row.sid:n.sid);if("clear"===n.kind)fs.rmSync(e,{force:!0});else if("put"===n.kind){if(n.soft&&"reply"===readWaitingState(e))return;if(n.onlyFrom&&!n.onlyFrom.includes(readWaitingState(e)))return;fs.mkdirSync(path.dirname(e),{recursive:!0,mode:448});const t=`${e}.${process.pid}.tmp`;fs.writeFileSync(t,`${JSON.stringify(n.row)}\n`,{encoding:"utf8",mode:384}),fs.renameSync(t,e)}}function ringBell(t){const n=t&&t.BLUELAMP_BELL_TTY;if("string"!=typeof n||!BELL_TTY_SHAPE.test(n))return!1;const e=fs.openSync(n,fs.constants.O_WRONLY|fs.constants.O_NOCTTY|fs.constants.O_NONBLOCK);try{fs.writeSync(e,"")}finally{fs.closeSync(e)}return!0}function quoteForShell(t){return`"${t.replace(/(["\\$`])/g,"\\$1")}"`}function buildSettingsJson(t,n){const e=`${quoteForShell(t)} ${quoteForShell(n)}`,o={};for(const t of HOOK_EVENTS){const n=HOOK_MATCHERS[t];o[t]=[{...n?{matcher:n}:{},hooks:[{type:"command",command:e}]}]}return JSON.stringify({hooks:o})}function appendFootprint(t,n,e={}){const o=footprintFile(t,dirCwdOf(n,e),n.sid);return fs.mkdirSync(path.dirname(o),{recursive:!0,mode:448}),fs.appendFileSync(o,`${JSON.stringify(n)}\n`,{encoding:"utf8",mode:384}),o}function main(){"--settings-json"===process.argv[2]&&(process.stdout.write(buildSettingsJson(process.execPath,__filename)),process.exit(0));try{const t=fs.readFileSync(0,"utf8"),n=JSON.parse(t);try{const t=toFootprint(n,process.env,new Date);t&&appendFootprint(os.homedir(),t,process.env)}catch{}try{const t=toWaitingUpdate(n,process.env,new Date);t&&(applyWaitingUpdate(os.homedir(),t),("bell"===t.kind||"put"===t.kind&&t.bell)&&ringBell(process.env))}catch{}const e=claudeConfigDirOf(os.homedir(),process.env),o=toolResultsDirToClean(n,e);o&&cleanToolResults(e,o)}catch{}process.exit(0)}module.exports={HOOK_EVENTS:HOOK_EVENTS,buildSettingsJson:buildSettingsJson,toFootprint:toFootprint,toWaitingUpdate:toWaitingUpdate,waitingHead:waitingHead,waitingFile:waitingFile,applyWaitingUpdate:applyWaitingUpdate,ringBell:ringBell,WAITING_HEAD_MAX:80,claudeConfigDirOf:claudeConfigDirOf,toolResultsDirToClean:toolResultsDirToClean,cleanToolResults:cleanToolResults,normalizePrompt:normalizePrompt,footprintDirName:footprintDirName,footprintFile:footprintFile,dirCwdOf:dirCwdOf,appendFootprint:appendFootprint},require.main===module&&main();
@@ -28,4 +28,11 @@ export declare function footprintEnv(hookPath: string, loadedRole: string | null
28
28
  * claude 次第で、足跡が黙って消えうる(足跡の無い席は見張りから見えない)。
29
29
  */
30
30
  export declare function userSettingsFlagError(argv: readonly string[]): string | null;
31
+ /** 箱 n=2708: 子の env に入れる鍵=起動子が走っている端末の path(書き手が返事待ちの鈴をここへ書く)。 */
32
+ export declare const BELL_TTY_ENV = "BLUELAMP_BELL_TTY";
33
+ /**
34
+ * `tty` の出力 → 子の env に足す鍵(純関数)。VS Code の端末でない・端末でない(`not a tty`)・形が違う → 空の値。
35
+ * 🔬 Claude Code 2.1.294 の hook は制御端末を持たない(/dev/tty が ENXIO)=端末の path は起動子が測って渡すしかない。
36
+ */
37
+ export declare function bellTtyEnv(termProgram: string | undefined, ttyOutput: string | null): Record<string, string>;
31
38
  //# sourceMappingURL=footprintSettings.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"footprintSettings.d.ts","sourceRoot":"","sources":["../../src/lib/footprintSettings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB,4BAA4B,CAAC;AAC5D,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,4BAA4B,CAAC;AAE5D;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAE3G;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,+BAA+B,CAAC;AAElE,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIhH;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAI5E"}
1
+ {"version":3,"file":"footprintSettings.d.ts","sourceRoot":"","sources":["../../src/lib/footprintSettings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB,4BAA4B,CAAC;AAC5D,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,4BAA4B,CAAC;AAE5D;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAE3G;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,+BAA+B,CAAC;AAElE,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIhH;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAI5E;AAED,kEAAkE;AAClE,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAIhD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAI5G"}
@@ -1 +1 @@
1
- "use strict";function footprintSettingsFileName(t,e){return`footprint-settings-${e(t).slice(0,16)}.json`}function footprintEnv(t,e,s){const o={[exports.FOOTPRINT_HOOK_ENV]:t,[exports.FOOTPRINT_DIR_CWD_ENV]:s};return e&&(o[exports.FOOTPRINT_ROLE_ENV]=e),o}function userSettingsFlagError(t){return void 0===t.find(t=>"--settings"===t||"string"==typeof t&&t.startsWith("--settings="))?null:"--settings は渡せません(起動子が足跡の hook を --settings で入れるため)。設定は ~/.claude/settings.json かプロジェクトの .claude/settings.json に書いてください"}Object.defineProperty(exports,"__esModule",{value:!0}),exports.FOOTPRINT_DIR_CWD_ENV=exports.FOOTPRINT_ROLE_ENV=exports.FOOTPRINT_HOOK_ENV=void 0,exports.footprintSettingsFileName=footprintSettingsFileName,exports.footprintEnv=footprintEnv,exports.userSettingsFlagError=userSettingsFlagError,exports.FOOTPRINT_HOOK_ENV="BLUELAMP_FOOTPRINT_HOOK",exports.FOOTPRINT_ROLE_ENV="BLUELAMP_FOOTPRINT_ROLE",exports.FOOTPRINT_DIR_CWD_ENV="BLUELAMP_FOOTPRINT_DIR_CWD";
1
+ "use strict";function footprintSettingsFileName(t,e){return`footprint-settings-${e(t).slice(0,16)}.json`}function footprintEnv(t,e,s){const o={[exports.FOOTPRINT_HOOK_ENV]:t,[exports.FOOTPRINT_DIR_CWD_ENV]:s};return e&&(o[exports.FOOTPRINT_ROLE_ENV]=e),o}function userSettingsFlagError(t){return void 0===t.find(t=>"--settings"===t||"string"==typeof t&&t.startsWith("--settings="))?null:"--settings は渡せません(起動子が足跡の hook を --settings で入れるため)。設定は ~/.claude/settings.json かプロジェクトの .claude/settings.json に書いてください"}Object.defineProperty(exports,"__esModule",{value:!0}),exports.BELL_TTY_ENV=exports.FOOTPRINT_DIR_CWD_ENV=exports.FOOTPRINT_ROLE_ENV=exports.FOOTPRINT_HOOK_ENV=void 0,exports.footprintSettingsFileName=footprintSettingsFileName,exports.footprintEnv=footprintEnv,exports.userSettingsFlagError=userSettingsFlagError,exports.bellTtyEnv=bellTtyEnv,exports.FOOTPRINT_HOOK_ENV="BLUELAMP_FOOTPRINT_HOOK",exports.FOOTPRINT_ROLE_ENV="BLUELAMP_FOOTPRINT_ROLE",exports.FOOTPRINT_DIR_CWD_ENV="BLUELAMP_FOOTPRINT_DIR_CWD",exports.BELL_TTY_ENV="BLUELAMP_BELL_TTY";const BELL_TTY_SHAPE=/^\/dev\/(?:ttys\d{1,4}|pts\/\d{1,4})$/;function bellTtyEnv(t,e){const s="vscode"===t&&"string"==typeof e?e.trim():"";return{[exports.BELL_TTY_ENV]:BELL_TTY_SHAPE.test(s)?s:""}}
@@ -1 +1 @@
1
- {"version":3,"file":"footprintSettings.js","sourceRoot":"","sources":["../../src/lib/footprintSettings.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAYH,8DAEC;AAQD,oCAIC;AAOD,sDAIC;AAnCD,6EAA6E;AAChE,QAAA,kBAAkB,GAAG,yBAAyB,CAAC;AAC5D,gFAAgF;AACnE,QAAA,kBAAkB,GAAG,yBAAyB,CAAC;AAE5D;;;;GAIG;AACH,SAAgB,yBAAyB,CAAC,YAAoB,EAAE,SAAmC;IACjG,OAAO,sBAAsB,SAAS,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC;AAC3E,CAAC;AAED;;GAEG;AACU,QAAA,qBAAqB,GAAG,4BAA4B,CAAC;AAElE,6DAA6D;AAC7D,SAAgB,YAAY,CAAC,QAAgB,EAAE,UAAyB,EAAE,MAAc;IACtF,MAAM,GAAG,GAA2B,EAAE,CAAC,0BAAkB,CAAC,EAAE,QAAQ,EAAE,CAAC,6BAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACxG,IAAI,UAAU;QAAE,GAAG,CAAC,0BAAkB,CAAC,GAAG,UAAU,CAAC;IACrD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,SAAgB,qBAAqB,CAAC,IAAuB;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,YAAY,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3G,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,yHAAyH,CAAC;AACnI,CAAC"}
1
+ {"version":3,"file":"footprintSettings.js","sourceRoot":"","sources":["../../src/lib/footprintSettings.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAYH,8DAEC;AAQD,oCAIC;AAOD,sDAIC;AAWD,gCAIC;AAlDD,6EAA6E;AAChE,QAAA,kBAAkB,GAAG,yBAAyB,CAAC;AAC5D,gFAAgF;AACnE,QAAA,kBAAkB,GAAG,yBAAyB,CAAC;AAE5D;;;;GAIG;AACH,SAAgB,yBAAyB,CAAC,YAAoB,EAAE,SAAmC;IACjG,OAAO,sBAAsB,SAAS,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC;AAC3E,CAAC;AAED;;GAEG;AACU,QAAA,qBAAqB,GAAG,4BAA4B,CAAC;AAElE,6DAA6D;AAC7D,SAAgB,YAAY,CAAC,QAAgB,EAAE,UAAyB,EAAE,MAAc;IACtF,MAAM,GAAG,GAA2B,EAAE,CAAC,0BAAkB,CAAC,EAAE,QAAQ,EAAE,CAAC,6BAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACxG,IAAI,UAAU;QAAE,GAAG,CAAC,0BAAkB,CAAC,GAAG,UAAU,CAAC;IACrD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,SAAgB,qBAAqB,CAAC,IAAuB;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,YAAY,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3G,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,yHAAyH,CAAC;AACnI,CAAC;AAED,kEAAkE;AACrD,QAAA,YAAY,GAAG,mBAAmB,CAAC;AAChD,4CAA4C;AAC5C,MAAM,cAAc,GAAG,uCAAuC,CAAC;AAE/D;;;GAGG;AACH,SAAgB,UAAU,CAAC,WAA+B,EAAE,SAAwB;IAClF,MAAM,GAAG,GAAG,WAAW,KAAK,QAAQ,IAAI,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9F,kDAAkD;IAClD,OAAO,EAAE,CAAC,oBAAY,CAAC,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACjE,CAAC"}
@@ -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"),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}`),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] keyword: ${e}`);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||[];if(0===t.length)return{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]};let r=`📦 MCPストア一覧 (${t.length}件)\n\n`;return t.forEach(e=>{const t=e.isOfficial?" [公式]":"";r+=`**${e.name}**${t}\n`,r+=` ID: ${e.mcpId}\n`,r+=` ${e.description}\n`,r+=` ツール数: ${e.toolCount}\n\n`}),r+='\nインストールするには: install_from_store(mcp_id: "MCP_ID")',{content:[{type:"text",text:r}]}}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."),[];let t=[];try{const r=await getGatewayConfig(e);if(!r.success)return log(`[catalog] Server config failed: ${r.error||"Unknown"}. Starting with no services.`),[];t=r.data?.services||[],log(`[catalog] Server config: ${t.length} services`),this.applyCatalog(t),writeCatalogCache(CATALOG_CACHE_FILE,t,catalogCacheFingerprint(e),log)}catch(e){return log(`[catalog] Server config error: ${e.message}. Starting with no services.`),[]}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}))}toolListSignature(){return stableJson(this.buildToolList().map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema})))}async refreshCatalogInBackground(e){const t=this.toolListSignature();try{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"),{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{notified:!0,reason:"diff"}}async run(){const e=resolveToken(),t=catalogCacheFingerprint(e),r=readCatalogCache(CATALOG_CACHE_FILE,t,log);r?(this.applyCatalog(r,{fromCache:!0}),log(`[catalogCache] ${this._catalogTools.size} tools from cache (HTTP は待たない)`)):(log("[catalogCache] no usable cache, waiting for server config"),await this.loadCatalog(e));const o=new StdioServerTransport;await this.server.connect(o),log(`[Gateway] BlueLamp Gateway v2.0.0 started. (${this._catalogTools.size} catalog tools available at connect)`),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 i=resolveToken();if(KEYWORD&&i){log(`[Gateway] Auto-loading knowledge for keyword: ${KEYWORD}`);const e=await getPromptFromPortal(KEYWORD,i,LANGUAGE);e&&e.success?log(`[Gateway] Auto-load successful: ${e.title}`):log(`[Gateway] Auto-load failed for keyword: ${KEYWORD}`)}}}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"),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"),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}`),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] keyword: ${e}`);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||[];if(0===t.length)return{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]};let r=`📦 MCPストア一覧 (${t.length}件)\n\n`;return t.forEach(e=>{const t=e.isOfficial?" [公式]":"";r+=`**${e.name}**${t}\n`,r+=` ID: ${e.mcpId}\n`,r+=` ${e.description}\n`,r+=` ツール数: ${e.toolCount}\n\n`}),r+='\nインストールするには: install_from_store(mcp_id: "MCP_ID")',{content:[{type:"text",text:r}]}}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 for keyword: ${KEYWORD}`);const e=await getPromptFromPortal(KEYWORD,s,LANGUAGE);e&&e.success?log(`[Gateway] Auto-load successful: ${e.title}`):log(`[Gateway] Auto-load failed for keyword: ${KEYWORD}`)}}}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)})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluelamp",
3
- "version": "6.6.24",
3
+ "version": "6.6.25",
4
4
  "description": "BlueLamp CLI: 旧 bluelampportal/mcp の auth / 200+ bluelamp1-N binary / postinstall scripts を TypeScript 化して monorepo に統合したパッケージ。",
5
5
  "keywords": [
6
6
  "bluelamp",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@bluelamp/cli-token": "1.0.3",
48
- "@modelcontextprotocol/sdk": "^1.0.0",
48
+ "@modelcontextprotocol/sdk": "^1.31.0",
49
49
  "cross-spawn": "^7.0.6",
50
50
  "dotenv": "^16.6.1",
51
51
  "node-fetch": "^3.3.2",