@tronsfey/ucli 0.5.2 → 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 +102 -89
- package/README.zh.md +80 -92
- package/dist/index.js +629 -502
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skill.md +73 -65
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";
|
|
@@ -423,120 +572,52 @@ async function getServiceHelp(entry) {
|
|
|
423
572
|
child.on("error", reject);
|
|
424
573
|
});
|
|
425
574
|
}
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
return `${prefix}- ${serialised.trimStart()}`;
|
|
453
|
-
}
|
|
454
|
-
const firstLine = serialised.slice(0, firstNewline);
|
|
455
|
-
const rest = serialised.slice(firstNewline + 1);
|
|
456
|
-
return `${prefix}- ${firstLine.trimStart()}
|
|
457
|
-
${rest}`;
|
|
458
|
-
}
|
|
459
|
-
return `${prefix}- ${serialised}`;
|
|
460
|
-
}).join("\n");
|
|
461
|
-
}
|
|
462
|
-
if (typeof value === "object") {
|
|
463
|
-
const entries = Object.entries(value);
|
|
464
|
-
if (entries.length === 0) return "{}";
|
|
465
|
-
const prefix = " ".repeat(indent);
|
|
466
|
-
return entries.map(([key, val]) => {
|
|
467
|
-
if (val === null || val === void 0) {
|
|
468
|
-
return `${prefix}${key}: null`;
|
|
469
|
-
}
|
|
470
|
-
if (typeof val === "object") {
|
|
471
|
-
const nested = toYaml(val, indent + 2);
|
|
472
|
-
return `${prefix}${key}:
|
|
473
|
-
${nested}`;
|
|
474
|
-
}
|
|
475
|
-
return `${prefix}${key}: ${toYaml(val, indent)}`;
|
|
476
|
-
}).join("\n");
|
|
477
|
-
}
|
|
478
|
-
return String(value);
|
|
479
|
-
}
|
|
480
|
-
function needsQuoting(s) {
|
|
481
|
-
if (s === "") return true;
|
|
482
|
-
if (s === "true" || s === "false" || s === "null") return true;
|
|
483
|
-
if (/^\d/.test(s)) return true;
|
|
484
|
-
if (/[:{}\[\],&*#?|<>=!%@`'"\\\n\r\t]/.test(s)) return true;
|
|
485
|
-
if (s.startsWith(" ") || s.endsWith(" ")) return true;
|
|
486
|
-
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
|
+
});
|
|
487
601
|
}
|
|
488
602
|
|
|
489
|
-
// src/commands/
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (!
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
508
|
-
...rest,
|
|
509
|
-
authConfig: { type: authConfig["type"] ?? rest.authType }
|
|
510
|
-
}));
|
|
511
|
-
if (isJsonOutput()) {
|
|
512
|
-
outputSuccess(safe);
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
const format = (opts.format ?? "table").toLowerCase();
|
|
516
|
-
if (entries.length === 0) {
|
|
517
|
-
console.log("No services registered in this group.");
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
if (format === "json") {
|
|
521
|
-
console.log(JSON.stringify(safe, null, 2));
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
if (format === "yaml") {
|
|
525
|
-
console.log(toYaml(safe));
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
|
|
529
|
-
console.log(`
|
|
530
|
-
${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
|
|
531
|
-
console.log(`${"-".repeat(nameWidth)} -------- ${"-".repeat(40)}`);
|
|
532
|
-
for (const e of entries) {
|
|
533
|
-
const auth = e.authType.padEnd(8);
|
|
534
|
-
const desc = e.description.length > 60 ? e.description.slice(0, 57) + "..." : e.description;
|
|
535
|
-
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
|
+
);
|
|
536
620
|
}
|
|
537
|
-
console.log();
|
|
538
|
-
});
|
|
539
|
-
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) => {
|
|
540
621
|
const cfg = getConfig();
|
|
541
622
|
const client = new ServerClient(cfg);
|
|
542
623
|
let entry;
|
|
@@ -546,116 +627,344 @@ ${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
|
|
|
546
627
|
outputError(
|
|
547
628
|
ExitCode.NOT_FOUND,
|
|
548
629
|
`Service not found: ${name}`,
|
|
549
|
-
"Run: ucli
|
|
630
|
+
"Run: ucli listoas to see available services"
|
|
550
631
|
);
|
|
551
632
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
+
}
|
|
562
771
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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);
|
|
567
835
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
+
}
|
|
571
858
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
console.log("\nAvailable operations:");
|
|
580
|
-
console.log("\u2500".repeat(60));
|
|
581
|
-
console.log(help);
|
|
582
|
-
});
|
|
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
|
+
}
|
|
583
866
|
}
|
|
584
867
|
|
|
585
|
-
// src/commands/
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
const service = opts.service ?? serviceArg;
|
|
595
|
-
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)) {
|
|
596
875
|
outputError(
|
|
597
876
|
ExitCode.USAGE_ERROR,
|
|
598
|
-
|
|
599
|
-
|
|
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>`
|
|
600
883
|
);
|
|
601
884
|
}
|
|
602
885
|
const cfg = getConfig();
|
|
603
886
|
const client = new ServerClient(cfg);
|
|
604
887
|
let entry;
|
|
605
888
|
try {
|
|
606
|
-
entry = await client.
|
|
889
|
+
entry = await client.getMCP(name);
|
|
607
890
|
} catch {
|
|
608
891
|
outputError(
|
|
609
892
|
ExitCode.NOT_FOUND,
|
|
610
|
-
`Unknown
|
|
611
|
-
"Run: ucli
|
|
893
|
+
`Unknown MCP server: ${name}`,
|
|
894
|
+
"Run: ucli listmcp to see available servers"
|
|
612
895
|
);
|
|
613
896
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
`
|
|
628
|
-
|
|
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;
|
|
629
931
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
+
);
|
|
635
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}`);
|
|
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;
|
|
636
967
|
}
|
|
637
|
-
}
|
|
638
|
-
if (opts.data) {
|
|
639
|
-
operationArgs.push("--data", opts.data);
|
|
640
|
-
}
|
|
641
|
-
const format = opts.format;
|
|
642
|
-
const query = opts.query;
|
|
643
|
-
const machine = opts.machine ?? false;
|
|
644
|
-
const dryRun = opts.dryRun ?? false;
|
|
645
|
-
try {
|
|
646
|
-
await runOperation({
|
|
647
|
-
entry,
|
|
648
|
-
operationArgs,
|
|
649
|
-
...format !== void 0 ? { format } : {},
|
|
650
|
-
...query !== void 0 ? { query } : {},
|
|
651
|
-
...machine ? { machine } : {},
|
|
652
|
-
...dryRun ? { dryRun } : {}
|
|
653
|
-
});
|
|
654
|
-
} catch (err) {
|
|
655
|
-
outputError(
|
|
656
|
-
ExitCode.GENERAL_ERROR,
|
|
657
|
-
`Operation failed: ${err.message}`
|
|
658
|
-
);
|
|
659
968
|
}
|
|
660
969
|
});
|
|
661
970
|
}
|
|
@@ -707,7 +1016,7 @@ function registerHelp(program2) {
|
|
|
707
1016
|
for (const e of entries) {
|
|
708
1017
|
console.log(` ${e.name.padEnd(20)} ${e.description}`);
|
|
709
1018
|
}
|
|
710
|
-
console.log("\nTip: Run `ucli
|
|
1019
|
+
console.log("\nTip: Run `ucli oas <service> listapi` for service-specific operations.");
|
|
711
1020
|
}
|
|
712
1021
|
} else {
|
|
713
1022
|
console.log("\nRun `ucli configure --server <url> --token <jwt>` to get started.");
|
|
@@ -721,7 +1030,7 @@ function registerHelp(program2) {
|
|
|
721
1030
|
entry = await client.getOAS(service);
|
|
722
1031
|
} catch {
|
|
723
1032
|
console.error(`Unknown service: ${service}`);
|
|
724
|
-
console.error("Run `ucli
|
|
1033
|
+
console.error("Run `ucli listoas` to see available services.");
|
|
725
1034
|
process.exit(ExitCode.NOT_FOUND);
|
|
726
1035
|
}
|
|
727
1036
|
console.log(`
|
|
@@ -734,10 +1043,9 @@ OAS spec: ${entry.remoteUrl}`);
|
|
|
734
1043
|
const help = await getServiceHelp(entry);
|
|
735
1044
|
console.log(help);
|
|
736
1045
|
console.log("\nExamples:");
|
|
737
|
-
console.log(` ucli
|
|
738
|
-
console.log(` ucli
|
|
739
|
-
console.log(` ucli
|
|
740
|
-
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"}'`);
|
|
741
1049
|
});
|
|
742
1050
|
}
|
|
743
1051
|
function printGeneralHelp() {
|
|
@@ -750,11 +1058,11 @@ SETUP
|
|
|
750
1058
|
Configure server connection and authentication.
|
|
751
1059
|
|
|
752
1060
|
DISCOVERY
|
|
753
|
-
ucli
|
|
1061
|
+
ucli listoas
|
|
754
1062
|
List all OAS services available in your group.
|
|
755
1063
|
|
|
756
|
-
ucli
|
|
757
|
-
|
|
1064
|
+
ucli listmcp
|
|
1065
|
+
List all MCP servers available in your group.
|
|
758
1066
|
|
|
759
1067
|
ucli introspect
|
|
760
1068
|
Return complete capability manifest in a single call (JSON).
|
|
@@ -763,9 +1071,18 @@ DISCOVERY
|
|
|
763
1071
|
ucli help [service]
|
|
764
1072
|
Show this guide, or service-specific operations.
|
|
765
1073
|
|
|
766
|
-
|
|
767
|
-
ucli
|
|
768
|
-
|
|
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.
|
|
769
1086
|
|
|
770
1087
|
Options:
|
|
771
1088
|
--format json|table|yaml Output format (default: json)
|
|
@@ -773,13 +1090,13 @@ EXECUTION
|
|
|
773
1090
|
--data <json|@file> Request body for POST/PUT/PATCH
|
|
774
1091
|
|
|
775
1092
|
MCP SERVERS
|
|
776
|
-
ucli mcp
|
|
777
|
-
List all MCP servers in your group.
|
|
778
|
-
|
|
779
|
-
ucli mcp tools <server>
|
|
1093
|
+
ucli mcp <server> listtool
|
|
780
1094
|
List tools available on a MCP server.
|
|
781
1095
|
|
|
782
|
-
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>
|
|
783
1100
|
Call a tool on a MCP server.
|
|
784
1101
|
|
|
785
1102
|
MAINTENANCE
|
|
@@ -802,236 +1119,18 @@ GLOBAL FLAGS
|
|
|
802
1119
|
|
|
803
1120
|
ERRORS
|
|
804
1121
|
401 Unauthorized \u2192 Run: ucli configure --server <url> --token <jwt>
|
|
805
|
-
404 Not Found \u2192 Check service name: ucli
|
|
806
|
-
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
|
|
807
1124
|
5xx Server Error \u2192 Retry or run: ucli refresh
|
|
808
1125
|
|
|
809
1126
|
AI AGENT QUICK START
|
|
810
1127
|
1. ucli introspect # discover everything in one call
|
|
811
|
-
2. ucli
|
|
812
|
-
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
|
|
813
1131
|
`);
|
|
814
1132
|
}
|
|
815
1133
|
|
|
816
|
-
// src/lib/mcp-runner.ts
|
|
817
|
-
function resolve(mod, name) {
|
|
818
|
-
if (typeof mod[name] === "function") return mod[name];
|
|
819
|
-
if (mod.default && typeof mod.default[name] === "function") return mod.default[name];
|
|
820
|
-
throw new Error(`Cannot resolve export "${name}" from module`);
|
|
821
|
-
}
|
|
822
|
-
async function getMcp2cli() {
|
|
823
|
-
const clientMod = await import("./client-Y6NONDCI.js");
|
|
824
|
-
const runnerMod = await import("./runner-ROLDMGH3.js");
|
|
825
|
-
return {
|
|
826
|
-
createMcpClient: resolve(clientMod, "createMcpClient"),
|
|
827
|
-
getTools: resolve(runnerMod, "getTools"),
|
|
828
|
-
runTool: resolve(runnerMod, "runTool"),
|
|
829
|
-
describeTool: resolve(runnerMod, "describeTool"),
|
|
830
|
-
describeToolJson: resolve(runnerMod, "describeToolJson")
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
async function closeClient(client) {
|
|
834
|
-
if (typeof client.close === "function") {
|
|
835
|
-
await client.close();
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
function buildMcpConfig(entry) {
|
|
839
|
-
const base = { type: entry.transport };
|
|
840
|
-
if (entry.transport === "http") {
|
|
841
|
-
base.url = entry.serverUrl;
|
|
842
|
-
} else {
|
|
843
|
-
base.command = entry.command;
|
|
844
|
-
}
|
|
845
|
-
const auth = entry.authConfig;
|
|
846
|
-
if (auth.type === "http_headers") {
|
|
847
|
-
base.headers = auth.headers;
|
|
848
|
-
} else if (auth.type === "env") {
|
|
849
|
-
base.env = auth.env;
|
|
850
|
-
}
|
|
851
|
-
return base;
|
|
852
|
-
}
|
|
853
|
-
async function listMcpTools(entry) {
|
|
854
|
-
const { createMcpClient, getTools } = await getMcp2cli();
|
|
855
|
-
const config = buildMcpConfig(entry);
|
|
856
|
-
const client = await createMcpClient(config);
|
|
857
|
-
try {
|
|
858
|
-
const tools = await getTools(client, config, { noCache: true });
|
|
859
|
-
return tools;
|
|
860
|
-
} finally {
|
|
861
|
-
await closeClient(client);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
async function describeMcpTool(entry, toolName, opts) {
|
|
865
|
-
const { createMcpClient, getTools, describeTool, describeToolJson } = await getMcp2cli();
|
|
866
|
-
const config = buildMcpConfig(entry);
|
|
867
|
-
const client = await createMcpClient(config);
|
|
868
|
-
try {
|
|
869
|
-
const tools = await getTools(client, config, { noCache: false, cacheTtl: 3600 });
|
|
870
|
-
const tool = tools.find((t) => t.name === toolName);
|
|
871
|
-
if (!tool) throw new Error(`Tool "${toolName}" not found on MCP server "${entry.name}"`);
|
|
872
|
-
if (opts?.json) {
|
|
873
|
-
describeToolJson(tool);
|
|
874
|
-
} else {
|
|
875
|
-
describeTool(tool);
|
|
876
|
-
}
|
|
877
|
-
} finally {
|
|
878
|
-
await closeClient(client);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
async function runMcpTool(entry, toolName, rawArgs, opts) {
|
|
882
|
-
const { createMcpClient, getTools, runTool } = await getMcp2cli();
|
|
883
|
-
const config = buildMcpConfig(entry);
|
|
884
|
-
const client = await createMcpClient(config);
|
|
885
|
-
try {
|
|
886
|
-
const tools = await getTools(client, config, { noCache: false, cacheTtl: 3600 });
|
|
887
|
-
const tool = tools.find((t) => t.name === toolName);
|
|
888
|
-
if (!tool) throw new Error(`Tool "${toolName}" not found on MCP server "${entry.name}"`);
|
|
889
|
-
const normalizedArgs = [];
|
|
890
|
-
for (const arg of rawArgs) {
|
|
891
|
-
if (arg.includes("=") && !arg.startsWith("--")) {
|
|
892
|
-
const idx = arg.indexOf("=");
|
|
893
|
-
const key = arg.slice(0, idx);
|
|
894
|
-
const value = arg.slice(idx + 1);
|
|
895
|
-
normalizedArgs.push(`--${key}`, value);
|
|
896
|
-
} else {
|
|
897
|
-
normalizedArgs.push(arg);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
await runTool(client, tool, normalizedArgs, {
|
|
901
|
-
...opts?.json ? { json: true } : {},
|
|
902
|
-
...opts?.inputJson ? { inputJson: opts.inputJson } : {}
|
|
903
|
-
});
|
|
904
|
-
} finally {
|
|
905
|
-
await closeClient(client);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// src/commands/mcp.ts
|
|
910
|
-
function registerMcp(program2) {
|
|
911
|
-
const mcp = program2.command("mcp").description("Interact with MCP servers registered in your group");
|
|
912
|
-
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) => {
|
|
913
|
-
const cfg = getConfig();
|
|
914
|
-
const client = new ServerClient(cfg);
|
|
915
|
-
const entries = await client.listMCP();
|
|
916
|
-
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
917
|
-
...rest,
|
|
918
|
-
authConfig: { type: authConfig.type }
|
|
919
|
-
}));
|
|
920
|
-
if (isJsonOutput()) {
|
|
921
|
-
outputSuccess(safe);
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
if (entries.length === 0) {
|
|
925
|
-
console.log("No MCP servers registered in this group.");
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
const format = (opts.format ?? "table").toLowerCase();
|
|
929
|
-
if (format === "json") {
|
|
930
|
-
console.log(JSON.stringify(safe, null, 2));
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
if (format === "yaml") {
|
|
934
|
-
console.log(toYaml(safe));
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
|
|
938
|
-
console.log(`
|
|
939
|
-
${"SERVER".padEnd(nameWidth)} TRANSPORT DESCRIPTION`);
|
|
940
|
-
console.log(`${"-".repeat(nameWidth)} --------- ${"-".repeat(40)}`);
|
|
941
|
-
for (const e of entries) {
|
|
942
|
-
const desc = e.description.length > 60 ? e.description.slice(0, 57) + "..." : e.description;
|
|
943
|
-
console.log(`${e.name.padEnd(nameWidth)} ${e.transport.padEnd(9)} ${desc}`);
|
|
944
|
-
}
|
|
945
|
-
console.log();
|
|
946
|
-
});
|
|
947
|
-
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) => {
|
|
948
|
-
const cfg = getConfig();
|
|
949
|
-
const client = new ServerClient(cfg);
|
|
950
|
-
let entry;
|
|
951
|
-
try {
|
|
952
|
-
entry = await client.getMCP(serverName);
|
|
953
|
-
} catch {
|
|
954
|
-
outputError(
|
|
955
|
-
ExitCode.NOT_FOUND,
|
|
956
|
-
`Unknown MCP server: ${serverName}`,
|
|
957
|
-
"Run: ucli mcp list to see available servers"
|
|
958
|
-
);
|
|
959
|
-
}
|
|
960
|
-
let tools;
|
|
961
|
-
try {
|
|
962
|
-
tools = await listMcpTools(entry);
|
|
963
|
-
} catch (err) {
|
|
964
|
-
outputError(ExitCode.GENERAL_ERROR, `Failed to fetch tools: ${err.message}`);
|
|
965
|
-
}
|
|
966
|
-
if (isJsonOutput()) {
|
|
967
|
-
outputSuccess(tools);
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
if (tools.length === 0) {
|
|
971
|
-
console.log(`No tools found on MCP server "${serverName}".`);
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
const format = (opts.format ?? "table").toLowerCase();
|
|
975
|
-
if (format === "json") {
|
|
976
|
-
console.log(JSON.stringify(tools, null, 2));
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
if (format === "yaml") {
|
|
980
|
-
console.log(toYaml(tools));
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
console.log(`
|
|
984
|
-
Tools on "${serverName}":`);
|
|
985
|
-
console.log("\u2500".repeat(60));
|
|
986
|
-
for (const t of tools) {
|
|
987
|
-
console.log(` ${t.name}`);
|
|
988
|
-
if (t.description) console.log(` ${t.description}`);
|
|
989
|
-
}
|
|
990
|
-
console.log();
|
|
991
|
-
});
|
|
992
|
-
mcp.command("describe <server> <tool>").description("Show detailed schema for a tool on a MCP server").option("--json", "Output full schema as JSON (for agent consumption)").action(async (serverName, toolName, opts) => {
|
|
993
|
-
const cfg = getConfig();
|
|
994
|
-
const client = new ServerClient(cfg);
|
|
995
|
-
let entry;
|
|
996
|
-
try {
|
|
997
|
-
entry = await client.getMCP(serverName);
|
|
998
|
-
} catch {
|
|
999
|
-
outputError(
|
|
1000
|
-
ExitCode.NOT_FOUND,
|
|
1001
|
-
`Unknown MCP server: ${serverName}`,
|
|
1002
|
-
"Run: ucli mcp list to see available servers"
|
|
1003
|
-
);
|
|
1004
|
-
}
|
|
1005
|
-
try {
|
|
1006
|
-
await describeMcpTool(entry, toolName, { json: opts.json });
|
|
1007
|
-
} catch (err) {
|
|
1008
|
-
outputError(ExitCode.GENERAL_ERROR, `Failed to describe tool: ${err.message}`);
|
|
1009
|
-
}
|
|
1010
|
-
});
|
|
1011
|
-
mcp.command("run <server> <tool> [args...]").description("Call a tool on a MCP server").option("--json", "Machine-readable JSON output").option("--input-json <json>", "Pass tool arguments as a JSON object").action(async (serverName, toolName, args, opts) => {
|
|
1012
|
-
const cfg = getConfig();
|
|
1013
|
-
const client = new ServerClient(cfg);
|
|
1014
|
-
let entry;
|
|
1015
|
-
try {
|
|
1016
|
-
entry = await client.getMCP(serverName);
|
|
1017
|
-
} catch {
|
|
1018
|
-
outputError(
|
|
1019
|
-
ExitCode.NOT_FOUND,
|
|
1020
|
-
`Unknown MCP server: ${serverName}`,
|
|
1021
|
-
"Run: ucli mcp list to see available servers"
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
try {
|
|
1025
|
-
await runMcpTool(entry, toolName, args, {
|
|
1026
|
-
json: opts.json,
|
|
1027
|
-
inputJson: opts.inputJson
|
|
1028
|
-
});
|
|
1029
|
-
} catch (err) {
|
|
1030
|
-
outputError(ExitCode.GENERAL_ERROR, `Tool execution failed: ${err.message}`);
|
|
1031
|
-
}
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
1134
|
// src/lib/errors.ts
|
|
1036
1135
|
var debugEnabled = false;
|
|
1037
1136
|
function setDebugMode(enabled) {
|
|
@@ -1046,7 +1145,7 @@ var HINT_MAP = {
|
|
|
1046
1145
|
[ExitCode.CONFIG_ERROR]: "Run: ucli configure --server <url> --token <jwt>",
|
|
1047
1146
|
[ExitCode.AUTH_ERROR]: "Your token may be expired or revoked. Run: ucli configure --server <url> --token <jwt>",
|
|
1048
1147
|
[ExitCode.CONNECTIVITY_ERROR]: "Check that the server URL is correct and the server is running. Run: ucli doctor",
|
|
1049
|
-
[ExitCode.NOT_FOUND]: "Check the resource name. Run: ucli
|
|
1148
|
+
[ExitCode.NOT_FOUND]: "Check the resource name. Run: ucli listoas or ucli listmcp",
|
|
1050
1149
|
[ExitCode.SERVER_ERROR]: "The server returned an unexpected error. Try again or run: ucli doctor"
|
|
1051
1150
|
};
|
|
1052
1151
|
|
|
@@ -1170,22 +1269,26 @@ _ucli_completions() {
|
|
|
1170
1269
|
COMPREPLY=()
|
|
1171
1270
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
1172
1271
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
1173
|
-
commands="configure
|
|
1272
|
+
commands="configure listoas listmcp oas mcp refresh help doctor completions introspect"
|
|
1174
1273
|
|
|
1175
1274
|
case "\${COMP_WORDS[1]}" in
|
|
1176
|
-
|
|
1177
|
-
|
|
1275
|
+
oas)
|
|
1276
|
+
if [ "$COMP_CWORD" -eq 3 ]; then
|
|
1277
|
+
COMPREPLY=( $(compgen -W "info listapi apiinfo invokeapi" -- "$cur") )
|
|
1278
|
+
fi
|
|
1178
1279
|
return 0
|
|
1179
1280
|
;;
|
|
1180
1281
|
mcp)
|
|
1181
|
-
|
|
1282
|
+
if [ "$COMP_CWORD" -eq 3 ]; then
|
|
1283
|
+
COMPREPLY=( $(compgen -W "listtool toolinfo invoketool" -- "$cur") )
|
|
1284
|
+
fi
|
|
1182
1285
|
return 0
|
|
1183
1286
|
;;
|
|
1184
1287
|
completions)
|
|
1185
1288
|
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
|
|
1186
1289
|
return 0
|
|
1187
1290
|
;;
|
|
1188
|
-
|
|
1291
|
+
listoas|listmcp|help)
|
|
1189
1292
|
# dynamic completions would require server calls; skip for now
|
|
1190
1293
|
return 0
|
|
1191
1294
|
;;
|
|
@@ -1207,11 +1310,12 @@ _ucli() {
|
|
|
1207
1310
|
local -a commands
|
|
1208
1311
|
commands=(
|
|
1209
1312
|
'configure:Configure server URL and authentication token'
|
|
1210
|
-
'
|
|
1211
|
-
'
|
|
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'
|
|
1212
1317
|
'refresh:Force-refresh the local OAS cache'
|
|
1213
1318
|
'help:Show usage guide'
|
|
1214
|
-
'mcp:Interact with MCP servers'
|
|
1215
1319
|
'doctor:Check configuration and connectivity'
|
|
1216
1320
|
'completions:Generate shell completion script'
|
|
1217
1321
|
'introspect:Return complete capability manifest for AI agents'
|
|
@@ -1227,11 +1331,15 @@ _ucli() {
|
|
|
1227
1331
|
;;
|
|
1228
1332
|
args)
|
|
1229
1333
|
case $words[1] in
|
|
1230
|
-
|
|
1231
|
-
|
|
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
|
|
1232
1338
|
;;
|
|
1233
1339
|
mcp)
|
|
1234
|
-
|
|
1340
|
+
if (( CURRENT == 3 )); then
|
|
1341
|
+
_values 'action' 'listtool[List tools]' 'toolinfo[Show tool details]' 'invoketool[Invoke a tool]'
|
|
1342
|
+
fi
|
|
1235
1343
|
;;
|
|
1236
1344
|
completions)
|
|
1237
1345
|
_values 'shell' bash zsh fish
|
|
@@ -1249,24 +1357,26 @@ complete -c ucli -e
|
|
|
1249
1357
|
|
|
1250
1358
|
# Top-level commands
|
|
1251
1359
|
complete -c ucli -n __fish_use_subcommand -a configure -d 'Configure server URL and token'
|
|
1252
|
-
complete -c ucli -n __fish_use_subcommand -a
|
|
1253
|
-
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'
|
|
1254
1364
|
complete -c ucli -n __fish_use_subcommand -a refresh -d 'Refresh local cache'
|
|
1255
1365
|
complete -c ucli -n __fish_use_subcommand -a help -d 'Show usage guide'
|
|
1256
|
-
complete -c ucli -n __fish_use_subcommand -a mcp -d 'Interact with MCP servers'
|
|
1257
1366
|
complete -c ucli -n __fish_use_subcommand -a doctor -d 'Check config and connectivity'
|
|
1258
1367
|
complete -c ucli -n __fish_use_subcommand -a completions -d 'Generate shell completions'
|
|
1259
|
-
|
|
1260
1368
|
complete -c ucli -n __fish_use_subcommand -a introspect -d 'Return capability manifest for AI agents'
|
|
1261
1369
|
|
|
1262
|
-
#
|
|
1263
|
-
complete -c ucli -n '__fish_seen_subcommand_from
|
|
1264
|
-
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'
|
|
1265
1375
|
|
|
1266
|
-
# mcp
|
|
1267
|
-
complete -c ucli -n '__fish_seen_subcommand_from mcp' -a
|
|
1268
|
-
complete -c ucli -n '__fish_seen_subcommand_from mcp' -a
|
|
1269
|
-
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'
|
|
1270
1380
|
|
|
1271
1381
|
# completions subcommands
|
|
1272
1382
|
complete -c ucli -n '__fish_seen_subcommand_from completions' -a 'bash zsh fish' -d 'Shell type'`;
|
|
@@ -1336,73 +1446,89 @@ function toIntrospectMcpServer(e) {
|
|
|
1336
1446
|
function getCommandReference() {
|
|
1337
1447
|
return [
|
|
1338
1448
|
{
|
|
1339
|
-
name: "
|
|
1449
|
+
name: "listoas",
|
|
1340
1450
|
description: "List all OAS services available in the current group",
|
|
1341
|
-
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]",
|
|
1342
1471
|
examples: [
|
|
1343
|
-
"ucli
|
|
1344
|
-
"ucli
|
|
1345
|
-
"ucli services list --refresh"
|
|
1472
|
+
"ucli oas payments listapi",
|
|
1473
|
+
"ucli oas payments listapi --format json"
|
|
1346
1474
|
]
|
|
1347
1475
|
},
|
|
1348
1476
|
{
|
|
1349
|
-
name: "
|
|
1350
|
-
description: "Show detailed
|
|
1351
|
-
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>",
|
|
1352
1480
|
examples: [
|
|
1353
|
-
"ucli
|
|
1354
|
-
"ucli
|
|
1481
|
+
"ucli oas payments apiinfo createCharge",
|
|
1482
|
+
"ucli oas payments apiinfo getTransaction"
|
|
1355
1483
|
]
|
|
1356
1484
|
},
|
|
1357
1485
|
{
|
|
1358
|
-
name: "
|
|
1359
|
-
description: "Execute an operation on an OAS service",
|
|
1360
|
-
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]",
|
|
1361
1489
|
examples: [
|
|
1362
|
-
"ucli
|
|
1363
|
-
|
|
1364
|
-
`ucli
|
|
1365
|
-
'ucli
|
|
1366
|
-
|
|
1367
|
-
`ucli run payments createCharge --dry-run --data '{"amount": 5000}'`
|
|
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}'`
|
|
1368
1495
|
]
|
|
1369
1496
|
},
|
|
1370
1497
|
{
|
|
1371
|
-
name: "
|
|
1498
|
+
name: "listmcp",
|
|
1372
1499
|
description: "List all MCP servers available in the current group",
|
|
1373
|
-
usage: "ucli
|
|
1500
|
+
usage: "ucli listmcp [--format json|table|yaml]",
|
|
1374
1501
|
examples: [
|
|
1375
|
-
"ucli
|
|
1376
|
-
"ucli
|
|
1502
|
+
"ucli listmcp",
|
|
1503
|
+
"ucli listmcp --format json"
|
|
1377
1504
|
]
|
|
1378
1505
|
},
|
|
1379
1506
|
{
|
|
1380
|
-
name: "mcp
|
|
1507
|
+
name: "mcp listtool",
|
|
1381
1508
|
description: "List tools available on a MCP server",
|
|
1382
|
-
usage: "ucli mcp
|
|
1509
|
+
usage: "ucli mcp <server> listtool [--format json|table|yaml]",
|
|
1383
1510
|
examples: [
|
|
1384
|
-
"ucli mcp
|
|
1385
|
-
"ucli mcp
|
|
1511
|
+
"ucli mcp weather listtool",
|
|
1512
|
+
"ucli mcp weather listtool --format json"
|
|
1386
1513
|
]
|
|
1387
1514
|
},
|
|
1388
1515
|
{
|
|
1389
|
-
name: "mcp
|
|
1390
|
-
description: "Show detailed schema for a tool on a MCP server",
|
|
1391
|
-
usage: "ucli mcp
|
|
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]",
|
|
1392
1519
|
examples: [
|
|
1393
|
-
"ucli mcp
|
|
1394
|
-
"ucli mcp
|
|
1520
|
+
"ucli mcp weather toolinfo get_forecast",
|
|
1521
|
+
"ucli mcp weather toolinfo get_forecast --json"
|
|
1395
1522
|
]
|
|
1396
1523
|
},
|
|
1397
1524
|
{
|
|
1398
|
-
name: "mcp
|
|
1525
|
+
name: "mcp invoketool",
|
|
1399
1526
|
description: "Call a tool on a MCP server",
|
|
1400
|
-
usage: "ucli mcp
|
|
1527
|
+
usage: "ucli mcp <server> invoketool <tool> [--data <json>] [--json]",
|
|
1401
1528
|
examples: [
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
`ucli mcp
|
|
1405
|
-
'ucli mcp run weather get_forecast --json location="New York"'
|
|
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"}'`
|
|
1406
1532
|
]
|
|
1407
1533
|
},
|
|
1408
1534
|
{
|
|
@@ -1459,10 +1585,11 @@ program.name("ucli").description(pkg.description).version(pkg.version, "-v, --ve
|
|
|
1459
1585
|
}
|
|
1460
1586
|
});
|
|
1461
1587
|
registerConfigure(program);
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1588
|
+
registerListOas(program);
|
|
1589
|
+
registerListMcp(program);
|
|
1590
|
+
registerOas(program);
|
|
1465
1591
|
registerMcp(program);
|
|
1592
|
+
registerRefresh(program);
|
|
1466
1593
|
registerDoctor(program);
|
|
1467
1594
|
registerCompletions(program);
|
|
1468
1595
|
registerIntrospect(program);
|