@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/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
- // src/lib/yaml.ts
428
- function toYaml(value, indent = 0) {
429
- if (value === null || value === void 0) {
430
- return "null";
431
- }
432
- if (typeof value === "boolean") {
433
- return value ? "true" : "false";
434
- }
435
- if (typeof value === "number") {
436
- return String(value);
437
- }
438
- if (typeof value === "string") {
439
- if (needsQuoting(value)) {
440
- return JSON.stringify(value);
441
- }
442
- return value;
443
- }
444
- if (Array.isArray(value)) {
445
- if (value.length === 0) return "[]";
446
- const prefix = " ".repeat(indent);
447
- return value.map((item) => {
448
- const serialised = toYaml(item, indent + 2);
449
- if (typeof item === "object" && item !== null && !Array.isArray(item)) {
450
- const firstNewline = serialised.indexOf("\n");
451
- if (firstNewline === -1) {
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/services.ts
490
- function registerServices(program2) {
491
- const services = program2.command("services").description("Manage and inspect available OAS services");
492
- services.command("list").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").option("--no-cache", "Bypass local cache and fetch fresh from server").action(async (opts) => {
493
- const cfg = getConfig();
494
- const client = new ServerClient(cfg);
495
- if (!opts.cache) {
496
- process.emitWarning("The --no-cache flag is deprecated. Please use --refresh instead.");
497
- }
498
- const useCache = opts.cache && !opts.refresh;
499
- let entries = useCache ? await readOASListCache() : null;
500
- if (!entries) {
501
- entries = await client.listOAS();
502
- if (entries.length > 0) {
503
- const maxTtl = Math.min(...entries.map((e) => e.cacheTtl));
504
- await writeOASListCache(entries, maxTtl);
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 services list to see available services"
630
+ "Run: ucli listoas to see available services"
550
631
  );
551
632
  }
552
- const help = await getServiceHelp(entry);
553
- const { authConfig, ...rest } = entry;
554
- const safe = {
555
- ...rest,
556
- authConfig: { type: authConfig["type"] ?? rest.authType },
557
- operationsHelp: help
558
- };
559
- if (isJsonOutput()) {
560
- outputSuccess(safe);
561
- return;
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
- const format = (opts.format ?? "table").toLowerCase();
564
- if (format === "json") {
565
- console.log(JSON.stringify(safe, null, 2));
566
- return;
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
- if (format === "yaml") {
569
- console.log(toYaml(safe));
570
- return;
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
- console.log(`
573
- Service: ${entry.name}`);
574
- console.log(`Description: ${entry.description || "(none)"}`);
575
- console.log(`OAS URL: ${entry.remoteUrl}`);
576
- if (entry.baseEndpoint) console.log(`Base endpoint: ${entry.baseEndpoint}`);
577
- console.log(`Auth type: ${entry.authType}`);
578
- console.log(`Cache TTL: ${entry.cacheTtl}s`);
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/run.ts
586
- function registerRun(program2) {
587
- program2.command("run [service] [args...]").description("Execute an operation on a service").option("--service <name>", "Service name (from `services list`)").option("--operation <id>", "OperationId to execute").option("--params <json>", "JSON string of operation parameters").option("--format <fmt>", "Output format: json | table | yaml", "json").option("--query <jmespath>", "Filter response with JMESPath expression").option("--data <json>", "Request body (JSON string or @filename)").option("--machine", "Agent-friendly mode: structured JSON envelope output").option("--dry-run", "Preview the HTTP request without executing (implies --machine)").allowUnknownOption(true).action(async (serviceArg, args, opts) => {
588
- if (serviceArg && opts.service && serviceArg !== opts.service) {
589
- outputError(
590
- ExitCode.USAGE_ERROR,
591
- `Conflicting service values: positional "${serviceArg}" and --service "${opts.service}". Use either the positional argument or --service flag, not both.`
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
- "Missing service name. Use positional <service> or --service <name>.",
599
- "Run: ucli services list to see available services"
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.getOAS(service);
889
+ entry = await client.getMCP(name);
607
890
  } catch {
608
891
  outputError(
609
892
  ExitCode.NOT_FOUND,
610
- `Unknown service: ${service}`,
611
- "Run: ucli services list to see available services"
893
+ `Unknown MCP server: ${name}`,
894
+ "Run: ucli listmcp to see available servers"
612
895
  );
613
896
  }
614
- const extraArgs = opts.args ?? [];
615
- const operationArgs = [...args, ...extraArgs];
616
- if (opts.operation) {
617
- operationArgs.unshift(opts.operation);
618
- }
619
- if (opts.params) {
620
- let parsed;
621
- try {
622
- parsed = JSON.parse(opts.params);
623
- } catch {
624
- outputError(
625
- ExitCode.USAGE_ERROR,
626
- "Invalid --params JSON.",
627
- `Example: --params '{"petId": 1}'`
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
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
631
- for (const [k, v] of Object.entries(parsed)) {
632
- if (v === void 0 || v === null) continue;
633
- const strVal = typeof v === "object" ? JSON.stringify(v) : String(v);
634
- operationArgs.push(`--${k}`, strVal);
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 help <service>` for service-specific operations.");
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 services list` to see available services.");
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 run ${entry.name} <operation>`);
738
- console.log(` ucli run ${entry.name} <operation> --format table`);
739
- console.log(` ucli run ${entry.name} <operation> --query "results[*].id"`);
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 services list
1061
+ ucli listoas
754
1062
  List all OAS services available in your group.
755
1063
 
756
- ucli services info <service>
757
- Show detailed service info and all available operations.
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
- EXECUTION
767
- ucli run <service> <operation> [options]
768
- Execute a service operation.
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 list
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 run <server> <tool> [key=value ...]
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 services list
806
- 4xx Client Error \u2192 Check operation args: ucli services info <service>
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 run <svc> <op> [args] # execute operations
812
- 3. Use --output json globally # get structured { success, data/error } envelopes
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 services list or ucli mcp list",
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 services run refresh help mcp doctor completions introspect"
1272
+ commands="configure listoas listmcp oas mcp refresh help doctor completions introspect"
1174
1273
 
1175
1274
  case "\${COMP_WORDS[1]}" in
1176
- services)
1177
- COMPREPLY=( $(compgen -W "list info" -- "$cur") )
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
- COMPREPLY=( $(compgen -W "list tools run" -- "$cur") )
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
- run|help)
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
- 'services:Manage and inspect available OAS services'
1211
- 'run:Execute an operation on a service'
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
- services)
1231
- _values 'subcommand' 'list[List all OAS services]' 'info[Show service details]'
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
- _values 'subcommand' 'list[List MCP servers]' 'tools[List tools]' 'run[Call a tool]'
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 services -d 'Manage OAS services'
1253
- complete -c ucli -n __fish_use_subcommand -a run -d 'Execute a service operation'
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
- # services subcommands
1263
- complete -c ucli -n '__fish_seen_subcommand_from services' -a list -d 'List services'
1264
- complete -c ucli -n '__fish_seen_subcommand_from services' -a info -d 'Show service details'
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 subcommands
1267
- complete -c ucli -n '__fish_seen_subcommand_from mcp' -a list -d 'List MCP servers'
1268
- complete -c ucli -n '__fish_seen_subcommand_from mcp' -a tools -d 'List tools'
1269
- complete -c ucli -n '__fish_seen_subcommand_from mcp' -a run -d 'Call a tool'
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: "services list",
1449
+ name: "listoas",
1340
1450
  description: "List all OAS services available in the current group",
1341
- usage: "ucli services list [--format json|table|yaml] [--refresh]",
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 services list",
1344
- "ucli services list --format json",
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: "services info",
1350
- description: "Show detailed information and available operations for a service",
1351
- usage: "ucli services info <name> [--format json|table|yaml]",
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 services info payments",
1354
- "ucli services info payments --format json"
1481
+ "ucli oas payments apiinfo createCharge",
1482
+ "ucli oas payments apiinfo getTransaction"
1355
1483
  ]
1356
1484
  },
1357
1485
  {
1358
- name: "run",
1359
- description: "Execute an operation on an OAS service",
1360
- usage: "ucli run <service> <operation> [--format json|table|yaml] [--query <jmespath>] [--data <json|@file>] [--params <json>] [--machine] [--dry-run]",
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 run payments listTransactions",
1363
- "ucli run payments getTransaction --transactionId txn_123",
1364
- `ucli run payments createCharge --data '{"amount": 5000, "currency": "USD"}'`,
1365
- 'ucli run inventory listProducts --query "items[?stock > `0`].name"',
1366
- "ucli run payments listTransactions --machine",
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: "mcp list",
1498
+ name: "listmcp",
1372
1499
  description: "List all MCP servers available in the current group",
1373
- usage: "ucli mcp list [--format json|table|yaml]",
1500
+ usage: "ucli listmcp [--format json|table|yaml]",
1374
1501
  examples: [
1375
- "ucli mcp list",
1376
- "ucli mcp list --format json"
1502
+ "ucli listmcp",
1503
+ "ucli listmcp --format json"
1377
1504
  ]
1378
1505
  },
1379
1506
  {
1380
- name: "mcp tools",
1507
+ name: "mcp listtool",
1381
1508
  description: "List tools available on a MCP server",
1382
- usage: "ucli mcp tools <server> [--format json|table|yaml]",
1509
+ usage: "ucli mcp <server> listtool [--format json|table|yaml]",
1383
1510
  examples: [
1384
- "ucli mcp tools weather",
1385
- "ucli mcp tools weather --format json"
1511
+ "ucli mcp weather listtool",
1512
+ "ucli mcp weather listtool --format json"
1386
1513
  ]
1387
1514
  },
1388
1515
  {
1389
- name: "mcp describe",
1390
- description: "Show detailed schema for a tool on a MCP server",
1391
- usage: "ucli mcp describe <server> <tool> [--json]",
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 describe weather get_forecast",
1394
- "ucli mcp describe weather get_forecast --json"
1520
+ "ucli mcp weather toolinfo get_forecast",
1521
+ "ucli mcp weather toolinfo get_forecast --json"
1395
1522
  ]
1396
1523
  },
1397
1524
  {
1398
- name: "mcp run",
1525
+ name: "mcp invoketool",
1399
1526
  description: "Call a tool on a MCP server",
1400
- usage: "ucli mcp run <server> <tool> [key=value ...] [--json] [--input-json <json>]",
1527
+ usage: "ucli mcp <server> invoketool <tool> [--data <json>] [--json]",
1401
1528
  examples: [
1402
- 'ucli mcp run weather get_forecast location="New York"',
1403
- 'ucli mcp run search web_search query="ucli docs" limit=5',
1404
- `ucli mcp run weather get_forecast --input-json '{"location": "New York"}'`,
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
- registerServices(program);
1463
- registerRun(program);
1464
- registerRefresh(program);
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);