@tronsfey/ucli 0.5.1 → 0.5.3
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 +137 -66
- package/README.zh.md +115 -69
- package/dist/{client-LCWUZRZX.js → client-Y6NONDCI.js} +25 -11
- package/dist/{client-LCWUZRZX.js.map → client-Y6NONDCI.js.map} +1 -1
- package/dist/index.js +636 -446
- package/dist/index.js.map +1 -1
- package/dist/{runner-HH357SRR.js → runner-ROLDMGH3.js} +250 -60
- package/dist/runner-ROLDMGH3.js.map +1 -0
- package/package.json +4 -4
- package/skill.md +134 -48
- package/dist/runner-HH357SRR.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -279,6 +279,155 @@ async function clearOASCache(name) {
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
// src/lib/yaml.ts
|
|
283
|
+
function toYaml(value, indent = 0) {
|
|
284
|
+
if (value === null || value === void 0) {
|
|
285
|
+
return "null";
|
|
286
|
+
}
|
|
287
|
+
if (typeof value === "boolean") {
|
|
288
|
+
return value ? "true" : "false";
|
|
289
|
+
}
|
|
290
|
+
if (typeof value === "number") {
|
|
291
|
+
return String(value);
|
|
292
|
+
}
|
|
293
|
+
if (typeof value === "string") {
|
|
294
|
+
if (needsQuoting(value)) {
|
|
295
|
+
return JSON.stringify(value);
|
|
296
|
+
}
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
if (Array.isArray(value)) {
|
|
300
|
+
if (value.length === 0) return "[]";
|
|
301
|
+
const prefix = " ".repeat(indent);
|
|
302
|
+
return value.map((item) => {
|
|
303
|
+
const serialised = toYaml(item, indent + 2);
|
|
304
|
+
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
305
|
+
const firstNewline = serialised.indexOf("\n");
|
|
306
|
+
if (firstNewline === -1) {
|
|
307
|
+
return `${prefix}- ${serialised.trimStart()}`;
|
|
308
|
+
}
|
|
309
|
+
const firstLine = serialised.slice(0, firstNewline);
|
|
310
|
+
const rest = serialised.slice(firstNewline + 1);
|
|
311
|
+
return `${prefix}- ${firstLine.trimStart()}
|
|
312
|
+
${rest}`;
|
|
313
|
+
}
|
|
314
|
+
return `${prefix}- ${serialised}`;
|
|
315
|
+
}).join("\n");
|
|
316
|
+
}
|
|
317
|
+
if (typeof value === "object") {
|
|
318
|
+
const entries = Object.entries(value);
|
|
319
|
+
if (entries.length === 0) return "{}";
|
|
320
|
+
const prefix = " ".repeat(indent);
|
|
321
|
+
return entries.map(([key, val]) => {
|
|
322
|
+
if (val === null || val === void 0) {
|
|
323
|
+
return `${prefix}${key}: null`;
|
|
324
|
+
}
|
|
325
|
+
if (typeof val === "object") {
|
|
326
|
+
const nested = toYaml(val, indent + 2);
|
|
327
|
+
return `${prefix}${key}:
|
|
328
|
+
${nested}`;
|
|
329
|
+
}
|
|
330
|
+
return `${prefix}${key}: ${toYaml(val, indent)}`;
|
|
331
|
+
}).join("\n");
|
|
332
|
+
}
|
|
333
|
+
return String(value);
|
|
334
|
+
}
|
|
335
|
+
function needsQuoting(s) {
|
|
336
|
+
if (s === "") return true;
|
|
337
|
+
if (s === "true" || s === "false" || s === "null") return true;
|
|
338
|
+
if (/^\d/.test(s)) return true;
|
|
339
|
+
if (/[:{}\[\],&*#?|<>=!%@`'"\\\n\r\t]/.test(s)) return true;
|
|
340
|
+
if (s.startsWith(" ") || s.endsWith(" ")) return true;
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/commands/listoas.ts
|
|
345
|
+
function registerListOas(program2) {
|
|
346
|
+
program2.command("listoas").description("List all OAS services available in the current group").option("--refresh", "Bypass local cache and fetch fresh from server").option("--format <fmt>", "Output format: table | json | yaml", "table").action(async (opts) => {
|
|
347
|
+
const cfg = getConfig();
|
|
348
|
+
const client = new ServerClient(cfg);
|
|
349
|
+
const useCache = !opts.refresh;
|
|
350
|
+
let entries = useCache ? await readOASListCache() : null;
|
|
351
|
+
if (!entries) {
|
|
352
|
+
entries = await client.listOAS();
|
|
353
|
+
if (entries.length > 0) {
|
|
354
|
+
const maxTtl = Math.min(...entries.map((e) => e.cacheTtl));
|
|
355
|
+
await writeOASListCache(entries, maxTtl);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
359
|
+
...rest,
|
|
360
|
+
authConfig: { type: authConfig["type"] ?? rest.authType }
|
|
361
|
+
}));
|
|
362
|
+
if (isJsonOutput()) {
|
|
363
|
+
outputSuccess(safe);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const format = (opts.format ?? "table").toLowerCase();
|
|
367
|
+
if (entries.length === 0) {
|
|
368
|
+
console.log("No services registered in this group.");
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (format === "json") {
|
|
372
|
+
console.log(JSON.stringify(safe, null, 2));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (format === "yaml") {
|
|
376
|
+
console.log(toYaml(safe));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
|
|
380
|
+
console.log(`
|
|
381
|
+
${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
|
|
382
|
+
console.log(`${"-".repeat(nameWidth)} -------- ${"-".repeat(40)}`);
|
|
383
|
+
for (const e of entries) {
|
|
384
|
+
const auth = e.authType.padEnd(8);
|
|
385
|
+
const desc = e.description.length > 60 ? e.description.slice(0, 57) + "..." : e.description;
|
|
386
|
+
console.log(`${e.name.padEnd(nameWidth)} ${auth} ${desc}`);
|
|
387
|
+
}
|
|
388
|
+
console.log();
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/commands/listmcp.ts
|
|
393
|
+
function registerListMcp(program2) {
|
|
394
|
+
program2.command("listmcp").description("List all MCP servers available in the current group").option("--format <fmt>", "Output format: table | json | yaml", "table").action(async (opts) => {
|
|
395
|
+
const cfg = getConfig();
|
|
396
|
+
const client = new ServerClient(cfg);
|
|
397
|
+
const entries = await client.listMCP();
|
|
398
|
+
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
399
|
+
...rest,
|
|
400
|
+
authConfig: { type: authConfig.type }
|
|
401
|
+
}));
|
|
402
|
+
if (isJsonOutput()) {
|
|
403
|
+
outputSuccess(safe);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (entries.length === 0) {
|
|
407
|
+
console.log("No MCP servers registered in this group.");
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const format = (opts.format ?? "table").toLowerCase();
|
|
411
|
+
if (format === "json") {
|
|
412
|
+
console.log(JSON.stringify(safe, null, 2));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (format === "yaml") {
|
|
416
|
+
console.log(toYaml(safe));
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
|
|
420
|
+
console.log(`
|
|
421
|
+
${"SERVER".padEnd(nameWidth)} TRANSPORT DESCRIPTION`);
|
|
422
|
+
console.log(`${"-".repeat(nameWidth)} --------- ${"-".repeat(40)}`);
|
|
423
|
+
for (const e of entries) {
|
|
424
|
+
const desc = e.description.length > 60 ? e.description.slice(0, 57) + "..." : e.description;
|
|
425
|
+
console.log(`${e.name.padEnd(nameWidth)} ${e.transport.padEnd(9)} ${desc}`);
|
|
426
|
+
}
|
|
427
|
+
console.log();
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
282
431
|
// src/lib/oas-runner.ts
|
|
283
432
|
import { spawn } from "child_process";
|
|
284
433
|
import { createRequire } from "module";
|
|
@@ -371,7 +520,7 @@ function buildSafeEnv(authEnv) {
|
|
|
371
520
|
}
|
|
372
521
|
async function runOperation(opts) {
|
|
373
522
|
const bin = resolveOpenapi2CliBin();
|
|
374
|
-
const { entry, operationArgs, format, query } = opts;
|
|
523
|
+
const { entry, operationArgs, format, query, machine, dryRun } = opts;
|
|
375
524
|
const args = [
|
|
376
525
|
"run",
|
|
377
526
|
"--oas",
|
|
@@ -379,6 +528,8 @@ async function runOperation(opts) {
|
|
|
379
528
|
"--cache-ttl",
|
|
380
529
|
String(entry.cacheTtl),
|
|
381
530
|
...entry.baseEndpoint ? ["--endpoint", entry.baseEndpoint] : [],
|
|
531
|
+
...machine ? ["--machine"] : [],
|
|
532
|
+
...dryRun ? ["--dry-run"] : [],
|
|
382
533
|
...format ? ["--format", format] : [],
|
|
383
534
|
...query ? ["--query", query] : [],
|
|
384
535
|
...operationArgs
|
|
@@ -421,120 +572,52 @@ async function getServiceHelp(entry) {
|
|
|
421
572
|
child.on("error", reject);
|
|
422
573
|
});
|
|
423
574
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return `${prefix}- ${serialised.trimStart()}`;
|
|
451
|
-
}
|
|
452
|
-
const firstLine = serialised.slice(0, firstNewline);
|
|
453
|
-
const rest = serialised.slice(firstNewline + 1);
|
|
454
|
-
return `${prefix}- ${firstLine.trimStart()}
|
|
455
|
-
${rest}`;
|
|
456
|
-
}
|
|
457
|
-
return `${prefix}- ${serialised}`;
|
|
458
|
-
}).join("\n");
|
|
459
|
-
}
|
|
460
|
-
if (typeof value === "object") {
|
|
461
|
-
const entries = Object.entries(value);
|
|
462
|
-
if (entries.length === 0) return "{}";
|
|
463
|
-
const prefix = " ".repeat(indent);
|
|
464
|
-
return entries.map(([key, val]) => {
|
|
465
|
-
if (val === null || val === void 0) {
|
|
466
|
-
return `${prefix}${key}: null`;
|
|
467
|
-
}
|
|
468
|
-
if (typeof val === "object") {
|
|
469
|
-
const nested = toYaml(val, indent + 2);
|
|
470
|
-
return `${prefix}${key}:
|
|
471
|
-
${nested}`;
|
|
472
|
-
}
|
|
473
|
-
return `${prefix}${key}: ${toYaml(val, indent)}`;
|
|
474
|
-
}).join("\n");
|
|
475
|
-
}
|
|
476
|
-
return String(value);
|
|
477
|
-
}
|
|
478
|
-
function needsQuoting(s) {
|
|
479
|
-
if (s === "") return true;
|
|
480
|
-
if (s === "true" || s === "false" || s === "null") return true;
|
|
481
|
-
if (/^\d/.test(s)) return true;
|
|
482
|
-
if (/[:{}\[\],&*#?|<>=!%@`'"\\\n\r\t]/.test(s)) return true;
|
|
483
|
-
if (s.startsWith(" ") || s.endsWith(" ")) return true;
|
|
484
|
-
return false;
|
|
575
|
+
async function getOperationHelp(entry, operationId) {
|
|
576
|
+
const bin = resolveOpenapi2CliBin();
|
|
577
|
+
const args = [
|
|
578
|
+
"run",
|
|
579
|
+
"--oas",
|
|
580
|
+
entry.remoteUrl,
|
|
581
|
+
"--cache-ttl",
|
|
582
|
+
String(entry.cacheTtl),
|
|
583
|
+
...entry.baseEndpoint ? ["--endpoint", entry.baseEndpoint] : [],
|
|
584
|
+
operationId,
|
|
585
|
+
"--help"
|
|
586
|
+
];
|
|
587
|
+
return new Promise((resolve2, reject) => {
|
|
588
|
+
let output = "";
|
|
589
|
+
const child = spawn(process.execPath, [bin, ...args], {
|
|
590
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
591
|
+
});
|
|
592
|
+
child.stdout?.on("data", (d) => {
|
|
593
|
+
output += d.toString();
|
|
594
|
+
});
|
|
595
|
+
child.stderr?.on("data", (d) => {
|
|
596
|
+
output += d.toString();
|
|
597
|
+
});
|
|
598
|
+
child.on("close", () => resolve2(output));
|
|
599
|
+
child.on("error", reject);
|
|
600
|
+
});
|
|
485
601
|
}
|
|
486
602
|
|
|
487
|
-
// src/commands/
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (!
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
506
|
-
...rest,
|
|
507
|
-
authConfig: { type: authConfig["type"] ?? rest.authType }
|
|
508
|
-
}));
|
|
509
|
-
if (isJsonOutput()) {
|
|
510
|
-
outputSuccess(safe);
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
const format = (opts.format ?? "table").toLowerCase();
|
|
514
|
-
if (entries.length === 0) {
|
|
515
|
-
console.log("No services registered in this group.");
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
if (format === "json") {
|
|
519
|
-
console.log(JSON.stringify(safe, null, 2));
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
if (format === "yaml") {
|
|
523
|
-
console.log(toYaml(safe));
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
|
|
527
|
-
console.log(`
|
|
528
|
-
${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
|
|
529
|
-
console.log(`${"-".repeat(nameWidth)} -------- ${"-".repeat(40)}`);
|
|
530
|
-
for (const e of entries) {
|
|
531
|
-
const auth = e.authType.padEnd(8);
|
|
532
|
-
const desc = e.description.length > 60 ? e.description.slice(0, 57) + "..." : e.description;
|
|
533
|
-
console.log(`${e.name.padEnd(nameWidth)} ${auth} ${desc}`);
|
|
603
|
+
// src/commands/oas.ts
|
|
604
|
+
var VALID_OAS_ACTIONS = ["info", "listapi", "apiinfo", "invokeapi"];
|
|
605
|
+
function registerOas(program2) {
|
|
606
|
+
program2.command("oas <name> <action> [args...]").description(
|
|
607
|
+
"Interact with an OAS service: info | listapi | apiinfo <api> | invokeapi <api> --data <json>"
|
|
608
|
+
).option("--format <fmt>", "Output format: json | table | yaml", "json").option("--data <json>", "Request body (JSON string or @filename, for invokeapi)").option("--params <json>", "Operation parameters as JSON (for invokeapi)").option("--query <jmespath>", "Filter response with JMESPath expression").option("--machine", "Agent-friendly mode: structured JSON envelope output").option("--dry-run", "Preview the HTTP request without executing (implies --machine)").allowUnknownOption(true).action(async (name, action, args, opts) => {
|
|
609
|
+
if (!VALID_OAS_ACTIONS.includes(action)) {
|
|
610
|
+
outputError(
|
|
611
|
+
ExitCode.USAGE_ERROR,
|
|
612
|
+
`Unknown action: ${action}`,
|
|
613
|
+
`Valid actions: ${VALID_OAS_ACTIONS.join(", ")}
|
|
614
|
+
Usage:
|
|
615
|
+
ucli oas <service> info
|
|
616
|
+
ucli oas <service> listapi
|
|
617
|
+
ucli oas <service> apiinfo <api>
|
|
618
|
+
ucli oas <service> invokeapi <api> --data <json>`
|
|
619
|
+
);
|
|
534
620
|
}
|
|
535
|
-
console.log();
|
|
536
|
-
});
|
|
537
|
-
services.command("info <name>").description("Show detailed information and available operations for a service").option("--format <fmt>", "Output format: table | json | yaml", "table").action(async (name, opts) => {
|
|
538
621
|
const cfg = getConfig();
|
|
539
622
|
const client = new ServerClient(cfg);
|
|
540
623
|
let entry;
|
|
@@ -544,112 +627,344 @@ ${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
|
|
|
544
627
|
outputError(
|
|
545
628
|
ExitCode.NOT_FOUND,
|
|
546
629
|
`Service not found: ${name}`,
|
|
547
|
-
"Run: ucli
|
|
630
|
+
"Run: ucli listoas to see available services"
|
|
548
631
|
);
|
|
549
632
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
633
|
+
switch (action) {
|
|
634
|
+
case "info": {
|
|
635
|
+
const { authConfig, ...rest } = entry;
|
|
636
|
+
const safe = {
|
|
637
|
+
...rest,
|
|
638
|
+
authConfig: { type: authConfig["type"] ?? rest.authType }
|
|
639
|
+
};
|
|
640
|
+
if (isJsonOutput()) {
|
|
641
|
+
outputSuccess(safe);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
const format = (opts.format ?? "json").toLowerCase();
|
|
645
|
+
if (format === "json") {
|
|
646
|
+
console.log(JSON.stringify(safe, null, 2));
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (format === "yaml") {
|
|
650
|
+
console.log(toYaml(safe));
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
console.log(`
|
|
654
|
+
Service: ${entry.name}`);
|
|
655
|
+
console.log(`Description: ${entry.description || "(none)"}`);
|
|
656
|
+
console.log(`OAS URL: ${entry.remoteUrl}`);
|
|
657
|
+
if (entry.baseEndpoint) console.log(`Base endpoint: ${entry.baseEndpoint}`);
|
|
658
|
+
console.log(`Auth type: ${entry.authType}`);
|
|
659
|
+
console.log(`Cache TTL: ${entry.cacheTtl}s`);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
case "listapi": {
|
|
663
|
+
const help = await getServiceHelp(entry);
|
|
664
|
+
const { authConfig, ...rest } = entry;
|
|
665
|
+
const safe = {
|
|
666
|
+
...rest,
|
|
667
|
+
authConfig: { type: authConfig["type"] ?? rest.authType },
|
|
668
|
+
operationsHelp: help
|
|
669
|
+
};
|
|
670
|
+
if (isJsonOutput()) {
|
|
671
|
+
outputSuccess(safe);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const format = (opts.format ?? "json").toLowerCase();
|
|
675
|
+
if (format === "json") {
|
|
676
|
+
console.log(JSON.stringify(safe, null, 2));
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (format === "yaml") {
|
|
680
|
+
console.log(toYaml(safe));
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
console.log(`
|
|
684
|
+
Service: ${entry.name}`);
|
|
685
|
+
console.log(`Description: ${entry.description || "(none)"}`);
|
|
686
|
+
console.log("\nAvailable operations:");
|
|
687
|
+
console.log("\u2500".repeat(60));
|
|
688
|
+
console.log(help);
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
case "apiinfo": {
|
|
692
|
+
const apiName = args[0];
|
|
693
|
+
if (!apiName) {
|
|
694
|
+
outputError(
|
|
695
|
+
ExitCode.USAGE_ERROR,
|
|
696
|
+
"Missing API operation name.",
|
|
697
|
+
"Usage: ucli oas <service> apiinfo <api>"
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
const help = await getOperationHelp(entry, apiName);
|
|
701
|
+
if (isJsonOutput()) {
|
|
702
|
+
outputSuccess({ operation: apiName, service: name, help });
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const format = (opts.format ?? "json").toLowerCase();
|
|
706
|
+
if (format === "json") {
|
|
707
|
+
console.log(JSON.stringify({ operation: apiName, service: name, help }, null, 2));
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
console.log(`
|
|
711
|
+
Operation: ${apiName} (service: ${name})`);
|
|
712
|
+
console.log("\u2500".repeat(60));
|
|
713
|
+
console.log(help);
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
case "invokeapi": {
|
|
717
|
+
const apiName = args[0];
|
|
718
|
+
if (!apiName) {
|
|
719
|
+
outputError(
|
|
720
|
+
ExitCode.USAGE_ERROR,
|
|
721
|
+
"Missing API operation name.",
|
|
722
|
+
"Usage: ucli oas <service> invokeapi <api> --data <json>"
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
const operationArgs = [apiName];
|
|
726
|
+
const extraArgs = args.slice(1);
|
|
727
|
+
operationArgs.push(...extraArgs);
|
|
728
|
+
if (opts.params) {
|
|
729
|
+
let parsed;
|
|
730
|
+
try {
|
|
731
|
+
parsed = JSON.parse(opts.params);
|
|
732
|
+
} catch {
|
|
733
|
+
outputError(
|
|
734
|
+
ExitCode.USAGE_ERROR,
|
|
735
|
+
"Invalid --params JSON.",
|
|
736
|
+
`Example: --params '{"petId": 1}'`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
740
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
741
|
+
if (v === void 0 || v === null) continue;
|
|
742
|
+
const strVal = typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
743
|
+
operationArgs.push(`--${k}`, strVal);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (opts.data) {
|
|
748
|
+
operationArgs.push("--data", opts.data);
|
|
749
|
+
}
|
|
750
|
+
const format = opts.format;
|
|
751
|
+
const query = opts.query;
|
|
752
|
+
const machine = opts.machine ?? false;
|
|
753
|
+
const dryRun = opts.dryRun ?? false;
|
|
754
|
+
try {
|
|
755
|
+
await runOperation({
|
|
756
|
+
entry,
|
|
757
|
+
operationArgs,
|
|
758
|
+
...format !== void 0 ? { format } : {},
|
|
759
|
+
...query !== void 0 ? { query } : {},
|
|
760
|
+
...machine ? { machine } : {},
|
|
761
|
+
...dryRun ? { dryRun } : {}
|
|
762
|
+
});
|
|
763
|
+
} catch (err) {
|
|
764
|
+
outputError(
|
|
765
|
+
ExitCode.GENERAL_ERROR,
|
|
766
|
+
`Operation failed: ${err.message}`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
560
771
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/lib/mcp-runner.ts
|
|
776
|
+
function resolve(mod, name) {
|
|
777
|
+
if (typeof mod[name] === "function") return mod[name];
|
|
778
|
+
if (mod.default && typeof mod.default[name] === "function") return mod.default[name];
|
|
779
|
+
throw new Error(`Cannot resolve export "${name}" from module`);
|
|
780
|
+
}
|
|
781
|
+
async function getMcp2cli() {
|
|
782
|
+
const clientMod = await import("./client-Y6NONDCI.js");
|
|
783
|
+
const runnerMod = await import("./runner-ROLDMGH3.js");
|
|
784
|
+
return {
|
|
785
|
+
createMcpClient: resolve(clientMod, "createMcpClient"),
|
|
786
|
+
getTools: resolve(runnerMod, "getTools"),
|
|
787
|
+
runTool: resolve(runnerMod, "runTool"),
|
|
788
|
+
describeTool: resolve(runnerMod, "describeTool"),
|
|
789
|
+
describeToolJson: resolve(runnerMod, "describeToolJson")
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
async function closeClient(client) {
|
|
793
|
+
if (typeof client.close === "function") {
|
|
794
|
+
await client.close();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function buildMcpConfig(entry) {
|
|
798
|
+
const base = { type: entry.transport };
|
|
799
|
+
if (entry.transport === "http") {
|
|
800
|
+
base.url = entry.serverUrl;
|
|
801
|
+
} else {
|
|
802
|
+
base.command = entry.command;
|
|
803
|
+
}
|
|
804
|
+
const auth = entry.authConfig;
|
|
805
|
+
if (auth.type === "http_headers") {
|
|
806
|
+
base.headers = auth.headers;
|
|
807
|
+
} else if (auth.type === "env") {
|
|
808
|
+
base.env = auth.env;
|
|
809
|
+
}
|
|
810
|
+
return base;
|
|
811
|
+
}
|
|
812
|
+
async function listMcpTools(entry) {
|
|
813
|
+
const { createMcpClient, getTools } = await getMcp2cli();
|
|
814
|
+
const config = buildMcpConfig(entry);
|
|
815
|
+
const client = await createMcpClient(config);
|
|
816
|
+
try {
|
|
817
|
+
const tools = await getTools(client, config, { noCache: true });
|
|
818
|
+
return tools;
|
|
819
|
+
} finally {
|
|
820
|
+
await closeClient(client);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
async function describeMcpTool(entry, toolName, opts) {
|
|
824
|
+
const { createMcpClient, getTools, describeTool, describeToolJson } = await getMcp2cli();
|
|
825
|
+
const config = buildMcpConfig(entry);
|
|
826
|
+
const client = await createMcpClient(config);
|
|
827
|
+
try {
|
|
828
|
+
const tools = await getTools(client, config, { noCache: false, cacheTtl: 3600 });
|
|
829
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
830
|
+
if (!tool) throw new Error(`Tool "${toolName}" not found on MCP server "${entry.name}"`);
|
|
831
|
+
if (opts?.json) {
|
|
832
|
+
describeToolJson(tool);
|
|
833
|
+
} else {
|
|
834
|
+
describeTool(tool);
|
|
565
835
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
836
|
+
} finally {
|
|
837
|
+
await closeClient(client);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
async function runMcpTool(entry, toolName, rawArgs, opts) {
|
|
841
|
+
const { createMcpClient, getTools, runTool } = await getMcp2cli();
|
|
842
|
+
const config = buildMcpConfig(entry);
|
|
843
|
+
const client = await createMcpClient(config);
|
|
844
|
+
try {
|
|
845
|
+
const tools = await getTools(client, config, { noCache: false, cacheTtl: 3600 });
|
|
846
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
847
|
+
if (!tool) throw new Error(`Tool "${toolName}" not found on MCP server "${entry.name}"`);
|
|
848
|
+
const normalizedArgs = [];
|
|
849
|
+
for (const arg of rawArgs) {
|
|
850
|
+
if (arg.includes("=") && !arg.startsWith("--")) {
|
|
851
|
+
const idx = arg.indexOf("=");
|
|
852
|
+
const key = arg.slice(0, idx);
|
|
853
|
+
const value = arg.slice(idx + 1);
|
|
854
|
+
normalizedArgs.push(`--${key}`, value);
|
|
855
|
+
} else {
|
|
856
|
+
normalizedArgs.push(arg);
|
|
857
|
+
}
|
|
569
858
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
console.log("\nAvailable operations:");
|
|
578
|
-
console.log("\u2500".repeat(60));
|
|
579
|
-
console.log(help);
|
|
580
|
-
});
|
|
859
|
+
await runTool(client, tool, normalizedArgs, {
|
|
860
|
+
...opts?.json ? { json: true } : {},
|
|
861
|
+
...opts?.inputJson ? { inputJson: opts.inputJson } : {}
|
|
862
|
+
});
|
|
863
|
+
} finally {
|
|
864
|
+
await closeClient(client);
|
|
865
|
+
}
|
|
581
866
|
}
|
|
582
867
|
|
|
583
|
-
// src/commands/
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
const service = opts.service ?? serviceArg;
|
|
593
|
-
if (!service) {
|
|
868
|
+
// src/commands/mcp.ts
|
|
869
|
+
var VALID_MCP_ACTIONS = ["listtool", "toolinfo", "invoketool"];
|
|
870
|
+
function registerMcp(program2) {
|
|
871
|
+
program2.command("mcp <name> <action> [args...]").description(
|
|
872
|
+
"Interact with a MCP server: listtool | toolinfo <tool> | invoketool <tool> --data <json>"
|
|
873
|
+
).option("--format <fmt>", "Output format: table | json | yaml", "table").option("--data <json>", "Tool arguments as a JSON object (for invoketool)").option("--json", "Machine-readable JSON output").allowUnknownOption(true).action(async (name, action, args, opts) => {
|
|
874
|
+
if (!VALID_MCP_ACTIONS.includes(action)) {
|
|
594
875
|
outputError(
|
|
595
876
|
ExitCode.USAGE_ERROR,
|
|
596
|
-
|
|
597
|
-
|
|
877
|
+
`Unknown action: ${action}`,
|
|
878
|
+
`Valid actions: ${VALID_MCP_ACTIONS.join(", ")}
|
|
879
|
+
Usage:
|
|
880
|
+
ucli mcp <server> listtool
|
|
881
|
+
ucli mcp <server> toolinfo <tool>
|
|
882
|
+
ucli mcp <server> invoketool <tool> --data <json>`
|
|
598
883
|
);
|
|
599
884
|
}
|
|
600
885
|
const cfg = getConfig();
|
|
601
886
|
const client = new ServerClient(cfg);
|
|
602
887
|
let entry;
|
|
603
888
|
try {
|
|
604
|
-
entry = await client.
|
|
889
|
+
entry = await client.getMCP(name);
|
|
605
890
|
} catch {
|
|
606
891
|
outputError(
|
|
607
892
|
ExitCode.NOT_FOUND,
|
|
608
|
-
`Unknown
|
|
609
|
-
"Run: ucli
|
|
893
|
+
`Unknown MCP server: ${name}`,
|
|
894
|
+
"Run: ucli listmcp to see available servers"
|
|
610
895
|
);
|
|
611
896
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
`
|
|
626
|
-
|
|
897
|
+
switch (action) {
|
|
898
|
+
case "listtool": {
|
|
899
|
+
let tools;
|
|
900
|
+
try {
|
|
901
|
+
tools = await listMcpTools(entry);
|
|
902
|
+
} catch (err) {
|
|
903
|
+
outputError(ExitCode.GENERAL_ERROR, `Failed to fetch tools: ${err.message}`);
|
|
904
|
+
}
|
|
905
|
+
if (isJsonOutput()) {
|
|
906
|
+
outputSuccess(tools);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (tools.length === 0) {
|
|
910
|
+
console.log(`No tools found on MCP server "${name}".`);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const format = (opts.format ?? "table").toLowerCase();
|
|
914
|
+
if (format === "json") {
|
|
915
|
+
console.log(JSON.stringify(tools, null, 2));
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
if (format === "yaml") {
|
|
919
|
+
console.log(toYaml(tools));
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
console.log(`
|
|
923
|
+
Tools on "${name}":`);
|
|
924
|
+
console.log("\u2500".repeat(60));
|
|
925
|
+
for (const t of tools) {
|
|
926
|
+
console.log(` ${t.name}`);
|
|
927
|
+
if (t.description) console.log(` ${t.description}`);
|
|
928
|
+
}
|
|
929
|
+
console.log();
|
|
930
|
+
break;
|
|
627
931
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
932
|
+
case "toolinfo": {
|
|
933
|
+
const toolName = args[0];
|
|
934
|
+
if (!toolName) {
|
|
935
|
+
outputError(
|
|
936
|
+
ExitCode.USAGE_ERROR,
|
|
937
|
+
"Missing tool name.",
|
|
938
|
+
"Usage: ucli mcp <server> toolinfo <tool>"
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
try {
|
|
942
|
+
await describeMcpTool(entry, toolName, { json: opts.json });
|
|
943
|
+
} catch (err) {
|
|
944
|
+
outputError(ExitCode.GENERAL_ERROR, `Failed to describe tool: ${err.message}`);
|
|
633
945
|
}
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
948
|
+
case "invoketool": {
|
|
949
|
+
const toolName = args[0];
|
|
950
|
+
if (!toolName) {
|
|
951
|
+
outputError(
|
|
952
|
+
ExitCode.USAGE_ERROR,
|
|
953
|
+
"Missing tool name.",
|
|
954
|
+
"Usage: ucli mcp <server> invoketool <tool> --data <json>"
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
const extraArgs = args.slice(1);
|
|
958
|
+
try {
|
|
959
|
+
await runMcpTool(entry, toolName, extraArgs, {
|
|
960
|
+
json: opts.json,
|
|
961
|
+
inputJson: opts.data
|
|
962
|
+
});
|
|
963
|
+
} catch (err) {
|
|
964
|
+
outputError(ExitCode.GENERAL_ERROR, `Tool execution failed: ${err.message}`);
|
|
965
|
+
}
|
|
966
|
+
break;
|
|
634
967
|
}
|
|
635
|
-
}
|
|
636
|
-
if (opts.data) {
|
|
637
|
-
operationArgs.push("--data", opts.data);
|
|
638
|
-
}
|
|
639
|
-
const format = opts.format;
|
|
640
|
-
const query = opts.query;
|
|
641
|
-
try {
|
|
642
|
-
await runOperation({
|
|
643
|
-
entry,
|
|
644
|
-
operationArgs,
|
|
645
|
-
...format !== void 0 ? { format } : {},
|
|
646
|
-
...query !== void 0 ? { query } : {}
|
|
647
|
-
});
|
|
648
|
-
} catch (err) {
|
|
649
|
-
outputError(
|
|
650
|
-
ExitCode.GENERAL_ERROR,
|
|
651
|
-
`Operation failed: ${err.message}`
|
|
652
|
-
);
|
|
653
968
|
}
|
|
654
969
|
});
|
|
655
970
|
}
|
|
@@ -701,7 +1016,7 @@ function registerHelp(program2) {
|
|
|
701
1016
|
for (const e of entries) {
|
|
702
1017
|
console.log(` ${e.name.padEnd(20)} ${e.description}`);
|
|
703
1018
|
}
|
|
704
|
-
console.log("\nTip: Run `ucli
|
|
1019
|
+
console.log("\nTip: Run `ucli oas <service> listapi` for service-specific operations.");
|
|
705
1020
|
}
|
|
706
1021
|
} else {
|
|
707
1022
|
console.log("\nRun `ucli configure --server <url> --token <jwt>` to get started.");
|
|
@@ -715,7 +1030,7 @@ function registerHelp(program2) {
|
|
|
715
1030
|
entry = await client.getOAS(service);
|
|
716
1031
|
} catch {
|
|
717
1032
|
console.error(`Unknown service: ${service}`);
|
|
718
|
-
console.error("Run `ucli
|
|
1033
|
+
console.error("Run `ucli listoas` to see available services.");
|
|
719
1034
|
process.exit(ExitCode.NOT_FOUND);
|
|
720
1035
|
}
|
|
721
1036
|
console.log(`
|
|
@@ -728,10 +1043,9 @@ OAS spec: ${entry.remoteUrl}`);
|
|
|
728
1043
|
const help = await getServiceHelp(entry);
|
|
729
1044
|
console.log(help);
|
|
730
1045
|
console.log("\nExamples:");
|
|
731
|
-
console.log(` ucli
|
|
732
|
-
console.log(` ucli
|
|
733
|
-
console.log(` ucli
|
|
734
|
-
console.log(` ucli run ${entry.name} <operation> --data '{"key":"value"}'`);
|
|
1046
|
+
console.log(` ucli oas ${entry.name} listapi`);
|
|
1047
|
+
console.log(` ucli oas ${entry.name} apiinfo <operation>`);
|
|
1048
|
+
console.log(` ucli oas ${entry.name} invokeapi <operation> --data '{"key":"value"}'`);
|
|
735
1049
|
});
|
|
736
1050
|
}
|
|
737
1051
|
function printGeneralHelp() {
|
|
@@ -744,11 +1058,11 @@ SETUP
|
|
|
744
1058
|
Configure server connection and authentication.
|
|
745
1059
|
|
|
746
1060
|
DISCOVERY
|
|
747
|
-
ucli
|
|
1061
|
+
ucli listoas
|
|
748
1062
|
List all OAS services available in your group.
|
|
749
1063
|
|
|
750
|
-
ucli
|
|
751
|
-
|
|
1064
|
+
ucli listmcp
|
|
1065
|
+
List all MCP servers available in your group.
|
|
752
1066
|
|
|
753
1067
|
ucli introspect
|
|
754
1068
|
Return complete capability manifest in a single call (JSON).
|
|
@@ -757,9 +1071,18 @@ DISCOVERY
|
|
|
757
1071
|
ucli help [service]
|
|
758
1072
|
Show this guide, or service-specific operations.
|
|
759
1073
|
|
|
760
|
-
|
|
761
|
-
ucli
|
|
762
|
-
|
|
1074
|
+
OAS SERVICES
|
|
1075
|
+
ucli oas <service> info
|
|
1076
|
+
Show detailed service information.
|
|
1077
|
+
|
|
1078
|
+
ucli oas <service> listapi
|
|
1079
|
+
List all available API operations.
|
|
1080
|
+
|
|
1081
|
+
ucli oas <service> apiinfo <api>
|
|
1082
|
+
Show detailed input/output parameters for an API operation.
|
|
1083
|
+
|
|
1084
|
+
ucli oas <service> invokeapi <api> [options]
|
|
1085
|
+
Execute an API operation.
|
|
763
1086
|
|
|
764
1087
|
Options:
|
|
765
1088
|
--format json|table|yaml Output format (default: json)
|
|
@@ -767,13 +1090,13 @@ EXECUTION
|
|
|
767
1090
|
--data <json|@file> Request body for POST/PUT/PATCH
|
|
768
1091
|
|
|
769
1092
|
MCP SERVERS
|
|
770
|
-
ucli mcp
|
|
771
|
-
List all MCP servers in your group.
|
|
772
|
-
|
|
773
|
-
ucli mcp tools <server>
|
|
1093
|
+
ucli mcp <server> listtool
|
|
774
1094
|
List tools available on a MCP server.
|
|
775
1095
|
|
|
776
|
-
ucli mcp
|
|
1096
|
+
ucli mcp <server> toolinfo <tool>
|
|
1097
|
+
Show detailed input/output parameters for a tool.
|
|
1098
|
+
|
|
1099
|
+
ucli mcp <server> invoketool <tool> --data <json>
|
|
777
1100
|
Call a tool on a MCP server.
|
|
778
1101
|
|
|
779
1102
|
MAINTENANCE
|
|
@@ -796,192 +1119,18 @@ GLOBAL FLAGS
|
|
|
796
1119
|
|
|
797
1120
|
ERRORS
|
|
798
1121
|
401 Unauthorized \u2192 Run: ucli configure --server <url> --token <jwt>
|
|
799
|
-
404 Not Found \u2192 Check service name: ucli
|
|
800
|
-
4xx Client Error \u2192 Check operation args: ucli
|
|
1122
|
+
404 Not Found \u2192 Check service name: ucli listoas
|
|
1123
|
+
4xx Client Error \u2192 Check operation args: ucli oas <service> listapi
|
|
801
1124
|
5xx Server Error \u2192 Retry or run: ucli refresh
|
|
802
1125
|
|
|
803
1126
|
AI AGENT QUICK START
|
|
804
1127
|
1. ucli introspect # discover everything in one call
|
|
805
|
-
2. ucli
|
|
806
|
-
3.
|
|
1128
|
+
2. ucli oas <svc> invokeapi <op> # execute OAS operations
|
|
1129
|
+
3. ucli mcp <srv> invoketool <t> # execute MCP tools
|
|
1130
|
+
4. Use --output json globally # get structured { success, data/error } envelopes
|
|
807
1131
|
`);
|
|
808
1132
|
}
|
|
809
1133
|
|
|
810
|
-
// src/lib/mcp-runner.ts
|
|
811
|
-
function resolve(mod, name) {
|
|
812
|
-
if (typeof mod[name] === "function") return mod[name];
|
|
813
|
-
if (mod.default && typeof mod.default[name] === "function") return mod.default[name];
|
|
814
|
-
throw new Error(`Cannot resolve export "${name}" from module`);
|
|
815
|
-
}
|
|
816
|
-
async function getMcp2cli() {
|
|
817
|
-
const clientMod = await import("./client-LCWUZRZX.js");
|
|
818
|
-
const runnerMod = await import("./runner-HH357SRR.js");
|
|
819
|
-
return {
|
|
820
|
-
createMcpClient: resolve(clientMod, "createMcpClient"),
|
|
821
|
-
getTools: resolve(runnerMod, "getTools"),
|
|
822
|
-
runTool: resolve(runnerMod, "runTool")
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
async function closeClient(client) {
|
|
826
|
-
if (typeof client.close === "function") {
|
|
827
|
-
await client.close();
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
function buildMcpConfig(entry) {
|
|
831
|
-
const base = { type: entry.transport };
|
|
832
|
-
if (entry.transport === "http") {
|
|
833
|
-
base.url = entry.serverUrl;
|
|
834
|
-
} else {
|
|
835
|
-
base.command = entry.command;
|
|
836
|
-
}
|
|
837
|
-
const auth = entry.authConfig;
|
|
838
|
-
if (auth.type === "http_headers") {
|
|
839
|
-
base.headers = auth.headers;
|
|
840
|
-
} else if (auth.type === "env") {
|
|
841
|
-
base.env = auth.env;
|
|
842
|
-
}
|
|
843
|
-
return base;
|
|
844
|
-
}
|
|
845
|
-
async function listMcpTools(entry) {
|
|
846
|
-
const { createMcpClient, getTools } = await getMcp2cli();
|
|
847
|
-
const config = buildMcpConfig(entry);
|
|
848
|
-
const client = await createMcpClient(config);
|
|
849
|
-
try {
|
|
850
|
-
const tools = await getTools(client, config, { noCache: true });
|
|
851
|
-
return tools;
|
|
852
|
-
} finally {
|
|
853
|
-
await closeClient(client);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
async function runMcpTool(entry, toolName, rawArgs) {
|
|
857
|
-
const { createMcpClient, getTools, runTool } = await getMcp2cli();
|
|
858
|
-
const config = buildMcpConfig(entry);
|
|
859
|
-
const client = await createMcpClient(config);
|
|
860
|
-
try {
|
|
861
|
-
const tools = await getTools(client, config, { noCache: false, cacheTtl: 3600 });
|
|
862
|
-
const tool = tools.find((t) => t.name === toolName);
|
|
863
|
-
if (!tool) throw new Error(`Tool "${toolName}" not found on MCP server "${entry.name}"`);
|
|
864
|
-
const normalizedArgs = [];
|
|
865
|
-
for (const arg of rawArgs) {
|
|
866
|
-
if (arg.includes("=") && !arg.startsWith("--")) {
|
|
867
|
-
const idx = arg.indexOf("=");
|
|
868
|
-
const key = arg.slice(0, idx);
|
|
869
|
-
const value = arg.slice(idx + 1);
|
|
870
|
-
normalizedArgs.push(`--${key}`, value);
|
|
871
|
-
} else {
|
|
872
|
-
normalizedArgs.push(arg);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
await runTool(client, tool, normalizedArgs, {});
|
|
876
|
-
} finally {
|
|
877
|
-
await closeClient(client);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
// src/commands/mcp.ts
|
|
882
|
-
function registerMcp(program2) {
|
|
883
|
-
const mcp = program2.command("mcp").description("Interact with MCP servers registered in your group");
|
|
884
|
-
mcp.command("list").description("List all MCP servers available in the current group").option("--format <fmt>", "Output format: table | json | yaml", "table").action(async (opts) => {
|
|
885
|
-
const cfg = getConfig();
|
|
886
|
-
const client = new ServerClient(cfg);
|
|
887
|
-
const entries = await client.listMCP();
|
|
888
|
-
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
889
|
-
...rest,
|
|
890
|
-
authConfig: { type: authConfig.type }
|
|
891
|
-
}));
|
|
892
|
-
if (isJsonOutput()) {
|
|
893
|
-
outputSuccess(safe);
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
if (entries.length === 0) {
|
|
897
|
-
console.log("No MCP servers registered in this group.");
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
const format = (opts.format ?? "table").toLowerCase();
|
|
901
|
-
if (format === "json") {
|
|
902
|
-
console.log(JSON.stringify(safe, null, 2));
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
if (format === "yaml") {
|
|
906
|
-
console.log(toYaml(safe));
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
|
|
910
|
-
console.log(`
|
|
911
|
-
${"SERVER".padEnd(nameWidth)} TRANSPORT DESCRIPTION`);
|
|
912
|
-
console.log(`${"-".repeat(nameWidth)} --------- ${"-".repeat(40)}`);
|
|
913
|
-
for (const e of entries) {
|
|
914
|
-
const desc = e.description.length > 60 ? e.description.slice(0, 57) + "..." : e.description;
|
|
915
|
-
console.log(`${e.name.padEnd(nameWidth)} ${e.transport.padEnd(9)} ${desc}`);
|
|
916
|
-
}
|
|
917
|
-
console.log();
|
|
918
|
-
});
|
|
919
|
-
mcp.command("tools <server>").description("List tools available on a MCP server").option("--format <fmt>", "Output format: table | json | yaml", "table").action(async (serverName, opts) => {
|
|
920
|
-
const cfg = getConfig();
|
|
921
|
-
const client = new ServerClient(cfg);
|
|
922
|
-
let entry;
|
|
923
|
-
try {
|
|
924
|
-
entry = await client.getMCP(serverName);
|
|
925
|
-
} catch {
|
|
926
|
-
outputError(
|
|
927
|
-
ExitCode.NOT_FOUND,
|
|
928
|
-
`Unknown MCP server: ${serverName}`,
|
|
929
|
-
"Run: ucli mcp list to see available servers"
|
|
930
|
-
);
|
|
931
|
-
}
|
|
932
|
-
let tools;
|
|
933
|
-
try {
|
|
934
|
-
tools = await listMcpTools(entry);
|
|
935
|
-
} catch (err) {
|
|
936
|
-
outputError(ExitCode.GENERAL_ERROR, `Failed to fetch tools: ${err.message}`);
|
|
937
|
-
}
|
|
938
|
-
if (isJsonOutput()) {
|
|
939
|
-
outputSuccess(tools);
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
if (tools.length === 0) {
|
|
943
|
-
console.log(`No tools found on MCP server "${serverName}".`);
|
|
944
|
-
return;
|
|
945
|
-
}
|
|
946
|
-
const format = (opts.format ?? "table").toLowerCase();
|
|
947
|
-
if (format === "json") {
|
|
948
|
-
console.log(JSON.stringify(tools, null, 2));
|
|
949
|
-
return;
|
|
950
|
-
}
|
|
951
|
-
if (format === "yaml") {
|
|
952
|
-
console.log(toYaml(tools));
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
console.log(`
|
|
956
|
-
Tools on "${serverName}":`);
|
|
957
|
-
console.log("\u2500".repeat(60));
|
|
958
|
-
for (const t of tools) {
|
|
959
|
-
console.log(` ${t.name}`);
|
|
960
|
-
if (t.description) console.log(` ${t.description}`);
|
|
961
|
-
}
|
|
962
|
-
console.log();
|
|
963
|
-
});
|
|
964
|
-
mcp.command("run <server> <tool> [args...]").description("Call a tool on a MCP server").action(async (serverName, toolName, args) => {
|
|
965
|
-
const cfg = getConfig();
|
|
966
|
-
const client = new ServerClient(cfg);
|
|
967
|
-
let entry;
|
|
968
|
-
try {
|
|
969
|
-
entry = await client.getMCP(serverName);
|
|
970
|
-
} catch {
|
|
971
|
-
outputError(
|
|
972
|
-
ExitCode.NOT_FOUND,
|
|
973
|
-
`Unknown MCP server: ${serverName}`,
|
|
974
|
-
"Run: ucli mcp list to see available servers"
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
try {
|
|
978
|
-
await runMcpTool(entry, toolName, args);
|
|
979
|
-
} catch (err) {
|
|
980
|
-
outputError(ExitCode.GENERAL_ERROR, `Tool execution failed: ${err.message}`);
|
|
981
|
-
}
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
|
|
985
1134
|
// src/lib/errors.ts
|
|
986
1135
|
var debugEnabled = false;
|
|
987
1136
|
function setDebugMode(enabled) {
|
|
@@ -996,7 +1145,7 @@ var HINT_MAP = {
|
|
|
996
1145
|
[ExitCode.CONFIG_ERROR]: "Run: ucli configure --server <url> --token <jwt>",
|
|
997
1146
|
[ExitCode.AUTH_ERROR]: "Your token may be expired or revoked. Run: ucli configure --server <url> --token <jwt>",
|
|
998
1147
|
[ExitCode.CONNECTIVITY_ERROR]: "Check that the server URL is correct and the server is running. Run: ucli doctor",
|
|
999
|
-
[ExitCode.NOT_FOUND]: "Check the resource name. Run: ucli
|
|
1148
|
+
[ExitCode.NOT_FOUND]: "Check the resource name. Run: ucli listoas or ucli listmcp",
|
|
1000
1149
|
[ExitCode.SERVER_ERROR]: "The server returned an unexpected error. Try again or run: ucli doctor"
|
|
1001
1150
|
};
|
|
1002
1151
|
|
|
@@ -1120,22 +1269,26 @@ _ucli_completions() {
|
|
|
1120
1269
|
COMPREPLY=()
|
|
1121
1270
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
1122
1271
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
1123
|
-
commands="configure
|
|
1272
|
+
commands="configure listoas listmcp oas mcp refresh help doctor completions introspect"
|
|
1124
1273
|
|
|
1125
1274
|
case "\${COMP_WORDS[1]}" in
|
|
1126
|
-
|
|
1127
|
-
|
|
1275
|
+
oas)
|
|
1276
|
+
if [ "$COMP_CWORD" -eq 3 ]; then
|
|
1277
|
+
COMPREPLY=( $(compgen -W "info listapi apiinfo invokeapi" -- "$cur") )
|
|
1278
|
+
fi
|
|
1128
1279
|
return 0
|
|
1129
1280
|
;;
|
|
1130
1281
|
mcp)
|
|
1131
|
-
|
|
1282
|
+
if [ "$COMP_CWORD" -eq 3 ]; then
|
|
1283
|
+
COMPREPLY=( $(compgen -W "listtool toolinfo invoketool" -- "$cur") )
|
|
1284
|
+
fi
|
|
1132
1285
|
return 0
|
|
1133
1286
|
;;
|
|
1134
1287
|
completions)
|
|
1135
1288
|
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
|
|
1136
1289
|
return 0
|
|
1137
1290
|
;;
|
|
1138
|
-
|
|
1291
|
+
listoas|listmcp|help)
|
|
1139
1292
|
# dynamic completions would require server calls; skip for now
|
|
1140
1293
|
return 0
|
|
1141
1294
|
;;
|
|
@@ -1157,11 +1310,12 @@ _ucli() {
|
|
|
1157
1310
|
local -a commands
|
|
1158
1311
|
commands=(
|
|
1159
1312
|
'configure:Configure server URL and authentication token'
|
|
1160
|
-
'
|
|
1161
|
-
'
|
|
1313
|
+
'listoas:List all OAS services'
|
|
1314
|
+
'listmcp:List all MCP servers'
|
|
1315
|
+
'oas:Interact with an OAS service'
|
|
1316
|
+
'mcp:Interact with a MCP server'
|
|
1162
1317
|
'refresh:Force-refresh the local OAS cache'
|
|
1163
1318
|
'help:Show usage guide'
|
|
1164
|
-
'mcp:Interact with MCP servers'
|
|
1165
1319
|
'doctor:Check configuration and connectivity'
|
|
1166
1320
|
'completions:Generate shell completion script'
|
|
1167
1321
|
'introspect:Return complete capability manifest for AI agents'
|
|
@@ -1177,11 +1331,15 @@ _ucli() {
|
|
|
1177
1331
|
;;
|
|
1178
1332
|
args)
|
|
1179
1333
|
case $words[1] in
|
|
1180
|
-
|
|
1181
|
-
|
|
1334
|
+
oas)
|
|
1335
|
+
if (( CURRENT == 3 )); then
|
|
1336
|
+
_values 'action' 'info[Show service info]' 'listapi[List API operations]' 'apiinfo[Show API details]' 'invokeapi[Invoke an API]'
|
|
1337
|
+
fi
|
|
1182
1338
|
;;
|
|
1183
1339
|
mcp)
|
|
1184
|
-
|
|
1340
|
+
if (( CURRENT == 3 )); then
|
|
1341
|
+
_values 'action' 'listtool[List tools]' 'toolinfo[Show tool details]' 'invoketool[Invoke a tool]'
|
|
1342
|
+
fi
|
|
1185
1343
|
;;
|
|
1186
1344
|
completions)
|
|
1187
1345
|
_values 'shell' bash zsh fish
|
|
@@ -1199,24 +1357,26 @@ complete -c ucli -e
|
|
|
1199
1357
|
|
|
1200
1358
|
# Top-level commands
|
|
1201
1359
|
complete -c ucli -n __fish_use_subcommand -a configure -d 'Configure server URL and token'
|
|
1202
|
-
complete -c ucli -n __fish_use_subcommand -a
|
|
1203
|
-
complete -c ucli -n __fish_use_subcommand -a
|
|
1360
|
+
complete -c ucli -n __fish_use_subcommand -a listoas -d 'List OAS services'
|
|
1361
|
+
complete -c ucli -n __fish_use_subcommand -a listmcp -d 'List MCP servers'
|
|
1362
|
+
complete -c ucli -n __fish_use_subcommand -a oas -d 'Interact with an OAS service'
|
|
1363
|
+
complete -c ucli -n __fish_use_subcommand -a mcp -d 'Interact with a MCP server'
|
|
1204
1364
|
complete -c ucli -n __fish_use_subcommand -a refresh -d 'Refresh local cache'
|
|
1205
1365
|
complete -c ucli -n __fish_use_subcommand -a help -d 'Show usage guide'
|
|
1206
|
-
complete -c ucli -n __fish_use_subcommand -a mcp -d 'Interact with MCP servers'
|
|
1207
1366
|
complete -c ucli -n __fish_use_subcommand -a doctor -d 'Check config and connectivity'
|
|
1208
1367
|
complete -c ucli -n __fish_use_subcommand -a completions -d 'Generate shell completions'
|
|
1209
|
-
|
|
1210
1368
|
complete -c ucli -n __fish_use_subcommand -a introspect -d 'Return capability manifest for AI agents'
|
|
1211
1369
|
|
|
1212
|
-
#
|
|
1213
|
-
complete -c ucli -n '__fish_seen_subcommand_from
|
|
1214
|
-
complete -c ucli -n '__fish_seen_subcommand_from
|
|
1370
|
+
# oas actions (third argument after server name)
|
|
1371
|
+
complete -c ucli -n '__fish_seen_subcommand_from oas' -a info -d 'Show service info'
|
|
1372
|
+
complete -c ucli -n '__fish_seen_subcommand_from oas' -a listapi -d 'List API operations'
|
|
1373
|
+
complete -c ucli -n '__fish_seen_subcommand_from oas' -a apiinfo -d 'Show API details'
|
|
1374
|
+
complete -c ucli -n '__fish_seen_subcommand_from oas' -a invokeapi -d 'Invoke an API'
|
|
1215
1375
|
|
|
1216
|
-
# mcp
|
|
1217
|
-
complete -c ucli -n '__fish_seen_subcommand_from mcp' -a
|
|
1218
|
-
complete -c ucli -n '__fish_seen_subcommand_from mcp' -a
|
|
1219
|
-
complete -c ucli -n '__fish_seen_subcommand_from mcp' -a
|
|
1376
|
+
# mcp actions (third argument after server name)
|
|
1377
|
+
complete -c ucli -n '__fish_seen_subcommand_from mcp' -a listtool -d 'List tools'
|
|
1378
|
+
complete -c ucli -n '__fish_seen_subcommand_from mcp' -a toolinfo -d 'Show tool details'
|
|
1379
|
+
complete -c ucli -n '__fish_seen_subcommand_from mcp' -a invoketool -d 'Invoke a tool'
|
|
1220
1380
|
|
|
1221
1381
|
# completions subcommands
|
|
1222
1382
|
complete -c ucli -n '__fish_seen_subcommand_from completions' -a 'bash zsh fish' -d 'Shell type'`;
|
|
@@ -1286,60 +1446,89 @@ function toIntrospectMcpServer(e) {
|
|
|
1286
1446
|
function getCommandReference() {
|
|
1287
1447
|
return [
|
|
1288
1448
|
{
|
|
1289
|
-
name: "
|
|
1449
|
+
name: "listoas",
|
|
1290
1450
|
description: "List all OAS services available in the current group",
|
|
1291
|
-
usage: "ucli
|
|
1451
|
+
usage: "ucli listoas [--format json|table|yaml] [--refresh]",
|
|
1452
|
+
examples: [
|
|
1453
|
+
"ucli listoas",
|
|
1454
|
+
"ucli listoas --format json",
|
|
1455
|
+
"ucli listoas --refresh"
|
|
1456
|
+
]
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
name: "oas info",
|
|
1460
|
+
description: "Show detailed information for an OAS service",
|
|
1461
|
+
usage: "ucli oas <service> info [--format json|table|yaml]",
|
|
1462
|
+
examples: [
|
|
1463
|
+
"ucli oas payments info",
|
|
1464
|
+
"ucli oas payments info --format json"
|
|
1465
|
+
]
|
|
1466
|
+
},
|
|
1467
|
+
{
|
|
1468
|
+
name: "oas listapi",
|
|
1469
|
+
description: "List all available API operations for an OAS service",
|
|
1470
|
+
usage: "ucli oas <service> listapi [--format json|table|yaml]",
|
|
1292
1471
|
examples: [
|
|
1293
|
-
"ucli
|
|
1294
|
-
"ucli
|
|
1295
|
-
"ucli services list --refresh"
|
|
1472
|
+
"ucli oas payments listapi",
|
|
1473
|
+
"ucli oas payments listapi --format json"
|
|
1296
1474
|
]
|
|
1297
1475
|
},
|
|
1298
1476
|
{
|
|
1299
|
-
name: "
|
|
1300
|
-
description: "Show detailed
|
|
1301
|
-
usage: "ucli
|
|
1477
|
+
name: "oas apiinfo",
|
|
1478
|
+
description: "Show detailed input/output parameters for a specific API operation",
|
|
1479
|
+
usage: "ucli oas <service> apiinfo <api>",
|
|
1302
1480
|
examples: [
|
|
1303
|
-
"ucli
|
|
1304
|
-
"ucli
|
|
1481
|
+
"ucli oas payments apiinfo createCharge",
|
|
1482
|
+
"ucli oas payments apiinfo getTransaction"
|
|
1305
1483
|
]
|
|
1306
1484
|
},
|
|
1307
1485
|
{
|
|
1308
|
-
name: "
|
|
1309
|
-
description: "Execute an operation on an OAS service",
|
|
1310
|
-
usage: "ucli
|
|
1486
|
+
name: "oas invokeapi",
|
|
1487
|
+
description: "Execute an API operation on an OAS service",
|
|
1488
|
+
usage: "ucli oas <service> invokeapi <api> [--format json|table|yaml] [--query <jmespath>] [--data <json|@file>] [--params <json>] [--machine] [--dry-run]",
|
|
1311
1489
|
examples: [
|
|
1312
|
-
"ucli
|
|
1313
|
-
|
|
1314
|
-
`ucli
|
|
1315
|
-
'ucli
|
|
1490
|
+
"ucli oas payments invokeapi listTransactions",
|
|
1491
|
+
`ucli oas payments invokeapi getTransaction --params '{"transactionId": "txn_123"}'`,
|
|
1492
|
+
`ucli oas payments invokeapi createCharge --data '{"amount": 5000, "currency": "USD"}'`,
|
|
1493
|
+
'ucli oas inventory invokeapi listProducts --query "items[?stock > `0`].name"',
|
|
1494
|
+
`ucli oas payments invokeapi createCharge --dry-run --data '{"amount": 5000}'`
|
|
1316
1495
|
]
|
|
1317
1496
|
},
|
|
1318
1497
|
{
|
|
1319
|
-
name: "
|
|
1498
|
+
name: "listmcp",
|
|
1320
1499
|
description: "List all MCP servers available in the current group",
|
|
1321
|
-
usage: "ucli
|
|
1500
|
+
usage: "ucli listmcp [--format json|table|yaml]",
|
|
1322
1501
|
examples: [
|
|
1323
|
-
"ucli
|
|
1324
|
-
"ucli
|
|
1502
|
+
"ucli listmcp",
|
|
1503
|
+
"ucli listmcp --format json"
|
|
1325
1504
|
]
|
|
1326
1505
|
},
|
|
1327
1506
|
{
|
|
1328
|
-
name: "mcp
|
|
1507
|
+
name: "mcp listtool",
|
|
1329
1508
|
description: "List tools available on a MCP server",
|
|
1330
|
-
usage: "ucli mcp
|
|
1509
|
+
usage: "ucli mcp <server> listtool [--format json|table|yaml]",
|
|
1510
|
+
examples: [
|
|
1511
|
+
"ucli mcp weather listtool",
|
|
1512
|
+
"ucli mcp weather listtool --format json"
|
|
1513
|
+
]
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
name: "mcp toolinfo",
|
|
1517
|
+
description: "Show detailed input/output schema for a tool on a MCP server",
|
|
1518
|
+
usage: "ucli mcp <server> toolinfo <tool> [--json]",
|
|
1331
1519
|
examples: [
|
|
1332
|
-
"ucli mcp
|
|
1333
|
-
"ucli mcp
|
|
1520
|
+
"ucli mcp weather toolinfo get_forecast",
|
|
1521
|
+
"ucli mcp weather toolinfo get_forecast --json"
|
|
1334
1522
|
]
|
|
1335
1523
|
},
|
|
1336
1524
|
{
|
|
1337
|
-
name: "mcp
|
|
1525
|
+
name: "mcp invoketool",
|
|
1338
1526
|
description: "Call a tool on a MCP server",
|
|
1339
|
-
usage: "ucli mcp
|
|
1527
|
+
usage: "ucli mcp <server> invoketool <tool> [--data <json>] [--json]",
|
|
1340
1528
|
examples: [
|
|
1341
|
-
|
|
1342
|
-
|
|
1529
|
+
`ucli mcp weather invoketool get_forecast --data '{"location": "New York"}'`,
|
|
1530
|
+
`ucli mcp search invoketool web_search --data '{"query": "ucli docs", "limit": 5}'`,
|
|
1531
|
+
`ucli mcp weather invoketool get_forecast --json --data '{"location": "New York"}'`
|
|
1343
1532
|
]
|
|
1344
1533
|
},
|
|
1345
1534
|
{
|
|
@@ -1396,10 +1585,11 @@ program.name("ucli").description(pkg.description).version(pkg.version, "-v, --ve
|
|
|
1396
1585
|
}
|
|
1397
1586
|
});
|
|
1398
1587
|
registerConfigure(program);
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1588
|
+
registerListOas(program);
|
|
1589
|
+
registerListMcp(program);
|
|
1590
|
+
registerOas(program);
|
|
1402
1591
|
registerMcp(program);
|
|
1592
|
+
registerRefresh(program);
|
|
1403
1593
|
registerDoctor(program);
|
|
1404
1594
|
registerCompletions(program);
|
|
1405
1595
|
registerIntrospect(program);
|