@modeltoolsprotocol/mtpcli 0.3.0 → 0.3.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 +52 -29
- package/dist/index.js +36 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,27 +6,21 @@ 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
|
|
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
|
-
**MCP (Model Context Protocol)** solves discovery beautifully. Tools declare typed schemas, and LLM hosts discover them via a structured handshake. But MCP
|
|
12
|
-
|
|
13
|
-
These are both good ideas with real tradeoffs:
|
|
11
|
+
**MCP (Model Context Protocol)** solves discovery beautifully. Tools declare typed schemas, and LLM hosts discover them via a structured handshake. But MCP tools aren't composable. Each invocation goes through the model. You can't pipe one MCP tool's output into another. You can't script them without an LLM in the loop.
|
|
14
12
|
|
|
15
13
|
| | CLI tools | MCP tools |
|
|
16
14
|
|---|---|---|
|
|
17
|
-
| **Composability** | First-class. Pipes, shell scripts, xargs
|
|
15
|
+
| **Composability** | First-class. Pipes, shell scripts, xargs. 50 years of Unix | Requires an orchestrator or agent framework |
|
|
18
16
|
| **Discovery** | Poor. Parse `--help` and hope | Excellent. Typed schemas via handshake |
|
|
19
17
|
| **Runtime** | Any shell, anywhere | Needs an MCP host (Claude Desktop, etc.) |
|
|
20
18
|
| **Deployment** | `brew install`, `cargo install`, a binary in PATH | Run a server process, configure the host |
|
|
21
19
|
| **Interop** | Pipes to anything | Talks to MCP clients only |
|
|
22
|
-
| **Scriptable without an LLM** | Yes
|
|
23
|
-
| **Streaming / subscriptions** | Limited (stdout streaming) | Built-in (SSE, notifications) |
|
|
24
|
-
| **Stateful interaction** | Stateless by design (each invocation is fresh) | Stateful sessions with context |
|
|
20
|
+
| **Scriptable without an LLM** | Yes, that's the whole point of CLIs | Not really, designed for LLM interaction |
|
|
25
21
|
| **Adoption cost** | One flag/decorator | New protocol, server scaffolding, SDK |
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
The gap is discovery. `mtpcli` fills it.
|
|
23
|
+
The gap for CLIs is discovery. `mtpcli` fills it.
|
|
30
24
|
|
|
31
25
|
## Install
|
|
32
26
|
|
|
@@ -34,8 +28,52 @@ The gap is discovery. `mtpcli` fills it.
|
|
|
34
28
|
npm install -g @modeltoolsprotocol/mtpcli
|
|
35
29
|
```
|
|
36
30
|
|
|
31
|
+
## What it does
|
|
32
|
+
|
|
33
|
+
**mtpcli** bridges the gap between CLI tools and MCP servers. It turns any `--describe` CLI into an MCP server, turns any MCP server into a composable CLI, and handles discovery, auth, and validation along the way.
|
|
34
|
+
|
|
37
35
|
## Usage
|
|
38
36
|
|
|
37
|
+
### Serve CLI tools over MCP
|
|
38
|
+
|
|
39
|
+
Any CLI that supports `--describe` becomes an MCP server:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
$ mtpcli serve --tool atlasctl --tool mytool
|
|
43
|
+
|
|
44
|
+
mtpcli serve: serving 6 tool(s) from 2 CLI tool(s)
|
|
45
|
+
- atlasctl__confluence page get
|
|
46
|
+
- atlasctl__config set
|
|
47
|
+
- atlasctl__config get
|
|
48
|
+
- mytool__convert
|
|
49
|
+
...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Drop it into your Claude Desktop config and it works like any other MCP server. The bridge reads `--describe`, translates commands to MCP tools, and shells out to the real CLI when the host calls a tool.
|
|
53
|
+
|
|
54
|
+
### Wrap an MCP server as a CLI
|
|
55
|
+
|
|
56
|
+
Atlassian ships an MCP server at `mcp.atlassian.com`. With `mtpcli wrap`, it's a CLI:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Discover what tools the server offers
|
|
60
|
+
$ mtpcli wrap --url "https://mcp.atlassian.com/v1/mcp" --describe
|
|
61
|
+
|
|
62
|
+
# Fetch a Confluence page
|
|
63
|
+
$ mtpcli wrap --url "https://mcp.atlassian.com/v1/mcp" \
|
|
64
|
+
getConfluencePage -- --cloudId "$CLOUD_ID" --pageId 12345 --contentFormat markdown
|
|
65
|
+
|
|
66
|
+
# Pipe it into jq, grep, or anything else
|
|
67
|
+
$ mtpcli wrap --url "https://mcp.atlassian.com/v1/mcp" \
|
|
68
|
+
getConfluencePage -- --cloudId "$CLOUD_ID" --pageId 12345 --contentFormat markdown \
|
|
69
|
+
| jq -r '.body'
|
|
70
|
+
|
|
71
|
+
# Works with stdio servers too
|
|
72
|
+
$ mtpcli wrap --server "npx @mcp/server-github" --describe
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The 2,500+ MCP servers people have built? They're all CLI tools now. Pipe their output, use them in scripts, compose them with other tools.
|
|
76
|
+
|
|
39
77
|
### Search for tools and commands
|
|
40
78
|
|
|
41
79
|
```bash
|
|
@@ -52,7 +90,7 @@ mtpcli search --scan-path "git commit"
|
|
|
52
90
|
### Authenticate with a tool
|
|
53
91
|
|
|
54
92
|
```bash
|
|
55
|
-
# OAuth2 login
|
|
93
|
+
# OAuth2 login (opens browser, handles callback)
|
|
56
94
|
mtpcli auth login mytool
|
|
57
95
|
|
|
58
96
|
# API key / bearer token login
|
|
@@ -68,22 +106,7 @@ eval $(mtpcli auth env mytool)
|
|
|
68
106
|
mtpcli auth logout mytool
|
|
69
107
|
```
|
|
70
108
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
# Start an MCP server that bridges describe-compatible tools
|
|
75
|
-
mtpcli serve --tool mytool --tool anothertool
|
|
76
|
-
```
|
|
77
|
-
|
|
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
|
|
86
|
-
```
|
|
109
|
+
See [AUTH.md](AUTH.md) for details on token storage, usage patterns, and bridge integration.
|
|
87
110
|
|
|
88
111
|
### Validate a tool's --describe output
|
|
89
112
|
|
|
@@ -114,7 +137,7 @@ mtpcli completions fish mytool | source
|
|
|
114
137
|
### Describe self
|
|
115
138
|
|
|
116
139
|
```bash
|
|
117
|
-
#
|
|
140
|
+
# mtpcli is itself an MTP-compliant tool
|
|
118
141
|
mtpcli --describe
|
|
119
142
|
```
|
|
120
143
|
|
package/dist/index.js
CHANGED
|
@@ -8738,7 +8738,7 @@ function argToJsonSchema(arg) {
|
|
|
8738
8738
|
return schema;
|
|
8739
8739
|
}
|
|
8740
8740
|
function commandToMcpTool(toolName, cmd) {
|
|
8741
|
-
const mcpName = cmd.name === "_root" ? toolName : `${toolName}__${cmd.name}`;
|
|
8741
|
+
const mcpName = cmd.name === "_root" ? toolName : `${toolName}__${cmd.name.replace(/ /g, "_")}`;
|
|
8742
8742
|
const properties = {};
|
|
8743
8743
|
const required = [];
|
|
8744
8744
|
for (const arg of cmd.args) {
|
|
@@ -8772,7 +8772,7 @@ function commandToMcpTool(toolName, cmd) {
|
|
|
8772
8772
|
function buildCliCommand(toolName, commandName, arguments_, commandSchema) {
|
|
8773
8773
|
const cmd = [toolName];
|
|
8774
8774
|
if (commandName !== "_root")
|
|
8775
|
-
cmd.push(commandName);
|
|
8775
|
+
cmd.push(...commandName.split(" "));
|
|
8776
8776
|
const argSchemas = new Map;
|
|
8777
8777
|
for (const a of commandSchema.args) {
|
|
8778
8778
|
argSchemas.set(a.name.replace(/^-+/, ""), a);
|
|
@@ -8826,29 +8826,57 @@ function invokeCliTool(toolName, commandName, arguments_, commandSchema, authEnv
|
|
|
8826
8826
|
stdio: [stdinStr !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
|
|
8827
8827
|
env: { ...process.env, ...authEnv }
|
|
8828
8828
|
});
|
|
8829
|
+
const timer = setTimeout(() => {
|
|
8830
|
+
child.kill();
|
|
8831
|
+
resolve({
|
|
8832
|
+
content: [{ type: "text", text: `Error: tool timed out after ${TOOL_TIMEOUT_MS / 1000}s` }],
|
|
8833
|
+
isError: true
|
|
8834
|
+
});
|
|
8835
|
+
}, TOOL_TIMEOUT_MS);
|
|
8829
8836
|
if (stdinStr !== undefined && child.stdin) {
|
|
8830
8837
|
child.stdin.write(stdinStr);
|
|
8831
8838
|
child.stdin.end();
|
|
8832
8839
|
}
|
|
8833
8840
|
let stdout = "";
|
|
8834
8841
|
let stderr = "";
|
|
8835
|
-
|
|
8836
|
-
|
|
8842
|
+
let stdoutBytes = 0;
|
|
8843
|
+
let stderrBytes = 0;
|
|
8844
|
+
let truncated = false;
|
|
8845
|
+
child.stdout?.on("data", (d) => {
|
|
8846
|
+
stdoutBytes += d.length;
|
|
8847
|
+
if (stdoutBytes <= MAX_OUTPUT_BYTES) {
|
|
8848
|
+
stdout += d;
|
|
8849
|
+
} else if (!truncated) {
|
|
8850
|
+
truncated = true;
|
|
8851
|
+
child.kill();
|
|
8852
|
+
}
|
|
8853
|
+
});
|
|
8854
|
+
child.stderr?.on("data", (d) => {
|
|
8855
|
+
stderrBytes += d.length;
|
|
8856
|
+
if (stderrBytes <= MAX_OUTPUT_BYTES) {
|
|
8857
|
+
stderr += d;
|
|
8858
|
+
}
|
|
8859
|
+
});
|
|
8837
8860
|
child.on("error", (e) => {
|
|
8861
|
+
clearTimeout(timer);
|
|
8838
8862
|
resolve({
|
|
8839
8863
|
content: [{ type: "text", text: `Error: ${e.message}` }],
|
|
8840
8864
|
isError: true
|
|
8841
8865
|
});
|
|
8842
8866
|
});
|
|
8843
8867
|
child.on("close", (code) => {
|
|
8868
|
+
clearTimeout(timer);
|
|
8844
8869
|
const content = [];
|
|
8870
|
+
if (truncated) {
|
|
8871
|
+
content.push({ type: "text", text: `[truncated: output exceeded ${MAX_OUTPUT_BYTES / 1024 / 1024}MB]` });
|
|
8872
|
+
}
|
|
8845
8873
|
if (stdout.trim())
|
|
8846
8874
|
content.push({ type: "text", text: stdout.trim() });
|
|
8847
8875
|
if (stderr.trim())
|
|
8848
8876
|
content.push({ type: "text", text: `[stderr] ${stderr.trim()}` });
|
|
8849
8877
|
if (content.length === 0)
|
|
8850
8878
|
content.push({ type: "text", text: "(no output)" });
|
|
8851
|
-
resolve({ content, isError: code !== 0 });
|
|
8879
|
+
resolve({ content, isError: code !== 0 || truncated });
|
|
8852
8880
|
});
|
|
8853
8881
|
});
|
|
8854
8882
|
}
|
|
@@ -8948,13 +8976,14 @@ async function run(toolNames) {
|
|
|
8948
8976
|
}
|
|
8949
8977
|
}
|
|
8950
8978
|
}
|
|
8951
|
-
var execFileAsync7, VERSION = "0.1.0";
|
|
8979
|
+
var execFileAsync7, VERSION = "0.1.0", TOOL_TIMEOUT_MS = 60000, MAX_OUTPUT_BYTES;
|
|
8952
8980
|
var init_serve = __esm(() => {
|
|
8953
8981
|
init_auth();
|
|
8954
8982
|
init_mcp();
|
|
8955
8983
|
init_models();
|
|
8956
8984
|
init_search2();
|
|
8957
8985
|
execFileAsync7 = promisify9(execFile9);
|
|
8986
|
+
MAX_OUTPUT_BYTES = 1024 * 1024 * 1024;
|
|
8958
8987
|
});
|
|
8959
8988
|
|
|
8960
8989
|
// src/mcp-oauth.ts
|
|
@@ -10564,7 +10593,7 @@ function cleanJson(obj) {
|
|
|
10564
10593
|
}
|
|
10565
10594
|
|
|
10566
10595
|
// src/index.ts
|
|
10567
|
-
var VERSION3 = "0.3.
|
|
10596
|
+
var VERSION3 = "0.3.2";
|
|
10568
10597
|
function selfDescribe() {
|
|
10569
10598
|
const schema = {
|
|
10570
10599
|
name: "mtpcli",
|