@tronsfey/ucli 0.5.1 → 0.5.3

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