@tronsfey/ucli 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -6
- package/README.zh.md +63 -5
- package/dist/{client-3I7XBDJU.js → client-Y6NONDCI.js} +112 -8
- package/dist/client-Y6NONDCI.js.map +1 -0
- package/dist/index.js +397 -77
- package/dist/index.js.map +1 -1
- package/dist/{runner-GVYIJNHN.js → runner-ROLDMGH3.js} +266 -60
- package/dist/runner-ROLDMGH3.js.map +1 -0
- package/package.json +4 -4
- package/skill.md +182 -20
- package/dist/client-3I7XBDJU.js.map +0 -1
- package/dist/runner-GVYIJNHN.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -166,23 +166,59 @@ var ExitCode = {
|
|
|
166
166
|
SERVER_ERROR: 7
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
+
// src/lib/output.ts
|
|
170
|
+
var currentMode = "text";
|
|
171
|
+
function setOutputMode(mode) {
|
|
172
|
+
currentMode = mode;
|
|
173
|
+
}
|
|
174
|
+
function isJsonOutput() {
|
|
175
|
+
return currentMode === "json";
|
|
176
|
+
}
|
|
177
|
+
function outputSuccess(data) {
|
|
178
|
+
if (currentMode === "json") {
|
|
179
|
+
console.log(JSON.stringify({ success: true, data }, null, 2));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function outputError(code, message, hint) {
|
|
183
|
+
if (currentMode === "json") {
|
|
184
|
+
const envelope = {
|
|
185
|
+
success: false,
|
|
186
|
+
error: { code, message, ...hint ? { hint } : {} }
|
|
187
|
+
};
|
|
188
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
189
|
+
process.exit(code);
|
|
190
|
+
}
|
|
191
|
+
console.error(`
|
|
192
|
+
\u2716 Error: ${message}`);
|
|
193
|
+
if (hint) console.error(` Hint: ${hint}`);
|
|
194
|
+
process.exit(code);
|
|
195
|
+
}
|
|
196
|
+
|
|
169
197
|
// src/commands/configure.ts
|
|
170
198
|
function registerConfigure(program2) {
|
|
171
199
|
program2.command("configure").description("Configure the OAS Gateway server URL and authentication token").requiredOption("--server <url>", "OAS Gateway server URL (e.g. https://oas.example.com)").requiredOption("--token <jwt>", "Group JWT token issued by the server admin").action(async (opts) => {
|
|
172
200
|
const serverUrl = opts.server.replace(/\/$/, "");
|
|
173
201
|
const token = opts.token;
|
|
174
|
-
|
|
202
|
+
if (!isJsonOutput()) {
|
|
203
|
+
console.log(`Connecting to ${serverUrl}...`);
|
|
204
|
+
}
|
|
175
205
|
const client = new ServerClient({ serverUrl, token });
|
|
176
206
|
try {
|
|
177
207
|
await client.listOAS();
|
|
178
208
|
saveConfig({ serverUrl, token });
|
|
209
|
+
if (isJsonOutput()) {
|
|
210
|
+
outputSuccess({ serverUrl, configured: true });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
179
213
|
console.log("\u2713 Configuration saved successfully.");
|
|
180
214
|
console.log(` Server: ${serverUrl}`);
|
|
181
215
|
console.log(` Token: ${token.slice(0, 20)}...`);
|
|
182
216
|
} catch (err) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
217
|
+
outputError(
|
|
218
|
+
ExitCode.CONNECTIVITY_ERROR,
|
|
219
|
+
`Connection failed: ${err.message}`,
|
|
220
|
+
"Check the server URL and token"
|
|
221
|
+
);
|
|
186
222
|
}
|
|
187
223
|
});
|
|
188
224
|
}
|
|
@@ -335,7 +371,7 @@ function buildSafeEnv(authEnv) {
|
|
|
335
371
|
}
|
|
336
372
|
async function runOperation(opts) {
|
|
337
373
|
const bin = resolveOpenapi2CliBin();
|
|
338
|
-
const { entry, operationArgs, format, query } = opts;
|
|
374
|
+
const { entry, operationArgs, format, query, machine, dryRun } = opts;
|
|
339
375
|
const args = [
|
|
340
376
|
"run",
|
|
341
377
|
"--oas",
|
|
@@ -343,6 +379,8 @@ async function runOperation(opts) {
|
|
|
343
379
|
"--cache-ttl",
|
|
344
380
|
String(entry.cacheTtl),
|
|
345
381
|
...entry.baseEndpoint ? ["--endpoint", entry.baseEndpoint] : [],
|
|
382
|
+
...machine ? ["--machine"] : [],
|
|
383
|
+
...dryRun ? ["--dry-run"] : [],
|
|
346
384
|
...format ? ["--format", format] : [],
|
|
347
385
|
...query ? ["--query", query] : [],
|
|
348
386
|
...operationArgs
|
|
@@ -466,24 +504,24 @@ function registerServices(program2) {
|
|
|
466
504
|
await writeOASListCache(entries, maxTtl);
|
|
467
505
|
}
|
|
468
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
|
+
}
|
|
469
515
|
const format = (opts.format ?? "table").toLowerCase();
|
|
470
516
|
if (entries.length === 0) {
|
|
471
517
|
console.log("No services registered in this group.");
|
|
472
518
|
return;
|
|
473
519
|
}
|
|
474
520
|
if (format === "json") {
|
|
475
|
-
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
476
|
-
...rest,
|
|
477
|
-
authConfig: { type: authConfig["type"] ?? rest.authType }
|
|
478
|
-
}));
|
|
479
521
|
console.log(JSON.stringify(safe, null, 2));
|
|
480
522
|
return;
|
|
481
523
|
}
|
|
482
524
|
if (format === "yaml") {
|
|
483
|
-
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
484
|
-
...rest,
|
|
485
|
-
authConfig: { type: authConfig["type"] ?? rest.authType }
|
|
486
|
-
}));
|
|
487
525
|
console.log(toYaml(safe));
|
|
488
526
|
return;
|
|
489
527
|
}
|
|
@@ -504,30 +542,30 @@ ${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
|
|
|
504
542
|
let entry;
|
|
505
543
|
try {
|
|
506
544
|
entry = await client.getOAS(name);
|
|
507
|
-
} catch
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
545
|
+
} catch {
|
|
546
|
+
outputError(
|
|
547
|
+
ExitCode.NOT_FOUND,
|
|
548
|
+
`Service not found: ${name}`,
|
|
549
|
+
"Run: ucli services list to see available services"
|
|
550
|
+
);
|
|
511
551
|
}
|
|
512
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;
|
|
562
|
+
}
|
|
513
563
|
const format = (opts.format ?? "table").toLowerCase();
|
|
514
564
|
if (format === "json") {
|
|
515
|
-
const { authConfig, ...rest } = entry;
|
|
516
|
-
const safe = {
|
|
517
|
-
...rest,
|
|
518
|
-
authConfig: { type: authConfig["type"] ?? rest.authType },
|
|
519
|
-
operationsHelp: help
|
|
520
|
-
};
|
|
521
565
|
console.log(JSON.stringify(safe, null, 2));
|
|
522
566
|
return;
|
|
523
567
|
}
|
|
524
568
|
if (format === "yaml") {
|
|
525
|
-
const { authConfig, ...rest } = entry;
|
|
526
|
-
const safe = {
|
|
527
|
-
...rest,
|
|
528
|
-
authConfig: { type: authConfig["type"] ?? rest.authType },
|
|
529
|
-
operationsHelp: help
|
|
530
|
-
};
|
|
531
569
|
console.log(toYaml(safe));
|
|
532
570
|
return;
|
|
533
571
|
}
|
|
@@ -546,15 +584,20 @@ Service: ${entry.name}`);
|
|
|
546
584
|
|
|
547
585
|
// src/commands/run.ts
|
|
548
586
|
function registerRun(program2) {
|
|
549
|
-
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) => {
|
|
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) => {
|
|
550
588
|
if (serviceArg && opts.service && serviceArg !== opts.service) {
|
|
551
|
-
|
|
552
|
-
|
|
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
|
+
);
|
|
553
593
|
}
|
|
554
594
|
const service = opts.service ?? serviceArg;
|
|
555
595
|
if (!service) {
|
|
556
|
-
|
|
557
|
-
|
|
596
|
+
outputError(
|
|
597
|
+
ExitCode.USAGE_ERROR,
|
|
598
|
+
"Missing service name. Use positional <service> or --service <name>.",
|
|
599
|
+
"Run: ucli services list to see available services"
|
|
600
|
+
);
|
|
558
601
|
}
|
|
559
602
|
const cfg = getConfig();
|
|
560
603
|
const client = new ServerClient(cfg);
|
|
@@ -562,9 +605,11 @@ function registerRun(program2) {
|
|
|
562
605
|
try {
|
|
563
606
|
entry = await client.getOAS(service);
|
|
564
607
|
} catch {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
608
|
+
outputError(
|
|
609
|
+
ExitCode.NOT_FOUND,
|
|
610
|
+
`Unknown service: ${service}`,
|
|
611
|
+
"Run: ucli services list to see available services"
|
|
612
|
+
);
|
|
568
613
|
}
|
|
569
614
|
const extraArgs = opts.args ?? [];
|
|
570
615
|
const operationArgs = [...args, ...extraArgs];
|
|
@@ -576,8 +621,11 @@ function registerRun(program2) {
|
|
|
576
621
|
try {
|
|
577
622
|
parsed = JSON.parse(opts.params);
|
|
578
623
|
} catch {
|
|
579
|
-
|
|
580
|
-
|
|
624
|
+
outputError(
|
|
625
|
+
ExitCode.USAGE_ERROR,
|
|
626
|
+
"Invalid --params JSON.",
|
|
627
|
+
`Example: --params '{"petId": 1}'`
|
|
628
|
+
);
|
|
581
629
|
}
|
|
582
630
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
583
631
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -592,16 +640,22 @@ function registerRun(program2) {
|
|
|
592
640
|
}
|
|
593
641
|
const format = opts.format;
|
|
594
642
|
const query = opts.query;
|
|
643
|
+
const machine = opts.machine ?? false;
|
|
644
|
+
const dryRun = opts.dryRun ?? false;
|
|
595
645
|
try {
|
|
596
646
|
await runOperation({
|
|
597
647
|
entry,
|
|
598
648
|
operationArgs,
|
|
599
649
|
...format !== void 0 ? { format } : {},
|
|
600
|
-
...query !== void 0 ? { query } : {}
|
|
650
|
+
...query !== void 0 ? { query } : {},
|
|
651
|
+
...machine ? { machine } : {},
|
|
652
|
+
...dryRun ? { dryRun } : {}
|
|
601
653
|
});
|
|
602
654
|
} catch (err) {
|
|
603
|
-
|
|
604
|
-
|
|
655
|
+
outputError(
|
|
656
|
+
ExitCode.GENERAL_ERROR,
|
|
657
|
+
`Operation failed: ${err.message}`
|
|
658
|
+
);
|
|
605
659
|
}
|
|
606
660
|
});
|
|
607
661
|
}
|
|
@@ -611,7 +665,9 @@ function registerRefresh(program2) {
|
|
|
611
665
|
program2.command("refresh").description("Force-refresh the local OAS cache from the server").option("--service <name>", "Refresh only a specific service").action(async (opts) => {
|
|
612
666
|
const cfg = getConfig();
|
|
613
667
|
const client = new ServerClient(cfg);
|
|
614
|
-
|
|
668
|
+
if (!isJsonOutput()) {
|
|
669
|
+
console.log("Refreshing OAS list from server...");
|
|
670
|
+
}
|
|
615
671
|
if (opts.service) {
|
|
616
672
|
await clearOASCache(opts.service);
|
|
617
673
|
} else {
|
|
@@ -622,6 +678,10 @@ function registerRefresh(program2) {
|
|
|
622
678
|
const maxTtl = Math.min(...entries.map((e) => e.cacheTtl));
|
|
623
679
|
await writeOASListCache(entries, maxTtl);
|
|
624
680
|
}
|
|
681
|
+
if (isJsonOutput()) {
|
|
682
|
+
outputSuccess({ refreshed: entries.length });
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
625
685
|
console.log(`\u2713 Refreshed ${entries.length} service(s).`);
|
|
626
686
|
});
|
|
627
687
|
}
|
|
@@ -696,6 +756,10 @@ DISCOVERY
|
|
|
696
756
|
ucli services info <service>
|
|
697
757
|
Show detailed service info and all available operations.
|
|
698
758
|
|
|
759
|
+
ucli introspect
|
|
760
|
+
Return complete capability manifest in a single call (JSON).
|
|
761
|
+
Ideal for AI agents: includes services, MCP servers, and command reference.
|
|
762
|
+
|
|
699
763
|
ucli help [service]
|
|
700
764
|
Show this guide, or service-specific operations.
|
|
701
765
|
|
|
@@ -708,6 +772,16 @@ EXECUTION
|
|
|
708
772
|
--query <jmespath> Filter response with JMESPath
|
|
709
773
|
--data <json|@file> Request body for POST/PUT/PATCH
|
|
710
774
|
|
|
775
|
+
MCP SERVERS
|
|
776
|
+
ucli mcp list
|
|
777
|
+
List all MCP servers in your group.
|
|
778
|
+
|
|
779
|
+
ucli mcp tools <server>
|
|
780
|
+
List tools available on a MCP server.
|
|
781
|
+
|
|
782
|
+
ucli mcp run <server> <tool> [key=value ...]
|
|
783
|
+
Call a tool on a MCP server.
|
|
784
|
+
|
|
711
785
|
MAINTENANCE
|
|
712
786
|
ucli refresh
|
|
713
787
|
Force-refresh the local OAS cache from the server.
|
|
@@ -722,6 +796,8 @@ SHELL COMPLETIONS
|
|
|
722
796
|
|
|
723
797
|
GLOBAL FLAGS
|
|
724
798
|
--debug Enable verbose debug logging
|
|
799
|
+
--output json Wrap ALL output in structured JSON envelopes
|
|
800
|
+
(for agent/automation consumption)
|
|
725
801
|
-v, --version Show version number
|
|
726
802
|
|
|
727
803
|
ERRORS
|
|
@@ -729,6 +805,11 @@ ERRORS
|
|
|
729
805
|
404 Not Found \u2192 Check service name: ucli services list
|
|
730
806
|
4xx Client Error \u2192 Check operation args: ucli services info <service>
|
|
731
807
|
5xx Server Error \u2192 Retry or run: ucli refresh
|
|
808
|
+
|
|
809
|
+
AI AGENT QUICK START
|
|
810
|
+
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
|
|
732
813
|
`);
|
|
733
814
|
}
|
|
734
815
|
|
|
@@ -739,12 +820,14 @@ function resolve(mod, name) {
|
|
|
739
820
|
throw new Error(`Cannot resolve export "${name}" from module`);
|
|
740
821
|
}
|
|
741
822
|
async function getMcp2cli() {
|
|
742
|
-
const clientMod = await import("./client-
|
|
743
|
-
const runnerMod = await import("./runner-
|
|
823
|
+
const clientMod = await import("./client-Y6NONDCI.js");
|
|
824
|
+
const runnerMod = await import("./runner-ROLDMGH3.js");
|
|
744
825
|
return {
|
|
745
826
|
createMcpClient: resolve(clientMod, "createMcpClient"),
|
|
746
827
|
getTools: resolve(runnerMod, "getTools"),
|
|
747
|
-
runTool: resolve(runnerMod, "runTool")
|
|
828
|
+
runTool: resolve(runnerMod, "runTool"),
|
|
829
|
+
describeTool: resolve(runnerMod, "describeTool"),
|
|
830
|
+
describeToolJson: resolve(runnerMod, "describeToolJson")
|
|
748
831
|
};
|
|
749
832
|
}
|
|
750
833
|
async function closeClient(client) {
|
|
@@ -778,7 +861,24 @@ async function listMcpTools(entry) {
|
|
|
778
861
|
await closeClient(client);
|
|
779
862
|
}
|
|
780
863
|
}
|
|
781
|
-
async function
|
|
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) {
|
|
782
882
|
const { createMcpClient, getTools, runTool } = await getMcp2cli();
|
|
783
883
|
const config = buildMcpConfig(entry);
|
|
784
884
|
const client = await createMcpClient(config);
|
|
@@ -797,7 +897,10 @@ async function runMcpTool(entry, toolName, rawArgs) {
|
|
|
797
897
|
normalizedArgs.push(arg);
|
|
798
898
|
}
|
|
799
899
|
}
|
|
800
|
-
await runTool(client, tool, normalizedArgs, {
|
|
900
|
+
await runTool(client, tool, normalizedArgs, {
|
|
901
|
+
...opts?.json ? { json: true } : {},
|
|
902
|
+
...opts?.inputJson ? { inputJson: opts.inputJson } : {}
|
|
903
|
+
});
|
|
801
904
|
} finally {
|
|
802
905
|
await closeClient(client);
|
|
803
906
|
}
|
|
@@ -810,24 +913,24 @@ function registerMcp(program2) {
|
|
|
810
913
|
const cfg = getConfig();
|
|
811
914
|
const client = new ServerClient(cfg);
|
|
812
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
|
+
}
|
|
813
924
|
if (entries.length === 0) {
|
|
814
925
|
console.log("No MCP servers registered in this group.");
|
|
815
926
|
return;
|
|
816
927
|
}
|
|
817
928
|
const format = (opts.format ?? "table").toLowerCase();
|
|
818
929
|
if (format === "json") {
|
|
819
|
-
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
820
|
-
...rest,
|
|
821
|
-
authConfig: { type: authConfig.type }
|
|
822
|
-
}));
|
|
823
930
|
console.log(JSON.stringify(safe, null, 2));
|
|
824
931
|
return;
|
|
825
932
|
}
|
|
826
933
|
if (format === "yaml") {
|
|
827
|
-
const safe = entries.map(({ authConfig, ...rest }) => ({
|
|
828
|
-
...rest,
|
|
829
|
-
authConfig: { type: authConfig.type }
|
|
830
|
-
}));
|
|
831
934
|
console.log(toYaml(safe));
|
|
832
935
|
return;
|
|
833
936
|
}
|
|
@@ -848,16 +951,21 @@ ${"SERVER".padEnd(nameWidth)} TRANSPORT DESCRIPTION`);
|
|
|
848
951
|
try {
|
|
849
952
|
entry = await client.getMCP(serverName);
|
|
850
953
|
} catch {
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
954
|
+
outputError(
|
|
955
|
+
ExitCode.NOT_FOUND,
|
|
956
|
+
`Unknown MCP server: ${serverName}`,
|
|
957
|
+
"Run: ucli mcp list to see available servers"
|
|
958
|
+
);
|
|
854
959
|
}
|
|
855
960
|
let tools;
|
|
856
961
|
try {
|
|
857
962
|
tools = await listMcpTools(entry);
|
|
858
963
|
} catch (err) {
|
|
859
|
-
|
|
860
|
-
|
|
964
|
+
outputError(ExitCode.GENERAL_ERROR, `Failed to fetch tools: ${err.message}`);
|
|
965
|
+
}
|
|
966
|
+
if (isJsonOutput()) {
|
|
967
|
+
outputSuccess(tools);
|
|
968
|
+
return;
|
|
861
969
|
}
|
|
862
970
|
if (tools.length === 0) {
|
|
863
971
|
console.log(`No tools found on MCP server "${serverName}".`);
|
|
@@ -881,22 +989,45 @@ Tools on "${serverName}":`);
|
|
|
881
989
|
}
|
|
882
990
|
console.log();
|
|
883
991
|
});
|
|
884
|
-
mcp.command("
|
|
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) => {
|
|
885
993
|
const cfg = getConfig();
|
|
886
994
|
const client = new ServerClient(cfg);
|
|
887
995
|
let entry;
|
|
888
996
|
try {
|
|
889
997
|
entry = await client.getMCP(serverName);
|
|
890
998
|
} catch {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
+
);
|
|
894
1023
|
}
|
|
895
1024
|
try {
|
|
896
|
-
await runMcpTool(entry, toolName, args
|
|
1025
|
+
await runMcpTool(entry, toolName, args, {
|
|
1026
|
+
json: opts.json,
|
|
1027
|
+
inputJson: opts.inputJson
|
|
1028
|
+
});
|
|
897
1029
|
} catch (err) {
|
|
898
|
-
|
|
899
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
1030
|
+
outputError(ExitCode.GENERAL_ERROR, `Tool execution failed: ${err.message}`);
|
|
900
1031
|
}
|
|
901
1032
|
});
|
|
902
1033
|
}
|
|
@@ -931,8 +1062,11 @@ function registerDoctor(program2) {
|
|
|
931
1062
|
ok: false,
|
|
932
1063
|
detail: "Not configured. Run: ucli configure --server <url> --token <jwt>"
|
|
933
1064
|
});
|
|
934
|
-
|
|
935
|
-
|
|
1065
|
+
outputError(
|
|
1066
|
+
ExitCode.CONFIG_ERROR,
|
|
1067
|
+
"Not configured",
|
|
1068
|
+
"Run: ucli configure --server <url> --token <jwt>"
|
|
1069
|
+
);
|
|
936
1070
|
}
|
|
937
1071
|
let cfg;
|
|
938
1072
|
try {
|
|
@@ -948,8 +1082,11 @@ function registerDoctor(program2) {
|
|
|
948
1082
|
ok: false,
|
|
949
1083
|
detail: `Failed to read config: ${err.message}`
|
|
950
1084
|
});
|
|
951
|
-
|
|
952
|
-
|
|
1085
|
+
outputError(
|
|
1086
|
+
ExitCode.CONFIG_ERROR,
|
|
1087
|
+
`Failed to read config: ${err.message}`,
|
|
1088
|
+
"Run: ucli configure --server <url> --token <jwt>"
|
|
1089
|
+
);
|
|
953
1090
|
}
|
|
954
1091
|
debug(`Checking connectivity to ${cfg.serverUrl}...`);
|
|
955
1092
|
try {
|
|
@@ -985,8 +1122,15 @@ function registerDoctor(program2) {
|
|
|
985
1122
|
detail: `Token rejected: ${msg}`
|
|
986
1123
|
});
|
|
987
1124
|
}
|
|
988
|
-
printResults(results);
|
|
989
1125
|
const allOk = results.every((r) => r.ok);
|
|
1126
|
+
if (isJsonOutput()) {
|
|
1127
|
+
outputSuccess({
|
|
1128
|
+
healthy: allOk,
|
|
1129
|
+
checks: results
|
|
1130
|
+
});
|
|
1131
|
+
process.exit(allOk ? ExitCode.SUCCESS : ExitCode.GENERAL_ERROR);
|
|
1132
|
+
}
|
|
1133
|
+
printResults(results);
|
|
990
1134
|
process.exit(allOk ? ExitCode.SUCCESS : ExitCode.GENERAL_ERROR);
|
|
991
1135
|
});
|
|
992
1136
|
}
|
|
@@ -1026,7 +1170,7 @@ _ucli_completions() {
|
|
|
1026
1170
|
COMPREPLY=()
|
|
1027
1171
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
1028
1172
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
1029
|
-
commands="configure services run refresh help mcp doctor completions"
|
|
1173
|
+
commands="configure services run refresh help mcp doctor completions introspect"
|
|
1030
1174
|
|
|
1031
1175
|
case "\${COMP_WORDS[1]}" in
|
|
1032
1176
|
services)
|
|
@@ -1070,6 +1214,7 @@ _ucli() {
|
|
|
1070
1214
|
'mcp:Interact with MCP servers'
|
|
1071
1215
|
'doctor:Check configuration and connectivity'
|
|
1072
1216
|
'completions:Generate shell completion script'
|
|
1217
|
+
'introspect:Return complete capability manifest for AI agents'
|
|
1073
1218
|
)
|
|
1074
1219
|
|
|
1075
1220
|
_arguments -C \\
|
|
@@ -1112,6 +1257,8 @@ complete -c ucli -n __fish_use_subcommand -a mcp -d 'Interact with MCP servers'
|
|
|
1112
1257
|
complete -c ucli -n __fish_use_subcommand -a doctor -d 'Check config and connectivity'
|
|
1113
1258
|
complete -c ucli -n __fish_use_subcommand -a completions -d 'Generate shell completions'
|
|
1114
1259
|
|
|
1260
|
+
complete -c ucli -n __fish_use_subcommand -a introspect -d 'Return capability manifest for AI agents'
|
|
1261
|
+
|
|
1115
1262
|
# services subcommands
|
|
1116
1263
|
complete -c ucli -n '__fish_seen_subcommand_from services' -a list -d 'List services'
|
|
1117
1264
|
complete -c ucli -n '__fish_seen_subcommand_from services' -a info -d 'Show service details'
|
|
@@ -1125,16 +1272,188 @@ complete -c ucli -n '__fish_seen_subcommand_from mcp' -a run -d 'Call a tool'
|
|
|
1125
1272
|
complete -c ucli -n '__fish_seen_subcommand_from completions' -a 'bash zsh fish' -d 'Shell type'`;
|
|
1126
1273
|
}
|
|
1127
1274
|
|
|
1275
|
+
// src/commands/introspect.ts
|
|
1276
|
+
function registerIntrospect(program2) {
|
|
1277
|
+
program2.command("introspect").description("Return a complete capability manifest for AI agent discovery (services, MCP servers, commands)").option("--format <fmt>", "Output format: json | yaml", "json").action(async (opts) => {
|
|
1278
|
+
const cfg = getConfig();
|
|
1279
|
+
const client = new ServerClient(cfg);
|
|
1280
|
+
let oasEntries = [];
|
|
1281
|
+
let mcpEntries = [];
|
|
1282
|
+
try {
|
|
1283
|
+
;
|
|
1284
|
+
[oasEntries, mcpEntries] = await Promise.all([
|
|
1285
|
+
client.listOAS(),
|
|
1286
|
+
client.listMCP()
|
|
1287
|
+
]);
|
|
1288
|
+
} catch (err) {
|
|
1289
|
+
const message = err.message;
|
|
1290
|
+
outputError(
|
|
1291
|
+
ExitCode.CONNECTIVITY_ERROR,
|
|
1292
|
+
`Failed to fetch capabilities: ${message}`,
|
|
1293
|
+
"Check server connectivity with: ucli doctor"
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
const manifest = {
|
|
1297
|
+
version: "1",
|
|
1298
|
+
services: oasEntries.map(toIntrospectService),
|
|
1299
|
+
mcpServers: mcpEntries.map(toIntrospectMcpServer),
|
|
1300
|
+
commands: getCommandReference()
|
|
1301
|
+
};
|
|
1302
|
+
const format = (opts.format ?? "json").toLowerCase();
|
|
1303
|
+
if (isJsonOutput()) {
|
|
1304
|
+
outputSuccess(manifest);
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
if (format === "json") {
|
|
1308
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if (format === "yaml") {
|
|
1312
|
+
console.log(toYaml(manifest));
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
function toIntrospectService(e) {
|
|
1319
|
+
return {
|
|
1320
|
+
name: e.name,
|
|
1321
|
+
description: e.description,
|
|
1322
|
+
authType: e.authType,
|
|
1323
|
+
remoteUrl: e.remoteUrl,
|
|
1324
|
+
baseEndpoint: e.baseEndpoint,
|
|
1325
|
+
cacheTtl: e.cacheTtl
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
function toIntrospectMcpServer(e) {
|
|
1329
|
+
return {
|
|
1330
|
+
name: e.name,
|
|
1331
|
+
description: e.description,
|
|
1332
|
+
transport: e.transport,
|
|
1333
|
+
enabled: e.enabled
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function getCommandReference() {
|
|
1337
|
+
return [
|
|
1338
|
+
{
|
|
1339
|
+
name: "services list",
|
|
1340
|
+
description: "List all OAS services available in the current group",
|
|
1341
|
+
usage: "ucli services list [--format json|table|yaml] [--refresh]",
|
|
1342
|
+
examples: [
|
|
1343
|
+
"ucli services list",
|
|
1344
|
+
"ucli services list --format json",
|
|
1345
|
+
"ucli services list --refresh"
|
|
1346
|
+
]
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
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]",
|
|
1352
|
+
examples: [
|
|
1353
|
+
"ucli services info payments",
|
|
1354
|
+
"ucli services info payments --format json"
|
|
1355
|
+
]
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
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]",
|
|
1361
|
+
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}'`
|
|
1368
|
+
]
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
name: "mcp list",
|
|
1372
|
+
description: "List all MCP servers available in the current group",
|
|
1373
|
+
usage: "ucli mcp list [--format json|table|yaml]",
|
|
1374
|
+
examples: [
|
|
1375
|
+
"ucli mcp list",
|
|
1376
|
+
"ucli mcp list --format json"
|
|
1377
|
+
]
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
name: "mcp tools",
|
|
1381
|
+
description: "List tools available on a MCP server",
|
|
1382
|
+
usage: "ucli mcp tools <server> [--format json|table|yaml]",
|
|
1383
|
+
examples: [
|
|
1384
|
+
"ucli mcp tools weather",
|
|
1385
|
+
"ucli mcp tools weather --format json"
|
|
1386
|
+
]
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
name: "mcp describe",
|
|
1390
|
+
description: "Show detailed schema for a tool on a MCP server",
|
|
1391
|
+
usage: "ucli mcp describe <server> <tool> [--json]",
|
|
1392
|
+
examples: [
|
|
1393
|
+
"ucli mcp describe weather get_forecast",
|
|
1394
|
+
"ucli mcp describe weather get_forecast --json"
|
|
1395
|
+
]
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
name: "mcp run",
|
|
1399
|
+
description: "Call a tool on a MCP server",
|
|
1400
|
+
usage: "ucli mcp run <server> <tool> [key=value ...] [--json] [--input-json <json>]",
|
|
1401
|
+
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"'
|
|
1406
|
+
]
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
name: "introspect",
|
|
1410
|
+
description: "Return complete capability manifest (this command)",
|
|
1411
|
+
usage: "ucli introspect [--format json|yaml]",
|
|
1412
|
+
examples: [
|
|
1413
|
+
"ucli introspect",
|
|
1414
|
+
"ucli introspect --format yaml"
|
|
1415
|
+
]
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
name: "refresh",
|
|
1419
|
+
description: "Force-refresh the local OAS cache from the server",
|
|
1420
|
+
usage: "ucli refresh [--service <name>]",
|
|
1421
|
+
examples: [
|
|
1422
|
+
"ucli refresh",
|
|
1423
|
+
"ucli refresh --service payments"
|
|
1424
|
+
]
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
name: "doctor",
|
|
1428
|
+
description: "Check configuration, server connectivity, and token validity",
|
|
1429
|
+
usage: "ucli doctor",
|
|
1430
|
+
examples: ["ucli doctor"]
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
name: "configure",
|
|
1434
|
+
description: "Configure the server URL and authentication token",
|
|
1435
|
+
usage: "ucli configure --server <url> --token <jwt>",
|
|
1436
|
+
examples: ["ucli configure --server https://oas.example.com --token eyJ..."]
|
|
1437
|
+
}
|
|
1438
|
+
];
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1128
1441
|
// src/index.ts
|
|
1129
1442
|
var require3 = createRequire2(import.meta.url);
|
|
1130
1443
|
var pkg = require3("../package.json");
|
|
1131
1444
|
var program = new Command();
|
|
1132
|
-
program.name("ucli").description(pkg.description).version(pkg.version, "-v, --version").option("--debug", "Enable verbose debug logging").addHelpCommand(false).hook("preAction", (_thisCommand, actionCommand) => {
|
|
1445
|
+
program.name("ucli").description(pkg.description).version(pkg.version, "-v, --version").option("--debug", "Enable verbose debug logging").option("--output <mode>", "Output mode: text | json (json wraps every result in a structured envelope for agent consumption)", "text").addHelpCommand(false).hook("preAction", (_thisCommand, actionCommand) => {
|
|
1133
1446
|
let cmd = actionCommand;
|
|
1134
1447
|
while (cmd) {
|
|
1135
|
-
|
|
1448
|
+
const opts = cmd.opts();
|
|
1449
|
+
if (opts.debug) {
|
|
1136
1450
|
setDebugMode(true);
|
|
1137
|
-
|
|
1451
|
+
}
|
|
1452
|
+
if (opts.output && typeof opts.output === "string") {
|
|
1453
|
+
const mode = opts.output.toLowerCase();
|
|
1454
|
+
if (mode === "json" || mode === "text") {
|
|
1455
|
+
setOutputMode(mode);
|
|
1456
|
+
}
|
|
1138
1457
|
}
|
|
1139
1458
|
cmd = cmd.parent;
|
|
1140
1459
|
}
|
|
@@ -1146,6 +1465,7 @@ registerRefresh(program);
|
|
|
1146
1465
|
registerMcp(program);
|
|
1147
1466
|
registerDoctor(program);
|
|
1148
1467
|
registerCompletions(program);
|
|
1468
|
+
registerIntrospect(program);
|
|
1149
1469
|
registerHelp(program);
|
|
1150
1470
|
program.parse(process.argv);
|
|
1151
1471
|
//# sourceMappingURL=index.js.map
|