@phidiassj/aiyoperps-mcp-installer 0.5.8 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/bin/aiyoperps-mcp-installer.js +129 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,8 @@ npx -y @phidiassj/aiyoperps-mcp-installer --url http://127.0.0.1:5078/mcp
|
|
|
25
25
|
|
|
26
26
|
- The installer creates `*.bak` backups before editing local config files.
|
|
27
27
|
- If a host is detected but not considered safe to modify, the installer reports it and skips automatic changes.
|
|
28
|
-
- For Codex, the installer writes `startup_timeout_sec = 60
|
|
29
|
-
- Before writing host config, the installer
|
|
30
|
-
- The generated MCP entry
|
|
28
|
+
- For Codex, the installer writes `startup_timeout_sec = 60`.
|
|
29
|
+
- Before writing host config, the installer installs the bridge package into a stable local runtime directory instead of relying on `npx` at every startup.
|
|
30
|
+
- The generated MCP entry uses `node <local bridge script> --debug-log <local log path> --quiet --url <detected endpoint>`.
|
|
31
|
+
- The generated bridge debug log is stored in the local bridge runtime directory (for example `~/.aiyoperps/mcp-bridge/codex-debug.log` on Linux/WSL).
|
|
31
32
|
- Before installation, the installer probes candidate MCP URLs and only writes config if it finds a reachable endpoint.
|
|
@@ -16,6 +16,7 @@ const BRIDGE_SCRIPT_RELATIVE_PATH = path.join(
|
|
|
16
16
|
'aiyoperps-mcp-bridge',
|
|
17
17
|
'bin',
|
|
18
18
|
'aiyoperps-mcp-bridge.js');
|
|
19
|
+
const BRIDGE_DEBUG_LOG_NAME = 'codex-debug.log';
|
|
19
20
|
const EXECUTABLE_CACHE = new Map();
|
|
20
21
|
let bridgeRuntimePrepared = false;
|
|
21
22
|
let bridgeRuntimeAttempted = false;
|
|
@@ -146,13 +147,23 @@ async function detectClaudeCode(url) {
|
|
|
146
147
|
|
|
147
148
|
async function detectClaudeDesktop(url) {
|
|
148
149
|
const candidates = getClaudeDesktopCandidates();
|
|
149
|
-
const configPath = candidates.find(fileExists) ??
|
|
150
|
+
const configPath = candidates.find(fileExists) ??
|
|
151
|
+
candidates.find(candidate => fs.existsSync(path.dirname(candidate))) ??
|
|
152
|
+
candidates[0];
|
|
150
153
|
const exists = fileExists(configPath);
|
|
151
154
|
const parentExists = fs.existsSync(path.dirname(configPath));
|
|
152
|
-
const
|
|
153
|
-
|
|
155
|
+
const baseConfigRoot = path.dirname(path.dirname(configPath));
|
|
156
|
+
const baseRootExists = fs.existsSync(baseConfigRoot);
|
|
157
|
+
const detected = exists || parentExists || baseRootExists;
|
|
158
|
+
let supported = baseRootExists;
|
|
154
159
|
let installed = false;
|
|
155
|
-
let reason =
|
|
160
|
+
let reason = exists
|
|
161
|
+
? 'ready for JSON config merge'
|
|
162
|
+
: parentExists
|
|
163
|
+
? 'ready for JSON config merge'
|
|
164
|
+
: baseRootExists
|
|
165
|
+
? 'default config path can be created'
|
|
166
|
+
: 'config root not found';
|
|
156
167
|
|
|
157
168
|
if (exists) {
|
|
158
169
|
try {
|
|
@@ -570,9 +581,14 @@ async function resolveInstallUrl(defaultUrl, urlExplicit) {
|
|
|
570
581
|
|
|
571
582
|
stdout.write('\nProbing MCP endpoint candidates...\n');
|
|
572
583
|
for (const candidate of candidates) {
|
|
573
|
-
const
|
|
574
|
-
stdout.write(` ${ok ? 'ok' : 'fail'} ${candidate}
|
|
575
|
-
if (ok) {
|
|
584
|
+
const result = await probeMcpEndpoint(candidate);
|
|
585
|
+
stdout.write(` ${result.ok ? 'ok' : 'fail'} ${candidate}`);
|
|
586
|
+
if (!result.ok) {
|
|
587
|
+
const detail = result.stderr || result.stdout || result.error?.message || `exit=${result.status ?? 'unknown'}`;
|
|
588
|
+
stdout.write(` (${truncateText(detail, 180)})`);
|
|
589
|
+
}
|
|
590
|
+
stdout.write('\n');
|
|
591
|
+
if (result.ok) {
|
|
576
592
|
return candidate;
|
|
577
593
|
}
|
|
578
594
|
}
|
|
@@ -617,8 +633,97 @@ async function canReachMcpEndpoint(url) {
|
|
|
617
633
|
const commandLine = resolveCommandLineSpec(process.platform, url, {
|
|
618
634
|
healthCheck: true
|
|
619
635
|
});
|
|
620
|
-
|
|
621
|
-
|
|
636
|
+
return runCommand(commandLine.command, commandLine.args);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async function probeMcpEndpoint(url) {
|
|
640
|
+
const httpResult = await probeMcpEndpointHttp(url);
|
|
641
|
+
if (!httpResult.ok) {
|
|
642
|
+
return httpResult;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (!bridgeRuntimePrepared && bridgeRuntimeAttempted) {
|
|
646
|
+
return httpResult;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const bridgeResult = await canReachMcpEndpoint(url);
|
|
650
|
+
if (!bridgeResult.ok) {
|
|
651
|
+
stdout.write(` note bridge health check failed but HTTP MCP is reachable; continuing with ${url}\n`);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return httpResult;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async function probeMcpEndpointHttp(url) {
|
|
658
|
+
const controller = new AbortController();
|
|
659
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
660
|
+
|
|
661
|
+
try {
|
|
662
|
+
const response = await fetch(url, {
|
|
663
|
+
method: 'POST',
|
|
664
|
+
headers: {
|
|
665
|
+
'Content-Type': 'application/json'
|
|
666
|
+
},
|
|
667
|
+
body: JSON.stringify({
|
|
668
|
+
jsonrpc: '2.0',
|
|
669
|
+
id: 'installer-probe',
|
|
670
|
+
method: 'ping',
|
|
671
|
+
params: {}
|
|
672
|
+
}),
|
|
673
|
+
signal: controller.signal
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
const text = await response.text();
|
|
677
|
+
if (!response.ok) {
|
|
678
|
+
return {
|
|
679
|
+
ok: false,
|
|
680
|
+
status: response.status,
|
|
681
|
+
stdout: '',
|
|
682
|
+
stderr: `HTTP ${response.status}: ${text || response.statusText}`
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
let payload;
|
|
687
|
+
try {
|
|
688
|
+
payload = JSON.parse(text);
|
|
689
|
+
} catch (error) {
|
|
690
|
+
return {
|
|
691
|
+
ok: false,
|
|
692
|
+
status: response.status,
|
|
693
|
+
stdout: '',
|
|
694
|
+
stderr: `Invalid JSON: ${error.message}`
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (payload?.error) {
|
|
699
|
+
return {
|
|
700
|
+
ok: false,
|
|
701
|
+
status: response.status,
|
|
702
|
+
stdout: '',
|
|
703
|
+
stderr: payload.error.message || 'JSON-RPC error'
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return {
|
|
708
|
+
ok: true,
|
|
709
|
+
status: response.status,
|
|
710
|
+
stdout: '',
|
|
711
|
+
stderr: ''
|
|
712
|
+
};
|
|
713
|
+
} catch (error) {
|
|
714
|
+
const message = error?.name === 'AbortError'
|
|
715
|
+
? 'Request timeout after 5000ms'
|
|
716
|
+
: error?.message || 'Unknown fetch error';
|
|
717
|
+
return {
|
|
718
|
+
ok: false,
|
|
719
|
+
status: null,
|
|
720
|
+
stdout: '',
|
|
721
|
+
stderr: message,
|
|
722
|
+
error
|
|
723
|
+
};
|
|
724
|
+
} finally {
|
|
725
|
+
clearTimeout(timeout);
|
|
726
|
+
}
|
|
622
727
|
}
|
|
623
728
|
|
|
624
729
|
function hasCodexBlock(content) {
|
|
@@ -775,7 +880,7 @@ function resolveCommandLineSpec(platform, url, options = {}) {
|
|
|
775
880
|
} else if (options.healthCheck) {
|
|
776
881
|
bridgeArgs.push('--health-check', '--quiet', '--url', url);
|
|
777
882
|
} else {
|
|
778
|
-
bridgeArgs.push('--quiet', '--url', url);
|
|
883
|
+
bridgeArgs.push('--debug-log', resolveBridgeDebugLogPath(), '--quiet', '--url', url);
|
|
779
884
|
}
|
|
780
885
|
|
|
781
886
|
const installedScriptPath = resolveInstalledBridgeScriptPath();
|
|
@@ -801,6 +906,16 @@ function resolveCommandLineSpec(platform, url, options = {}) {
|
|
|
801
906
|
};
|
|
802
907
|
}
|
|
803
908
|
|
|
909
|
+
function truncateText(value, maxLength) {
|
|
910
|
+
if (!value) {
|
|
911
|
+
return '';
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
return value.length <= maxLength
|
|
915
|
+
? value
|
|
916
|
+
: `${value.slice(0, maxLength)}...`;
|
|
917
|
+
}
|
|
918
|
+
|
|
804
919
|
async function ensureBridgeRuntimePrepared() {
|
|
805
920
|
if (bridgeRuntimeAttempted) {
|
|
806
921
|
return bridgeRuntimePrepared;
|
|
@@ -864,6 +979,10 @@ function resolveInstalledBridgeScriptPath() {
|
|
|
864
979
|
return fileExists(scriptPath) ? scriptPath : null;
|
|
865
980
|
}
|
|
866
981
|
|
|
982
|
+
function resolveBridgeDebugLogPath() {
|
|
983
|
+
return path.join(resolveBridgeInstallRoot(), BRIDGE_DEBUG_LOG_NAME);
|
|
984
|
+
}
|
|
985
|
+
|
|
867
986
|
function resolveCodexConfigPath() {
|
|
868
987
|
if (env.CODEX_CONFIG_PATH) {
|
|
869
988
|
return env.CODEX_CONFIG_PATH;
|