@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.
Files changed (3) hide show
  1. package/README.md +52 -29
  2. package/dist/index.js +36 -7
  3. 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 - 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
- **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
-
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 -50 years of Unix | Requires an orchestrator or agent framework |
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 -that's the whole point of CLIs | Not really -designed for LLM interaction |
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
- MCP is the right choice when you need stateful sessions, streaming, or deep integration with an LLM host. CLIs are the right choice when you want composability, scriptability, and zero infrastructure.
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
- ### Serve tools over MCP
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
- # Output mtpcli's own describe schema (JSON)
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
- child.stdout?.on("data", (d) => stdout += d);
8836
- child.stderr?.on("data", (d) => stderr += d);
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.0";
10596
+ var VERSION3 = "0.3.2";
10568
10597
  function selfDescribe() {
10569
10598
  const schema = {
10570
10599
  name: "mtpcli",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modeltoolsprotocol/mtpcli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "bin": {