@phidiassj/aiyoperps-mcp-installer 0.5.4 → 0.5.5
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 +1 -0
- package/bin/aiyoperps-mcp-installer.js +133 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,3 +28,4 @@ npx -y @phidiassj/aiyoperps-mcp-installer --url http://127.0.0.1:5078/mcp
|
|
|
28
28
|
- For Codex, the installer writes `startup_timeout_sec = 60` because the first `npx` launch may take longer than the default 10-second startup timeout.
|
|
29
29
|
- Before writing host config, the installer prewarms the bridge package with `npx ... --help` to reduce first-run startup latency.
|
|
30
30
|
- The generated MCP entry includes `--startup-ping` so the bridge verifies the AiyoPerps MCP endpoint before entering stdio mode.
|
|
31
|
+
- Before installation, the installer probes candidate MCP URLs and only writes config if it finds a reachable endpoint.
|
|
@@ -229,7 +229,13 @@ async function installAll(hosts, url) {
|
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
await
|
|
232
|
+
const resolvedUrl = await resolveInstallUrl(url, cli.urlExplicit);
|
|
233
|
+
if (!resolvedUrl) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
bindInstallActions(selected, resolvedUrl);
|
|
238
|
+
await prewarmBridgePackage(resolvedUrl);
|
|
233
239
|
await runForHosts(selected, 'install');
|
|
234
240
|
}
|
|
235
241
|
|
|
@@ -252,7 +258,13 @@ async function installAnyOf(rl, hosts, url) {
|
|
|
252
258
|
return;
|
|
253
259
|
}
|
|
254
260
|
|
|
255
|
-
await
|
|
261
|
+
const resolvedUrl = await resolveInstallUrl(url, cli.urlExplicit);
|
|
262
|
+
if (!resolvedUrl) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
bindInstallActions(selected, resolvedUrl);
|
|
267
|
+
await prewarmBridgePackage(resolvedUrl);
|
|
256
268
|
await runForHosts(selected, 'install');
|
|
257
269
|
}
|
|
258
270
|
|
|
@@ -317,6 +329,13 @@ async function runNonInteractive(cliArgs, hosts, url) {
|
|
|
317
329
|
requireSupported: true,
|
|
318
330
|
requireInstalled: false
|
|
319
331
|
});
|
|
332
|
+
const resolvedUrl = await resolveInstallUrl(url, cliArgs.urlExplicit);
|
|
333
|
+
if (!resolvedUrl) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
bindInstallActions(selected, resolvedUrl);
|
|
338
|
+
await prewarmBridgePackage(resolvedUrl);
|
|
320
339
|
await runForHosts(selected, 'install');
|
|
321
340
|
return;
|
|
322
341
|
}
|
|
@@ -358,6 +377,27 @@ function selectHostsByIds(hosts, ids, requirements) {
|
|
|
358
377
|
});
|
|
359
378
|
}
|
|
360
379
|
|
|
380
|
+
function bindInstallActions(hosts, url) {
|
|
381
|
+
for (const host of hosts) {
|
|
382
|
+
host.install = createInstallAction(host, url);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function createInstallAction(host, url) {
|
|
387
|
+
switch (host.id) {
|
|
388
|
+
case 'codex':
|
|
389
|
+
return () => installCodex(host.path, url);
|
|
390
|
+
case 'claude-code':
|
|
391
|
+
return () => installClaudeCode(url);
|
|
392
|
+
case 'claude-desktop':
|
|
393
|
+
return () => installClaudeDesktop(host.path, url);
|
|
394
|
+
case 'openclaw':
|
|
395
|
+
return () => installOpenClaw(url);
|
|
396
|
+
default:
|
|
397
|
+
return async () => { };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
361
401
|
function installCodex(configPath, url) {
|
|
362
402
|
ensureParentDir(configPath);
|
|
363
403
|
|
|
@@ -514,6 +554,88 @@ async function prewarmBridgePackage(url) {
|
|
|
514
554
|
stdout.write(' prewarm complete\n');
|
|
515
555
|
}
|
|
516
556
|
|
|
557
|
+
async function resolveInstallUrl(defaultUrl, urlExplicit) {
|
|
558
|
+
const candidates = urlExplicit
|
|
559
|
+
? [defaultUrl]
|
|
560
|
+
: buildCandidateUrls(defaultUrl);
|
|
561
|
+
|
|
562
|
+
stdout.write('\nProbing MCP endpoint candidates...\n');
|
|
563
|
+
for (const candidate of candidates) {
|
|
564
|
+
const ok = await canReachMcpEndpoint(candidate);
|
|
565
|
+
stdout.write(` ${ok ? 'ok' : 'fail'} ${candidate}\n`);
|
|
566
|
+
if (ok) {
|
|
567
|
+
return candidate;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
stdout.write(
|
|
572
|
+
'No reachable AiyoPerps MCP endpoint was detected. ' +
|
|
573
|
+
'Start the AiyoPerps HTTP API first or pass a valid --url, then retry.\n');
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function buildCandidateUrls(defaultUrl) {
|
|
578
|
+
const urls = [
|
|
579
|
+
defaultUrl,
|
|
580
|
+
'http://localhost:5078/mcp',
|
|
581
|
+
'http://winhost:5078/mcp',
|
|
582
|
+
'http://host.docker.internal:5078/mcp'
|
|
583
|
+
];
|
|
584
|
+
|
|
585
|
+
const wslGatewayUrl = detectWslGatewayUrl();
|
|
586
|
+
if (wslGatewayUrl) {
|
|
587
|
+
urls.push(wslGatewayUrl);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return [...new Set(urls)];
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function detectWslGatewayUrl() {
|
|
594
|
+
if (!isWsl()) {
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
const content = fs.readFileSync('/etc/resolv.conf', 'utf8');
|
|
600
|
+
const match = content.match(/^nameserver\s+([0-9.]+)\s*$/m);
|
|
601
|
+
return match ? `http://${match[1]}:5078/mcp` : null;
|
|
602
|
+
} catch {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async function canReachMcpEndpoint(url) {
|
|
608
|
+
const controller = new AbortController();
|
|
609
|
+
const timeout = setTimeout(() => controller.abort(), 2500);
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
const response = await fetch(url, {
|
|
613
|
+
method: 'POST',
|
|
614
|
+
headers: {
|
|
615
|
+
'Content-Type': 'application/json'
|
|
616
|
+
},
|
|
617
|
+
body: JSON.stringify({
|
|
618
|
+
jsonrpc: '2.0',
|
|
619
|
+
id: 'installer-probe',
|
|
620
|
+
method: 'ping',
|
|
621
|
+
params: {}
|
|
622
|
+
}),
|
|
623
|
+
signal: controller.signal
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
if (!response.ok) {
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const payload = await response.json();
|
|
631
|
+
return !payload?.error;
|
|
632
|
+
} catch {
|
|
633
|
+
return false;
|
|
634
|
+
} finally {
|
|
635
|
+
clearTimeout(timeout);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
517
639
|
function hasCodexBlock(content) {
|
|
518
640
|
return new RegExp(`^\\[mcp_servers\\.${SERVER_NAME}\\]\\s*$`, 'm').test(content);
|
|
519
641
|
}
|
|
@@ -570,6 +692,10 @@ function resolveTargetUrl(args) {
|
|
|
570
692
|
return env.AIYOPERPS_MCP_URL || DEFAULT_URL;
|
|
571
693
|
}
|
|
572
694
|
|
|
695
|
+
function isWsl() {
|
|
696
|
+
return Boolean(env.WSL_DISTRO_NAME || env.WSL_INTEROP);
|
|
697
|
+
}
|
|
698
|
+
|
|
573
699
|
function parseCliArgs(args) {
|
|
574
700
|
const install = [];
|
|
575
701
|
const uninstall = [];
|
|
@@ -577,6 +703,7 @@ function parseCliArgs(args) {
|
|
|
577
703
|
let statusOnly = false;
|
|
578
704
|
let nonInteractive = false;
|
|
579
705
|
let url = env.AIYOPERPS_MCP_URL || DEFAULT_URL;
|
|
706
|
+
let urlExplicit = Boolean(env.AIYOPERPS_MCP_URL);
|
|
580
707
|
|
|
581
708
|
for (let index = 0; index < args.length; index += 1) {
|
|
582
709
|
const arg = args[index];
|
|
@@ -626,11 +753,13 @@ function parseCliArgs(args) {
|
|
|
626
753
|
|
|
627
754
|
if (arg.startsWith('--url=')) {
|
|
628
755
|
url = arg.slice('--url='.length);
|
|
756
|
+
urlExplicit = true;
|
|
629
757
|
continue;
|
|
630
758
|
}
|
|
631
759
|
|
|
632
760
|
if (arg === '--url' && index + 1 < args.length) {
|
|
633
761
|
url = args[index + 1];
|
|
762
|
+
urlExplicit = true;
|
|
634
763
|
index += 1;
|
|
635
764
|
continue;
|
|
636
765
|
}
|
|
@@ -642,7 +771,8 @@ function parseCliArgs(args) {
|
|
|
642
771
|
installAll,
|
|
643
772
|
statusOnly,
|
|
644
773
|
nonInteractive,
|
|
645
|
-
url
|
|
774
|
+
url,
|
|
775
|
+
urlExplicit
|
|
646
776
|
};
|
|
647
777
|
}
|
|
648
778
|
|