@modeltoolsprotocol/mtpcli 0.1.0 → 0.2.1
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 +38 -9
- package/dist/index.js +733 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ The command-line interface for the [Model Tools Protocol](https://github.com/mod
|
|
|
6
6
|
|
|
7
7
|
LLM agents need to discover and use tools. Right now there are two worlds:
|
|
8
8
|
|
|
9
|
-
**CLI tools** are the backbone of software development. They're composable (`|`), scriptable, version-controlled, and work everywhere. But LLMs can't discover what a CLI does -they have to parse `--help` text, guess at arguments, and hope for the best.
|
|
9
|
+
**CLI tools** are the backbone of software development. They're composable (`|`), scriptable, version-controlled, and work everywhere. But LLMs can't discover what a CLI does - they have to parse `--help` text, guess at arguments, and hope for the best.
|
|
10
10
|
|
|
11
11
|
**MCP (Model Context Protocol)** solves discovery beautifully. Tools declare typed schemas, and LLM hosts discover them via a structured handshake. But MCP requires running a server process, speaking JSON-RPC over stdio/SSE, and building within the MCP ecosystem. Your existing CLI tools don't get any of this for free.
|
|
12
12
|
|
|
@@ -55,12 +55,15 @@ mtpcli search --scan-path "git commit"
|
|
|
55
55
|
# OAuth2 login
|
|
56
56
|
mtpcli auth login mytool
|
|
57
57
|
|
|
58
|
-
# API key login
|
|
59
|
-
mtpcli auth login mytool --
|
|
58
|
+
# API key / bearer token login
|
|
59
|
+
mtpcli auth login mytool --token sk-xxx
|
|
60
60
|
|
|
61
61
|
# Check auth status
|
|
62
62
|
mtpcli auth status mytool
|
|
63
63
|
|
|
64
|
+
# Inject token into env
|
|
65
|
+
eval $(mtpcli auth env mytool)
|
|
66
|
+
|
|
64
67
|
# Log out
|
|
65
68
|
mtpcli auth logout mytool
|
|
66
69
|
```
|
|
@@ -69,17 +72,43 @@ mtpcli auth logout mytool
|
|
|
69
72
|
|
|
70
73
|
```bash
|
|
71
74
|
# Start an MCP server that bridges describe-compatible tools
|
|
72
|
-
mtpcli serve -- mytool anothertool
|
|
75
|
+
mtpcli serve --tool mytool --tool anothertool
|
|
76
|
+
```
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
### Wrap an MCP server as a CLI
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Describe an MCP server's tools
|
|
82
|
+
mtpcli wrap --server "npx @mcp/server-github" --describe
|
|
83
|
+
|
|
84
|
+
# Call a tool on an MCP server
|
|
85
|
+
mtpcli wrap --server "npx @mcp/server-github" search_repos -- --query mtpcli
|
|
76
86
|
```
|
|
77
87
|
|
|
78
|
-
###
|
|
88
|
+
### Validate a tool's --describe output
|
|
79
89
|
|
|
80
90
|
```bash
|
|
81
|
-
#
|
|
82
|
-
mtpcli
|
|
91
|
+
# Validate a tool against the MTP spec
|
|
92
|
+
mtpcli validate mytool
|
|
93
|
+
|
|
94
|
+
# Validate JSON from stdin (for CI)
|
|
95
|
+
cat describe.json | mtpcli validate --stdin
|
|
96
|
+
|
|
97
|
+
# JSON output
|
|
98
|
+
mtpcli validate mytool --json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Generate shell completions
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Bash
|
|
105
|
+
eval $(mtpcli completions bash mytool)
|
|
106
|
+
|
|
107
|
+
# Zsh
|
|
108
|
+
eval $(mtpcli completions zsh mytool)
|
|
109
|
+
|
|
110
|
+
# Fish
|
|
111
|
+
mtpcli completions fish mytool | source
|
|
83
112
|
```
|
|
84
113
|
|
|
85
114
|
### Describe self
|
package/dist/index.js
CHANGED
|
@@ -7917,17 +7917,38 @@ function sendNotification(writer, method, params) {
|
|
|
7917
7917
|
writer.write(JSON.stringify(req) + `
|
|
7918
7918
|
`);
|
|
7919
7919
|
}
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
const
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7920
|
+
function readResponse(rl) {
|
|
7921
|
+
return new Promise((resolve, reject) => {
|
|
7922
|
+
const onLine = (line) => {
|
|
7923
|
+
const trimmed = line.trim();
|
|
7924
|
+
if (!trimmed)
|
|
7925
|
+
return;
|
|
7926
|
+
let msg;
|
|
7927
|
+
try {
|
|
7928
|
+
msg = JSON.parse(trimmed);
|
|
7929
|
+
} catch (e) {
|
|
7930
|
+
rl.off("line", onLine);
|
|
7931
|
+
rl.off("close", onClose);
|
|
7932
|
+
reject(e);
|
|
7933
|
+
return;
|
|
7934
|
+
}
|
|
7935
|
+
if ("id" in msg && msg.id !== undefined) {
|
|
7936
|
+
rl.off("line", onLine);
|
|
7937
|
+
rl.off("close", onClose);
|
|
7938
|
+
try {
|
|
7939
|
+
resolve(JsonRpcResponseSchema2.parse(msg));
|
|
7940
|
+
} catch (e) {
|
|
7941
|
+
reject(e);
|
|
7942
|
+
}
|
|
7943
|
+
}
|
|
7944
|
+
};
|
|
7945
|
+
const onClose = () => {
|
|
7946
|
+
rl.off("line", onLine);
|
|
7947
|
+
reject(new Error("MCP server closed connection"));
|
|
7948
|
+
};
|
|
7949
|
+
rl.on("line", onLine);
|
|
7950
|
+
rl.on("close", onClose);
|
|
7951
|
+
});
|
|
7931
7952
|
}
|
|
7932
7953
|
var init_mcp = __esm(() => {
|
|
7933
7954
|
init_models();
|
|
@@ -8452,6 +8473,614 @@ var init_wrap = __esm(() => {
|
|
|
8452
8473
|
init_mcp();
|
|
8453
8474
|
});
|
|
8454
8475
|
|
|
8476
|
+
// src/validate.ts
|
|
8477
|
+
var exports_validate = {};
|
|
8478
|
+
__export(exports_validate, {
|
|
8479
|
+
validateTool: () => validateTool,
|
|
8480
|
+
validateJson: () => validateJson,
|
|
8481
|
+
run: () => run3,
|
|
8482
|
+
crossReferenceHelp: () => crossReferenceHelp
|
|
8483
|
+
});
|
|
8484
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
8485
|
+
import { promisify as promisify10 } from "node:util";
|
|
8486
|
+
function validateJson(raw) {
|
|
8487
|
+
const diags = [];
|
|
8488
|
+
let parsed;
|
|
8489
|
+
try {
|
|
8490
|
+
parsed = JSON.parse(raw);
|
|
8491
|
+
} catch (e) {
|
|
8492
|
+
diags.push({
|
|
8493
|
+
level: "error",
|
|
8494
|
+
code: "INVALID_JSON",
|
|
8495
|
+
message: `Not valid JSON: ${e instanceof Error ? e.message : e}`
|
|
8496
|
+
});
|
|
8497
|
+
return diags;
|
|
8498
|
+
}
|
|
8499
|
+
const result = ToolSchemaSchema2.safeParse(parsed);
|
|
8500
|
+
if (!result.success) {
|
|
8501
|
+
for (const issue of result.error.issues) {
|
|
8502
|
+
diags.push({
|
|
8503
|
+
level: "error",
|
|
8504
|
+
code: "SCHEMA_VIOLATION",
|
|
8505
|
+
message: issue.message,
|
|
8506
|
+
path: issue.path.join(".")
|
|
8507
|
+
});
|
|
8508
|
+
}
|
|
8509
|
+
return diags;
|
|
8510
|
+
}
|
|
8511
|
+
const schema = result.data;
|
|
8512
|
+
for (let ci = 0;ci < schema.commands.length; ci++) {
|
|
8513
|
+
const cmd = schema.commands[ci];
|
|
8514
|
+
const cmdPath = `commands.${ci}`;
|
|
8515
|
+
if (cmd.examples.length === 0) {
|
|
8516
|
+
diags.push({
|
|
8517
|
+
level: "warning",
|
|
8518
|
+
code: "MISSING_EXAMPLES",
|
|
8519
|
+
message: `Command "${cmd.name}" has no examples`,
|
|
8520
|
+
path: `${cmdPath}.examples`
|
|
8521
|
+
});
|
|
8522
|
+
}
|
|
8523
|
+
for (let ai = 0;ai < cmd.args.length; ai++) {
|
|
8524
|
+
const arg = cmd.args[ai];
|
|
8525
|
+
const argPath = `${cmdPath}.args.${ai}`;
|
|
8526
|
+
if (!VALID_ARG_TYPES.has(arg.type)) {
|
|
8527
|
+
diags.push({
|
|
8528
|
+
level: "error",
|
|
8529
|
+
code: "INVALID_ARG_TYPE",
|
|
8530
|
+
message: `Arg "${arg.name}" has invalid type "${arg.type}"`,
|
|
8531
|
+
path: `${argPath}.type`
|
|
8532
|
+
});
|
|
8533
|
+
}
|
|
8534
|
+
if (arg.type === "enum" && (!arg.values || arg.values.length === 0)) {
|
|
8535
|
+
diags.push({
|
|
8536
|
+
level: "warning",
|
|
8537
|
+
code: "ENUM_NO_VALUES",
|
|
8538
|
+
message: `Enum arg "${arg.name}" has no values`,
|
|
8539
|
+
path: `${argPath}.values`
|
|
8540
|
+
});
|
|
8541
|
+
}
|
|
8542
|
+
if (!arg.description) {
|
|
8543
|
+
diags.push({
|
|
8544
|
+
level: "warning",
|
|
8545
|
+
code: "MISSING_DESCRIPTION",
|
|
8546
|
+
message: `Arg "${arg.name}" has no description`,
|
|
8547
|
+
path: `${argPath}.description`
|
|
8548
|
+
});
|
|
8549
|
+
}
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
return diags;
|
|
8553
|
+
}
|
|
8554
|
+
function crossReferenceHelp(schema, helpText) {
|
|
8555
|
+
const diags = [];
|
|
8556
|
+
const helpFlags = new Set;
|
|
8557
|
+
const flagRe = /--([\w][\w-]*)/g;
|
|
8558
|
+
let m;
|
|
8559
|
+
while ((m = flagRe.exec(helpText)) !== null) {
|
|
8560
|
+
helpFlags.add(`--${m[1]}`);
|
|
8561
|
+
}
|
|
8562
|
+
helpFlags.delete("--help");
|
|
8563
|
+
helpFlags.delete("--version");
|
|
8564
|
+
helpFlags.delete("--describe");
|
|
8565
|
+
const declaredFlags = new Set;
|
|
8566
|
+
for (const cmd of schema.commands) {
|
|
8567
|
+
for (const arg of cmd.args) {
|
|
8568
|
+
if (arg.name.startsWith("--")) {
|
|
8569
|
+
declaredFlags.add(arg.name);
|
|
8570
|
+
}
|
|
8571
|
+
}
|
|
8572
|
+
}
|
|
8573
|
+
for (const flag of declaredFlags) {
|
|
8574
|
+
if (!helpFlags.has(flag)) {
|
|
8575
|
+
diags.push({
|
|
8576
|
+
level: "info",
|
|
8577
|
+
code: "HELP_ARG_MISMATCH",
|
|
8578
|
+
message: `Flag "${flag}" in --describe but not found in --help`
|
|
8579
|
+
});
|
|
8580
|
+
}
|
|
8581
|
+
}
|
|
8582
|
+
for (const flag of helpFlags) {
|
|
8583
|
+
if (!declaredFlags.has(flag)) {
|
|
8584
|
+
diags.push({
|
|
8585
|
+
level: "info",
|
|
8586
|
+
code: "HELP_ARG_MISMATCH",
|
|
8587
|
+
message: `Flag "${flag}" in --help but not found in --describe`
|
|
8588
|
+
});
|
|
8589
|
+
}
|
|
8590
|
+
}
|
|
8591
|
+
return diags;
|
|
8592
|
+
}
|
|
8593
|
+
async function validateTool(toolName, opts) {
|
|
8594
|
+
const diags = [];
|
|
8595
|
+
let toolPath;
|
|
8596
|
+
try {
|
|
8597
|
+
toolPath = await import_which3.default(toolName);
|
|
8598
|
+
} catch {
|
|
8599
|
+
diags.push({
|
|
8600
|
+
level: "error",
|
|
8601
|
+
code: "NOT_FOUND",
|
|
8602
|
+
message: `Tool "${toolName}" not found in PATH`
|
|
8603
|
+
});
|
|
8604
|
+
return buildResult(toolName, diags);
|
|
8605
|
+
}
|
|
8606
|
+
let describeOutput;
|
|
8607
|
+
try {
|
|
8608
|
+
const { stdout } = await execFileAsync8(toolPath, ["--describe"], {
|
|
8609
|
+
timeout: 1e4
|
|
8610
|
+
});
|
|
8611
|
+
describeOutput = stdout.trim();
|
|
8612
|
+
if (!describeOutput) {
|
|
8613
|
+
diags.push({
|
|
8614
|
+
level: "error",
|
|
8615
|
+
code: "DESCRIBE_FAILED",
|
|
8616
|
+
message: `"${toolName} --describe" produced empty output`
|
|
8617
|
+
});
|
|
8618
|
+
return buildResult(toolName, diags);
|
|
8619
|
+
}
|
|
8620
|
+
} catch (e) {
|
|
8621
|
+
diags.push({
|
|
8622
|
+
level: "error",
|
|
8623
|
+
code: "DESCRIBE_FAILED",
|
|
8624
|
+
message: `"${toolName} --describe" failed: ${e instanceof Error ? e.message : e}`
|
|
8625
|
+
});
|
|
8626
|
+
return buildResult(toolName, diags);
|
|
8627
|
+
}
|
|
8628
|
+
diags.push(...validateJson(describeOutput));
|
|
8629
|
+
const hasErrors = diags.some((d) => d.level === "error");
|
|
8630
|
+
if (!hasErrors && !opts?.skipHelp) {
|
|
8631
|
+
try {
|
|
8632
|
+
const { stdout: helpText } = await execFileAsync8(toolPath, ["--help"], {
|
|
8633
|
+
timeout: 5000
|
|
8634
|
+
});
|
|
8635
|
+
const schema = ToolSchemaSchema2.parse(JSON.parse(describeOutput));
|
|
8636
|
+
diags.push(...crossReferenceHelp(schema, helpText));
|
|
8637
|
+
} catch {}
|
|
8638
|
+
}
|
|
8639
|
+
return buildResult(toolName, diags);
|
|
8640
|
+
}
|
|
8641
|
+
function buildResult(tool, diagnostics) {
|
|
8642
|
+
const errors2 = diagnostics.filter((d) => d.level === "error").length;
|
|
8643
|
+
const warnings = diagnostics.filter((d) => d.level === "warning").length;
|
|
8644
|
+
const info = diagnostics.filter((d) => d.level === "info").length;
|
|
8645
|
+
return {
|
|
8646
|
+
tool,
|
|
8647
|
+
valid: errors2 === 0,
|
|
8648
|
+
diagnostics,
|
|
8649
|
+
summary: { errors: errors2, warnings, info }
|
|
8650
|
+
};
|
|
8651
|
+
}
|
|
8652
|
+
async function run3(toolOrStdin, opts) {
|
|
8653
|
+
let result;
|
|
8654
|
+
if (opts.stdin) {
|
|
8655
|
+
const chunks = [];
|
|
8656
|
+
for await (const chunk of process.stdin) {
|
|
8657
|
+
chunks.push(chunk);
|
|
8658
|
+
}
|
|
8659
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
8660
|
+
const diags = validateJson(raw);
|
|
8661
|
+
result = buildResult(opts.stdin ? "stdin" : "unknown", diags);
|
|
8662
|
+
} else if (toolOrStdin) {
|
|
8663
|
+
result = await validateTool(toolOrStdin, { skipHelp: opts.skipHelp });
|
|
8664
|
+
} else {
|
|
8665
|
+
process.stderr.write(`error: provide a tool name or use --stdin
|
|
8666
|
+
`);
|
|
8667
|
+
process.exit(1);
|
|
8668
|
+
}
|
|
8669
|
+
if (opts.json) {
|
|
8670
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8671
|
+
} else {
|
|
8672
|
+
printHuman(result);
|
|
8673
|
+
}
|
|
8674
|
+
if (!result.valid)
|
|
8675
|
+
process.exit(1);
|
|
8676
|
+
}
|
|
8677
|
+
function printHuman(result) {
|
|
8678
|
+
const icon = result.valid ? "PASS" : "FAIL";
|
|
8679
|
+
console.log(`${icon} ${result.tool}`);
|
|
8680
|
+
for (const d of result.diagnostics) {
|
|
8681
|
+
const tag = d.level === "error" ? "ERR" : d.level === "warning" ? "WRN" : "INF";
|
|
8682
|
+
const path2 = d.path ? ` (${d.path})` : "";
|
|
8683
|
+
console.log(` ${tag} [${d.code}] ${d.message}${path2}`);
|
|
8684
|
+
}
|
|
8685
|
+
const parts = [];
|
|
8686
|
+
if (result.summary.errors)
|
|
8687
|
+
parts.push(`${result.summary.errors} error(s)`);
|
|
8688
|
+
if (result.summary.warnings)
|
|
8689
|
+
parts.push(`${result.summary.warnings} warning(s)`);
|
|
8690
|
+
if (result.summary.info)
|
|
8691
|
+
parts.push(`${result.summary.info} info`);
|
|
8692
|
+
if (parts.length)
|
|
8693
|
+
console.log(` ${parts.join(", ")}`);
|
|
8694
|
+
}
|
|
8695
|
+
var import_which3, execFileAsync8, VALID_ARG_TYPES;
|
|
8696
|
+
var init_validate = __esm(() => {
|
|
8697
|
+
init_models();
|
|
8698
|
+
import_which3 = __toESM(require_lib(), 1);
|
|
8699
|
+
execFileAsync8 = promisify10(execFile10);
|
|
8700
|
+
VALID_ARG_TYPES = new Set([
|
|
8701
|
+
"string",
|
|
8702
|
+
"integer",
|
|
8703
|
+
"number",
|
|
8704
|
+
"boolean",
|
|
8705
|
+
"array",
|
|
8706
|
+
"enum",
|
|
8707
|
+
"path"
|
|
8708
|
+
]);
|
|
8709
|
+
});
|
|
8710
|
+
|
|
8711
|
+
// src/completions.ts
|
|
8712
|
+
var exports_completions = {};
|
|
8713
|
+
__export(exports_completions, {
|
|
8714
|
+
schemaToCompletionContext: () => schemaToCompletionContext,
|
|
8715
|
+
run: () => run4,
|
|
8716
|
+
generateZsh: () => generateZsh,
|
|
8717
|
+
generateFish: () => generateFish,
|
|
8718
|
+
generateBash: () => generateBash,
|
|
8719
|
+
generate: () => generate
|
|
8720
|
+
});
|
|
8721
|
+
function extractFlags(args) {
|
|
8722
|
+
const flags = [];
|
|
8723
|
+
for (const arg of args) {
|
|
8724
|
+
if (arg.name.startsWith("--")) {
|
|
8725
|
+
flags.push({
|
|
8726
|
+
name: arg.name,
|
|
8727
|
+
description: arg.description ?? "",
|
|
8728
|
+
type: arg.type,
|
|
8729
|
+
values: arg.values
|
|
8730
|
+
});
|
|
8731
|
+
}
|
|
8732
|
+
}
|
|
8733
|
+
return flags;
|
|
8734
|
+
}
|
|
8735
|
+
function schemaToCompletionContext(schema) {
|
|
8736
|
+
let rootFlags = [];
|
|
8737
|
+
const groups = new Map;
|
|
8738
|
+
for (const cmd of schema.commands) {
|
|
8739
|
+
if (cmd.name === "_root") {
|
|
8740
|
+
rootFlags = extractFlags(cmd.args);
|
|
8741
|
+
continue;
|
|
8742
|
+
}
|
|
8743
|
+
const parts = cmd.name.split(/\s+/);
|
|
8744
|
+
const key = parts[0];
|
|
8745
|
+
const entry = { parts, description: cmd.description, flags: extractFlags(cmd.args) };
|
|
8746
|
+
let group = groups.get(key);
|
|
8747
|
+
if (!group) {
|
|
8748
|
+
group = [];
|
|
8749
|
+
groups.set(key, group);
|
|
8750
|
+
}
|
|
8751
|
+
group.push(entry);
|
|
8752
|
+
}
|
|
8753
|
+
const commands = [];
|
|
8754
|
+
for (const [key, entries] of groups) {
|
|
8755
|
+
if (entries.length === 1 && entries[0].parts.length === 1) {
|
|
8756
|
+
commands.push({
|
|
8757
|
+
name: key,
|
|
8758
|
+
description: entries[0].description,
|
|
8759
|
+
flags: entries[0].flags,
|
|
8760
|
+
children: []
|
|
8761
|
+
});
|
|
8762
|
+
} else {
|
|
8763
|
+
const children = [];
|
|
8764
|
+
let groupDesc = "";
|
|
8765
|
+
for (const entry of entries) {
|
|
8766
|
+
const childName = entry.parts.slice(1).join(" ");
|
|
8767
|
+
if (!childName) {
|
|
8768
|
+
groupDesc = entry.description;
|
|
8769
|
+
continue;
|
|
8770
|
+
}
|
|
8771
|
+
children.push({
|
|
8772
|
+
name: childName,
|
|
8773
|
+
description: entry.description,
|
|
8774
|
+
flags: entry.flags,
|
|
8775
|
+
children: []
|
|
8776
|
+
});
|
|
8777
|
+
}
|
|
8778
|
+
if (!groupDesc && children.length > 0) {
|
|
8779
|
+
groupDesc = `${key} commands`;
|
|
8780
|
+
}
|
|
8781
|
+
commands.push({
|
|
8782
|
+
name: key,
|
|
8783
|
+
description: groupDesc,
|
|
8784
|
+
flags: [],
|
|
8785
|
+
children
|
|
8786
|
+
});
|
|
8787
|
+
}
|
|
8788
|
+
}
|
|
8789
|
+
return { toolName: schema.name, rootFlags, commands };
|
|
8790
|
+
}
|
|
8791
|
+
function generateBash(toolName, ctx) {
|
|
8792
|
+
const funcName = `_${toolName.replace(/[^a-zA-Z0-9]/g, "_")}_completions`;
|
|
8793
|
+
const topNames = ctx.commands.map((c) => c.name);
|
|
8794
|
+
const lines = [];
|
|
8795
|
+
lines.push(`${funcName}() {`);
|
|
8796
|
+
lines.push(` local cur prev words cword`);
|
|
8797
|
+
lines.push(` _get_comp_words_by_ref -n : cur prev words cword 2>/dev/null || {`);
|
|
8798
|
+
lines.push(` cur="\${COMP_WORDS[COMP_CWORD]}"`);
|
|
8799
|
+
lines.push(` prev="\${COMP_WORDS[COMP_CWORD-1]}"`);
|
|
8800
|
+
lines.push(` words=("\${COMP_WORDS[@]}")`);
|
|
8801
|
+
lines.push(` cword=$COMP_CWORD`);
|
|
8802
|
+
lines.push(` }`);
|
|
8803
|
+
lines.push(``);
|
|
8804
|
+
lines.push(` if ((cword == 1)); then`);
|
|
8805
|
+
const toplevel = [...topNames];
|
|
8806
|
+
if (ctx.rootFlags.length > 0) {
|
|
8807
|
+
toplevel.push(...ctx.rootFlags.map((f) => f.name));
|
|
8808
|
+
}
|
|
8809
|
+
lines.push(` COMPREPLY=($(compgen -W "${toplevel.join(" ")}" -- "$cur"))`);
|
|
8810
|
+
lines.push(` return`);
|
|
8811
|
+
lines.push(` fi`);
|
|
8812
|
+
lines.push(``);
|
|
8813
|
+
lines.push(` local subcmd="\${words[1]}"`);
|
|
8814
|
+
lines.push(``);
|
|
8815
|
+
lines.push(` case "$subcmd" in`);
|
|
8816
|
+
for (const cmd of ctx.commands) {
|
|
8817
|
+
if (cmd.children.length > 0) {
|
|
8818
|
+
const childNames = cmd.children.map((c) => c.name);
|
|
8819
|
+
lines.push(` ${cmd.name})`);
|
|
8820
|
+
lines.push(` if ((cword == 2)); then`);
|
|
8821
|
+
lines.push(` COMPREPLY=($(compgen -W "${childNames.join(" ")}" -- "$cur"))`);
|
|
8822
|
+
lines.push(` return`);
|
|
8823
|
+
lines.push(` fi`);
|
|
8824
|
+
lines.push(` local childcmd="\${words[2]}"`);
|
|
8825
|
+
for (const child of cmd.children) {
|
|
8826
|
+
for (const flag of child.flags) {
|
|
8827
|
+
if (flag.type === "enum" && flag.values && flag.values.length > 0) {
|
|
8828
|
+
lines.push(` if [[ "$childcmd" == "${child.name}" && "$prev" == "${flag.name}" ]]; then`);
|
|
8829
|
+
lines.push(` COMPREPLY=($(compgen -W "${flag.values.join(" ")}" -- "$cur"))`);
|
|
8830
|
+
lines.push(` return`);
|
|
8831
|
+
lines.push(` fi`);
|
|
8832
|
+
}
|
|
8833
|
+
if (flag.type === "path") {
|
|
8834
|
+
lines.push(` if [[ "$childcmd" == "${child.name}" && "$prev" == "${flag.name}" ]]; then`);
|
|
8835
|
+
lines.push(` compopt -o default`);
|
|
8836
|
+
lines.push(` COMPREPLY=()`);
|
|
8837
|
+
lines.push(` return`);
|
|
8838
|
+
lines.push(` fi`);
|
|
8839
|
+
}
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
8842
|
+
lines.push(` case "$childcmd" in`);
|
|
8843
|
+
for (const child of cmd.children) {
|
|
8844
|
+
const flags = child.flags.map((f) => f.name).join(" ");
|
|
8845
|
+
lines.push(` ${child.name}) COMPREPLY=($(compgen -W "${flags}" -- "$cur"));;`);
|
|
8846
|
+
}
|
|
8847
|
+
lines.push(` *) ;;`);
|
|
8848
|
+
lines.push(` esac`);
|
|
8849
|
+
lines.push(` ;;`);
|
|
8850
|
+
} else {
|
|
8851
|
+
lines.push(` ${cmd.name})`);
|
|
8852
|
+
for (const flag of cmd.flags) {
|
|
8853
|
+
if (flag.type === "enum" && flag.values && flag.values.length > 0) {
|
|
8854
|
+
lines.push(` if [[ "$prev" == "${flag.name}" ]]; then`);
|
|
8855
|
+
lines.push(` COMPREPLY=($(compgen -W "${flag.values.join(" ")}" -- "$cur"))`);
|
|
8856
|
+
lines.push(` return`);
|
|
8857
|
+
lines.push(` fi`);
|
|
8858
|
+
}
|
|
8859
|
+
if (flag.type === "path") {
|
|
8860
|
+
lines.push(` if [[ "$prev" == "${flag.name}" ]]; then`);
|
|
8861
|
+
lines.push(` compopt -o default`);
|
|
8862
|
+
lines.push(` COMPREPLY=()`);
|
|
8863
|
+
lines.push(` return`);
|
|
8864
|
+
lines.push(` fi`);
|
|
8865
|
+
}
|
|
8866
|
+
}
|
|
8867
|
+
const flags = cmd.flags.map((f) => f.name).join(" ");
|
|
8868
|
+
lines.push(` COMPREPLY=($(compgen -W "${flags}" -- "$cur"))`);
|
|
8869
|
+
lines.push(` ;;`);
|
|
8870
|
+
}
|
|
8871
|
+
}
|
|
8872
|
+
lines.push(` *) ;;`);
|
|
8873
|
+
lines.push(` esac`);
|
|
8874
|
+
lines.push(`}`);
|
|
8875
|
+
lines.push(``);
|
|
8876
|
+
lines.push(`complete -F ${funcName} ${toolName}`);
|
|
8877
|
+
return lines.join(`
|
|
8878
|
+
`) + `
|
|
8879
|
+
`;
|
|
8880
|
+
}
|
|
8881
|
+
function zshEsc(s) {
|
|
8882
|
+
return s.replace(/'/g, "'\\''");
|
|
8883
|
+
}
|
|
8884
|
+
function zshFlagLine(flag) {
|
|
8885
|
+
const desc = zshEsc(flag.description);
|
|
8886
|
+
if (flag.type === "enum" && flag.values && flag.values.length > 0) {
|
|
8887
|
+
return `'${flag.name}[${desc}]:value:(${flag.values.join(" ")})'`;
|
|
8888
|
+
} else if (flag.type === "path") {
|
|
8889
|
+
return `'${flag.name}[${desc}]:file:_files'`;
|
|
8890
|
+
} else if (flag.type === "boolean") {
|
|
8891
|
+
return `'${flag.name}[${desc}]'`;
|
|
8892
|
+
}
|
|
8893
|
+
return `'${flag.name}[${desc}]:value:'`;
|
|
8894
|
+
}
|
|
8895
|
+
function generateZsh(toolName, ctx) {
|
|
8896
|
+
const funcName = `_${toolName.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
8897
|
+
const lines = [];
|
|
8898
|
+
lines.push(`#compdef ${toolName}`);
|
|
8899
|
+
lines.push(``);
|
|
8900
|
+
for (const cmd of ctx.commands) {
|
|
8901
|
+
if (cmd.children.length === 0)
|
|
8902
|
+
continue;
|
|
8903
|
+
const subFunc = `${funcName}_${cmd.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
8904
|
+
lines.push(`${subFunc}() {`);
|
|
8905
|
+
lines.push(` local -a subcmds`);
|
|
8906
|
+
lines.push(` subcmds=(`);
|
|
8907
|
+
for (const child of cmd.children) {
|
|
8908
|
+
lines.push(` '${child.name}:${zshEsc(child.description)}'`);
|
|
8909
|
+
}
|
|
8910
|
+
lines.push(` )`);
|
|
8911
|
+
lines.push(``);
|
|
8912
|
+
lines.push(` _arguments -C \\`);
|
|
8913
|
+
lines.push(` '1:command:->subcmd' \\`);
|
|
8914
|
+
lines.push(` '*::arg:->args'`);
|
|
8915
|
+
lines.push(``);
|
|
8916
|
+
lines.push(` case "$state" in`);
|
|
8917
|
+
lines.push(` subcmd)`);
|
|
8918
|
+
lines.push(` _describe '${cmd.name} command' subcmds`);
|
|
8919
|
+
lines.push(` ;;`);
|
|
8920
|
+
lines.push(` args)`);
|
|
8921
|
+
lines.push(` case "\${words[1]}" in`);
|
|
8922
|
+
for (const child of cmd.children) {
|
|
8923
|
+
lines.push(` ${child.name})`);
|
|
8924
|
+
lines.push(` _arguments \\`);
|
|
8925
|
+
for (const flag of child.flags) {
|
|
8926
|
+
lines.push(` ${zshFlagLine(flag)} \\`);
|
|
8927
|
+
}
|
|
8928
|
+
lines.push(` ;;`);
|
|
8929
|
+
}
|
|
8930
|
+
lines.push(` esac`);
|
|
8931
|
+
lines.push(` ;;`);
|
|
8932
|
+
lines.push(` esac`);
|
|
8933
|
+
lines.push(`}`);
|
|
8934
|
+
lines.push(``);
|
|
8935
|
+
}
|
|
8936
|
+
lines.push(`${funcName}() {`);
|
|
8937
|
+
lines.push(` local -a subcmds`);
|
|
8938
|
+
if (ctx.commands.length > 0) {
|
|
8939
|
+
lines.push(` subcmds=(`);
|
|
8940
|
+
for (const cmd of ctx.commands) {
|
|
8941
|
+
lines.push(` '${cmd.name}:${zshEsc(cmd.description)}'`);
|
|
8942
|
+
}
|
|
8943
|
+
lines.push(` )`);
|
|
8944
|
+
}
|
|
8945
|
+
lines.push(``);
|
|
8946
|
+
lines.push(` _arguments -C \\`);
|
|
8947
|
+
for (const flag of ctx.rootFlags) {
|
|
8948
|
+
lines.push(` ${zshFlagLine(flag)} \\`);
|
|
8949
|
+
}
|
|
8950
|
+
lines.push(` '1:command:->subcmd' \\`);
|
|
8951
|
+
lines.push(` '*::arg:->args'`);
|
|
8952
|
+
lines.push(``);
|
|
8953
|
+
lines.push(` case "$state" in`);
|
|
8954
|
+
lines.push(` subcmd)`);
|
|
8955
|
+
lines.push(` _describe 'command' subcmds`);
|
|
8956
|
+
lines.push(` ;;`);
|
|
8957
|
+
lines.push(` args)`);
|
|
8958
|
+
lines.push(` case "\${words[1]}" in`);
|
|
8959
|
+
for (const cmd of ctx.commands) {
|
|
8960
|
+
if (cmd.children.length > 0) {
|
|
8961
|
+
const subFunc = `${funcName}_${cmd.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
8962
|
+
lines.push(` ${cmd.name}) ${subFunc};;`);
|
|
8963
|
+
} else {
|
|
8964
|
+
lines.push(` ${cmd.name})`);
|
|
8965
|
+
lines.push(` _arguments \\`);
|
|
8966
|
+
for (const flag of cmd.flags) {
|
|
8967
|
+
lines.push(` ${zshFlagLine(flag)} \\`);
|
|
8968
|
+
}
|
|
8969
|
+
lines.push(` ;;`);
|
|
8970
|
+
}
|
|
8971
|
+
}
|
|
8972
|
+
lines.push(` esac`);
|
|
8973
|
+
lines.push(` ;;`);
|
|
8974
|
+
lines.push(` esac`);
|
|
8975
|
+
lines.push(`}`);
|
|
8976
|
+
lines.push(``);
|
|
8977
|
+
lines.push(`${funcName} "$@"`);
|
|
8978
|
+
return lines.join(`
|
|
8979
|
+
`) + `
|
|
8980
|
+
`;
|
|
8981
|
+
}
|
|
8982
|
+
function fishEsc(s) {
|
|
8983
|
+
return s.replace(/'/g, "\\'");
|
|
8984
|
+
}
|
|
8985
|
+
function fishFlagLines(toolName, condition, flags) {
|
|
8986
|
+
const lines = [];
|
|
8987
|
+
for (const flag of flags) {
|
|
8988
|
+
const flagName = flag.name.replace(/^--/, "");
|
|
8989
|
+
const desc = fishEsc(flag.description);
|
|
8990
|
+
if (flag.type === "enum" && flag.values && flag.values.length > 0) {
|
|
8991
|
+
lines.push(`complete -c ${toolName} -n '${condition}' -l ${flagName} -d '${desc}' -f -a '${flag.values.join(" ")}'`);
|
|
8992
|
+
} else if (flag.type === "path") {
|
|
8993
|
+
lines.push(`complete -c ${toolName} -n '${condition}' -l ${flagName} -d '${desc}' -F`);
|
|
8994
|
+
} else if (flag.type === "boolean") {
|
|
8995
|
+
lines.push(`complete -c ${toolName} -n '${condition}' -l ${flagName} -d '${desc}'`);
|
|
8996
|
+
} else {
|
|
8997
|
+
lines.push(`complete -c ${toolName} -n '${condition}' -l ${flagName} -d '${desc}' -x`);
|
|
8998
|
+
}
|
|
8999
|
+
}
|
|
9000
|
+
return lines;
|
|
9001
|
+
}
|
|
9002
|
+
function generateFish(toolName, ctx) {
|
|
9003
|
+
const lines = [];
|
|
9004
|
+
const groupCmds = ctx.commands.filter((c) => c.children.length > 0);
|
|
9005
|
+
if (groupCmds.length > 0) {
|
|
9006
|
+
for (const group of groupCmds) {
|
|
9007
|
+
const childNames = group.children.map((c) => c.name).join(" ");
|
|
9008
|
+
const funcName = `__${toolName.replace(/[^a-zA-Z0-9]/g, "_")}_needs_${group.name}_subcmd`;
|
|
9009
|
+
lines.push(`function ${funcName}`);
|
|
9010
|
+
lines.push(` set -l cmd (commandline -opc)`);
|
|
9011
|
+
lines.push(` if not contains -- ${group.name} $cmd`);
|
|
9012
|
+
lines.push(` return 1`);
|
|
9013
|
+
lines.push(` end`);
|
|
9014
|
+
lines.push(` for subcmd in ${childNames}`);
|
|
9015
|
+
lines.push(` if contains -- $subcmd $cmd`);
|
|
9016
|
+
lines.push(` return 1`);
|
|
9017
|
+
lines.push(` end`);
|
|
9018
|
+
lines.push(` end`);
|
|
9019
|
+
lines.push(` return 0`);
|
|
9020
|
+
lines.push(`end`);
|
|
9021
|
+
lines.push(``);
|
|
9022
|
+
}
|
|
9023
|
+
}
|
|
9024
|
+
for (const flag of ctx.rootFlags) {
|
|
9025
|
+
const flagName = flag.name.replace(/^--/, "");
|
|
9026
|
+
const desc = fishEsc(flag.description);
|
|
9027
|
+
lines.push(`complete -c ${toolName} -n '__fish_use_subcommand' -l ${flagName} -d '${desc}'`);
|
|
9028
|
+
}
|
|
9029
|
+
for (const cmd of ctx.commands) {
|
|
9030
|
+
const desc = fishEsc(cmd.description);
|
|
9031
|
+
lines.push(`complete -c ${toolName} -n '__fish_use_subcommand' -a ${cmd.name} -d '${desc}'`);
|
|
9032
|
+
}
|
|
9033
|
+
for (const cmd of ctx.commands) {
|
|
9034
|
+
if (cmd.children.length > 0) {
|
|
9035
|
+
const funcName = `__${toolName.replace(/[^a-zA-Z0-9]/g, "_")}_needs_${cmd.name}_subcmd`;
|
|
9036
|
+
for (const child of cmd.children) {
|
|
9037
|
+
const desc = fishEsc(child.description);
|
|
9038
|
+
lines.push(`complete -c ${toolName} -n '${funcName}' -a ${child.name} -d '${desc}'`);
|
|
9039
|
+
}
|
|
9040
|
+
for (const child of cmd.children) {
|
|
9041
|
+
const condition = `__fish_seen_subcommand_from ${child.name}`;
|
|
9042
|
+
lines.push(...fishFlagLines(toolName, condition, child.flags));
|
|
9043
|
+
}
|
|
9044
|
+
} else {
|
|
9045
|
+
const condition = `__fish_seen_subcommand_from ${cmd.name}`;
|
|
9046
|
+
lines.push(...fishFlagLines(toolName, condition, cmd.flags));
|
|
9047
|
+
}
|
|
9048
|
+
}
|
|
9049
|
+
return lines.join(`
|
|
9050
|
+
`) + `
|
|
9051
|
+
`;
|
|
9052
|
+
}
|
|
9053
|
+
function generate(shell, toolName, schema) {
|
|
9054
|
+
const ctx = schemaToCompletionContext(schema);
|
|
9055
|
+
switch (shell) {
|
|
9056
|
+
case "bash":
|
|
9057
|
+
return generateBash(toolName, ctx);
|
|
9058
|
+
case "zsh":
|
|
9059
|
+
return generateZsh(toolName, ctx);
|
|
9060
|
+
case "fish":
|
|
9061
|
+
return generateFish(toolName, ctx);
|
|
9062
|
+
default:
|
|
9063
|
+
throw new Error(`Unknown shell: ${shell}`);
|
|
9064
|
+
}
|
|
9065
|
+
}
|
|
9066
|
+
async function run4(shell, toolName) {
|
|
9067
|
+
if (!["bash", "zsh", "fish"].includes(shell)) {
|
|
9068
|
+
process.stderr.write(`error: unsupported shell "${shell}". Use bash, zsh, or fish.
|
|
9069
|
+
`);
|
|
9070
|
+
process.exit(1);
|
|
9071
|
+
}
|
|
9072
|
+
const schema = await getToolSchema2(toolName);
|
|
9073
|
+
if (!schema) {
|
|
9074
|
+
process.stderr.write(`error: could not get --describe from "${toolName}"
|
|
9075
|
+
`);
|
|
9076
|
+
process.exit(1);
|
|
9077
|
+
}
|
|
9078
|
+
process.stdout.write(generate(shell, toolName, schema));
|
|
9079
|
+
}
|
|
9080
|
+
var init_completions = __esm(() => {
|
|
9081
|
+
init_search2();
|
|
9082
|
+
});
|
|
9083
|
+
|
|
8455
9084
|
// node_modules/commander/esm.mjs
|
|
8456
9085
|
var import__ = __toESM(require_commander(), 1);
|
|
8457
9086
|
var {
|
|
@@ -8579,7 +9208,7 @@ function cleanJson(obj) {
|
|
|
8579
9208
|
}
|
|
8580
9209
|
|
|
8581
9210
|
// src/index.ts
|
|
8582
|
-
var VERSION3 = "0.1
|
|
9211
|
+
var VERSION3 = "0.2.1";
|
|
8583
9212
|
function selfDescribe() {
|
|
8584
9213
|
const schema = {
|
|
8585
9214
|
name: "mtpcli",
|
|
@@ -8777,6 +9406,82 @@ function selfDescribe() {
|
|
|
8777
9406
|
command: 'mtpcli wrap --server "npx @mcp/server-github" search_repos -- --query mtpcli'
|
|
8778
9407
|
}
|
|
8779
9408
|
]
|
|
9409
|
+
},
|
|
9410
|
+
{
|
|
9411
|
+
name: "validate",
|
|
9412
|
+
description: "Validate a tool's --describe output against the MTP spec",
|
|
9413
|
+
args: [
|
|
9414
|
+
{
|
|
9415
|
+
name: "tool",
|
|
9416
|
+
type: "string",
|
|
9417
|
+
description: "Tool name to validate"
|
|
9418
|
+
},
|
|
9419
|
+
{
|
|
9420
|
+
name: "--json",
|
|
9421
|
+
type: "boolean",
|
|
9422
|
+
default: false,
|
|
9423
|
+
description: "Output as JSON"
|
|
9424
|
+
},
|
|
9425
|
+
{
|
|
9426
|
+
name: "--stdin",
|
|
9427
|
+
type: "boolean",
|
|
9428
|
+
default: false,
|
|
9429
|
+
description: "Read JSON from stdin instead of running tool"
|
|
9430
|
+
},
|
|
9431
|
+
{
|
|
9432
|
+
name: "--skip-help",
|
|
9433
|
+
type: "boolean",
|
|
9434
|
+
default: false,
|
|
9435
|
+
description: "Skip --help cross-reference check"
|
|
9436
|
+
}
|
|
9437
|
+
],
|
|
9438
|
+
examples: [
|
|
9439
|
+
{
|
|
9440
|
+
description: "Validate a tool",
|
|
9441
|
+
command: "mtpcli validate mytool"
|
|
9442
|
+
},
|
|
9443
|
+
{
|
|
9444
|
+
description: "Validate from stdin",
|
|
9445
|
+
command: "cat describe.json | mtpcli validate --stdin"
|
|
9446
|
+
},
|
|
9447
|
+
{
|
|
9448
|
+
description: "JSON output for CI",
|
|
9449
|
+
command: "mtpcli validate mytool --json"
|
|
9450
|
+
}
|
|
9451
|
+
]
|
|
9452
|
+
},
|
|
9453
|
+
{
|
|
9454
|
+
name: "completions",
|
|
9455
|
+
description: "Generate shell completions for a --describe-compatible tool",
|
|
9456
|
+
args: [
|
|
9457
|
+
{
|
|
9458
|
+
name: "shell",
|
|
9459
|
+
type: "enum",
|
|
9460
|
+
required: true,
|
|
9461
|
+
values: ["bash", "zsh", "fish"],
|
|
9462
|
+
description: "Target shell"
|
|
9463
|
+
},
|
|
9464
|
+
{
|
|
9465
|
+
name: "tool",
|
|
9466
|
+
type: "string",
|
|
9467
|
+
required: true,
|
|
9468
|
+
description: "Tool name"
|
|
9469
|
+
}
|
|
9470
|
+
],
|
|
9471
|
+
examples: [
|
|
9472
|
+
{
|
|
9473
|
+
description: "Install bash completions",
|
|
9474
|
+
command: "eval $(mtpcli completions bash mytool)"
|
|
9475
|
+
},
|
|
9476
|
+
{
|
|
9477
|
+
description: "Install zsh completions",
|
|
9478
|
+
command: "eval $(mtpcli completions zsh mytool)"
|
|
9479
|
+
},
|
|
9480
|
+
{
|
|
9481
|
+
description: "Install fish completions",
|
|
9482
|
+
command: "mtpcli completions fish mytool | source"
|
|
9483
|
+
}
|
|
9484
|
+
]
|
|
8780
9485
|
}
|
|
8781
9486
|
]
|
|
8782
9487
|
};
|
|
@@ -8837,12 +9542,24 @@ authCmd.command("refresh").description("Force-refresh the stored OAuth token for
|
|
|
8837
9542
|
await runRefresh2(tool);
|
|
8838
9543
|
});
|
|
8839
9544
|
program2.command("serve").description("Serve CLI tools as an MCP server (cli2mcp bridge)").requiredOption("--tool <names...>", "Tool(s) to serve").action(async (opts) => {
|
|
8840
|
-
const { run:
|
|
8841
|
-
await
|
|
9545
|
+
const { run: run5 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
9546
|
+
await run5(opts.tool);
|
|
8842
9547
|
});
|
|
8843
9548
|
program2.command("wrap").description("Wrap an MCP server as a CLI tool (mcp2cli bridge)").requiredOption("--server <cmd>", "MCP server command to run").option("--describe", "Output --describe JSON instead of invoking", false).argument("[tool_name]", "Tool name to invoke").argument("[args...]", "Arguments for the tool (after --)").action(async (toolName, args, opts) => {
|
|
8844
|
-
const { run:
|
|
8845
|
-
await
|
|
9549
|
+
const { run: run5 } = await Promise.resolve().then(() => (init_wrap(), exports_wrap));
|
|
9550
|
+
await run5(opts.server, opts.describe, toolName, args);
|
|
9551
|
+
});
|
|
9552
|
+
program2.command("validate").description("Validate a tool's --describe output against the MTP spec").argument("[tool]", "Tool name to validate").option("--json", "Output as JSON", false).option("--stdin", "Read JSON from stdin", false).option("--skip-help", "Skip --help cross-reference", false).action(async (tool, opts) => {
|
|
9553
|
+
const { run: run5 } = await Promise.resolve().then(() => (init_validate(), exports_validate));
|
|
9554
|
+
await run5(tool, {
|
|
9555
|
+
json: opts.json,
|
|
9556
|
+
stdin: opts.stdin,
|
|
9557
|
+
skipHelp: opts.skipHelp
|
|
9558
|
+
});
|
|
9559
|
+
});
|
|
9560
|
+
program2.command("completions").description("Generate shell completions from --describe output").argument("<shell>", "Shell type (bash, zsh, fish)").argument("<tool>", "Tool name").action(async (shell, tool) => {
|
|
9561
|
+
const { run: run5 } = await Promise.resolve().then(() => (init_completions(), exports_completions));
|
|
9562
|
+
await run5(shell, tool);
|
|
8846
9563
|
});
|
|
8847
9564
|
try {
|
|
8848
9565
|
await program2.parseAsync(process.argv);
|