mcp2cli 3.1.0__tar.gz → 3.2.0__tar.gz
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.
- {mcp2cli-3.1.0 → mcp2cli-3.2.0}/PKG-INFO +34 -1
- {mcp2cli-3.1.0 → mcp2cli-3.2.0}/README.md +33 -0
- {mcp2cli-3.1.0 → mcp2cli-3.2.0}/pyproject.toml +1 -1
- {mcp2cli-3.1.0 → mcp2cli-3.2.0}/src/mcp2cli/__init__.py +187 -44
- {mcp2cli-3.1.0 → mcp2cli-3.2.0}/src/mcp2cli/__main__.py +0 -0
- {mcp2cli-3.1.0 → mcp2cli-3.2.0}/src/mcp2cli/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp2cli
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Turn any MCP server or OpenAPI spec into a CLI
|
|
5
5
|
Author: Stephan Fitzpatrick
|
|
6
6
|
Author-email: Stephan Fitzpatrick <stephan@knowsuchagency.com>
|
|
@@ -236,6 +236,36 @@ mcp2cli @myapi --list --sort alpha
|
|
|
236
236
|
|
|
237
237
|
When usage data exists for a source, `--list` defaults to sorting by call frequency. Otherwise insertion order is preserved. Usage data is stored in `~/.cache/mcp2cli/usage.json`.
|
|
238
238
|
|
|
239
|
+
### JSON output
|
|
240
|
+
|
|
241
|
+
`--json` forces **valid JSON on stdout for every command**, in every mode. It is the
|
|
242
|
+
machine-readable counterpart to the human-formatted default output, designed for LLM
|
|
243
|
+
agents and scripts that need to parse results reliably.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# --list emits a JSON array of command objects (name, description, parameters, ...)
|
|
247
|
+
mcp2cli --mcp https://mcp.example.com/sse --list --json
|
|
248
|
+
mcp2cli --spec ./openapi.json --list --json
|
|
249
|
+
mcp2cli --graphql https://api.example.com/graphql --list --json
|
|
250
|
+
|
|
251
|
+
# --list --json --compact emits a JSON array of names only
|
|
252
|
+
mcp2cli --mcp https://mcp.example.com/sse --list --json --compact
|
|
253
|
+
|
|
254
|
+
# MCP tool calls emit the FULL CallToolResult envelope — including
|
|
255
|
+
# structuredContent and isError, not just the flattened text. This surfaces the
|
|
256
|
+
# machine-readable result that modern MCP tools put in structuredContent.
|
|
257
|
+
mcp2cli --mcp https://mcp.example.com/sse --json search --query "test"
|
|
258
|
+
# { "content": [...], "structuredContent": {...}, "isError": false }
|
|
259
|
+
|
|
260
|
+
# OpenAPI / GraphQL calls emit the response as JSON (non-JSON bodies become a JSON string)
|
|
261
|
+
mcp2cli --spec ./openapi.json --json list-pets
|
|
262
|
+
mcp2cli --graphql https://api.example.com/graphql --json users
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`--json` takes precedence over `--raw` and `--toon` (both of which can produce
|
|
266
|
+
non-JSON), so it always wins — that is what makes it a reliable "force JSON" switch.
|
|
267
|
+
Indentation follows the usual rule: pretty on a TTY or with `--pretty`, compact when piped.
|
|
268
|
+
|
|
239
269
|
### Output control
|
|
240
270
|
|
|
241
271
|
```bash
|
|
@@ -308,6 +338,9 @@ Options:
|
|
|
308
338
|
--fields FIELDS Override GraphQL selection set (e.g. "id name email")
|
|
309
339
|
--pretty Pretty-print JSON output
|
|
310
340
|
--raw Print raw response body
|
|
341
|
+
--json Force valid JSON output for every command (--list and tool
|
|
342
|
+
calls). MCP calls emit the full result envelope including
|
|
343
|
+
structuredContent. Takes precedence over --raw and --toon.
|
|
311
344
|
--toon Encode output as TOON (token-efficient for LLMs)
|
|
312
345
|
--head N Limit output to first N records (arrays)
|
|
313
346
|
--version Show version
|
|
@@ -217,6 +217,36 @@ mcp2cli @myapi --list --sort alpha
|
|
|
217
217
|
|
|
218
218
|
When usage data exists for a source, `--list` defaults to sorting by call frequency. Otherwise insertion order is preserved. Usage data is stored in `~/.cache/mcp2cli/usage.json`.
|
|
219
219
|
|
|
220
|
+
### JSON output
|
|
221
|
+
|
|
222
|
+
`--json` forces **valid JSON on stdout for every command**, in every mode. It is the
|
|
223
|
+
machine-readable counterpart to the human-formatted default output, designed for LLM
|
|
224
|
+
agents and scripts that need to parse results reliably.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# --list emits a JSON array of command objects (name, description, parameters, ...)
|
|
228
|
+
mcp2cli --mcp https://mcp.example.com/sse --list --json
|
|
229
|
+
mcp2cli --spec ./openapi.json --list --json
|
|
230
|
+
mcp2cli --graphql https://api.example.com/graphql --list --json
|
|
231
|
+
|
|
232
|
+
# --list --json --compact emits a JSON array of names only
|
|
233
|
+
mcp2cli --mcp https://mcp.example.com/sse --list --json --compact
|
|
234
|
+
|
|
235
|
+
# MCP tool calls emit the FULL CallToolResult envelope — including
|
|
236
|
+
# structuredContent and isError, not just the flattened text. This surfaces the
|
|
237
|
+
# machine-readable result that modern MCP tools put in structuredContent.
|
|
238
|
+
mcp2cli --mcp https://mcp.example.com/sse --json search --query "test"
|
|
239
|
+
# { "content": [...], "structuredContent": {...}, "isError": false }
|
|
240
|
+
|
|
241
|
+
# OpenAPI / GraphQL calls emit the response as JSON (non-JSON bodies become a JSON string)
|
|
242
|
+
mcp2cli --spec ./openapi.json --json list-pets
|
|
243
|
+
mcp2cli --graphql https://api.example.com/graphql --json users
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`--json` takes precedence over `--raw` and `--toon` (both of which can produce
|
|
247
|
+
non-JSON), so it always wins — that is what makes it a reliable "force JSON" switch.
|
|
248
|
+
Indentation follows the usual rule: pretty on a TTY or with `--pretty`, compact when piped.
|
|
249
|
+
|
|
220
250
|
### Output control
|
|
221
251
|
|
|
222
252
|
```bash
|
|
@@ -289,6 +319,9 @@ Options:
|
|
|
289
319
|
--fields FIELDS Override GraphQL selection set (e.g. "id name email")
|
|
290
320
|
--pretty Pretty-print JSON output
|
|
291
321
|
--raw Print raw response body
|
|
322
|
+
--json Force valid JSON output for every command (--list and tool
|
|
323
|
+
calls). MCP calls emit the full result envelope including
|
|
324
|
+
structuredContent. Takes precedence over --raw and --toon.
|
|
292
325
|
--toon Encode output as TOON (token-efficient for LLMs)
|
|
293
326
|
--head N Limit output to first N records (arrays)
|
|
294
327
|
--version Show version
|
|
@@ -277,6 +277,14 @@ def _apply_head(data, n: int):
|
|
|
277
277
|
return data
|
|
278
278
|
|
|
279
279
|
|
|
280
|
+
def _emit_json(data, pretty: bool = False) -> None:
|
|
281
|
+
"""Print *data* as JSON. Indented when *pretty* or stdout is a TTY, else compact."""
|
|
282
|
+
if pretty or sys.stdout.isatty():
|
|
283
|
+
print(json.dumps(data, indent=2))
|
|
284
|
+
else:
|
|
285
|
+
print(json.dumps(data))
|
|
286
|
+
|
|
287
|
+
|
|
280
288
|
def output_result(
|
|
281
289
|
data,
|
|
282
290
|
*,
|
|
@@ -284,7 +292,22 @@ def output_result(
|
|
|
284
292
|
raw: bool = False,
|
|
285
293
|
toon: bool = False,
|
|
286
294
|
head: int | None = None,
|
|
295
|
+
json_output: bool = False,
|
|
287
296
|
):
|
|
297
|
+
# --json forces valid JSON for every command, overriding --raw and --toon
|
|
298
|
+
# (both of which can produce non-JSON). Strings that contain JSON are
|
|
299
|
+
# unwrapped; everything else is emitted as a JSON value (e.g. a string
|
|
300
|
+
# literal for plain prose), guaranteeing parseable stdout.
|
|
301
|
+
if json_output:
|
|
302
|
+
if isinstance(data, str):
|
|
303
|
+
try:
|
|
304
|
+
data = json.loads(data)
|
|
305
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
306
|
+
pass
|
|
307
|
+
if head is not None:
|
|
308
|
+
data = _apply_head(data, head)
|
|
309
|
+
_emit_json(data, pretty)
|
|
310
|
+
return
|
|
288
311
|
if raw:
|
|
289
312
|
if isinstance(data, str):
|
|
290
313
|
print(data)
|
|
@@ -310,10 +333,53 @@ def output_result(
|
|
|
310
333
|
file=sys.stderr,
|
|
311
334
|
)
|
|
312
335
|
# Fall through to normal output
|
|
313
|
-
|
|
314
|
-
|
|
336
|
+
_emit_json(data, pretty)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _python_type_name(t: type | None) -> str:
|
|
340
|
+
"""Human/JSON-friendly type label for a ParamDef.python_type (None = boolean flag)."""
|
|
341
|
+
if t is None:
|
|
342
|
+
return "boolean"
|
|
343
|
+
return getattr(t, "__name__", str(t))
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _param_to_dict(p: "ParamDef") -> dict:
|
|
347
|
+
d = {
|
|
348
|
+
"name": p.name,
|
|
349
|
+
"type": _python_type_name(p.python_type),
|
|
350
|
+
"required": p.required,
|
|
351
|
+
"description": p.description,
|
|
352
|
+
"location": p.location,
|
|
353
|
+
}
|
|
354
|
+
if p.choices:
|
|
355
|
+
d["choices"] = p.choices
|
|
356
|
+
return d
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def command_to_dict(cmd: "CommandDef") -> dict:
|
|
360
|
+
"""Serialize a CommandDef to a JSON-friendly dict for `--list --json`."""
|
|
361
|
+
d: dict = {"name": cmd.name, "description": cmd.description or ""}
|
|
362
|
+
if cmd.method:
|
|
363
|
+
d["method"] = cmd.method.upper()
|
|
364
|
+
if cmd.path:
|
|
365
|
+
d["path"] = cmd.path
|
|
366
|
+
if cmd.tool_name:
|
|
367
|
+
d["toolName"] = cmd.tool_name
|
|
368
|
+
if cmd.graphql_operation_type:
|
|
369
|
+
d["operationType"] = cmd.graphql_operation_type
|
|
370
|
+
d["parameters"] = [_param_to_dict(p) for p in cmd.params]
|
|
371
|
+
return d
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def print_commands_json(
|
|
375
|
+
commands: "list[CommandDef]", compact: bool = False, pretty: bool = False
|
|
376
|
+
) -> None:
|
|
377
|
+
"""Emit a command list as JSON (array of objects, or names when *compact*)."""
|
|
378
|
+
if compact:
|
|
379
|
+
payload = [cmd.name for cmd in commands]
|
|
315
380
|
else:
|
|
316
|
-
|
|
381
|
+
payload = [command_to_dict(cmd) for cmd in commands]
|
|
382
|
+
_emit_json(payload, pretty)
|
|
317
383
|
|
|
318
384
|
|
|
319
385
|
def _build_http_headers(auth_headers: list[tuple[str, str]], multipart: bool = False) -> dict[str, str]:
|
|
@@ -1407,10 +1473,16 @@ def list_graphql_commands(
|
|
|
1407
1473
|
source_hash: str = "",
|
|
1408
1474
|
sort_mode: str | None = None,
|
|
1409
1475
|
top: int | None = None,
|
|
1476
|
+
json_output: bool = False,
|
|
1477
|
+
pretty: bool = False,
|
|
1410
1478
|
):
|
|
1411
1479
|
"""Group commands by operation type and print."""
|
|
1412
1480
|
commands = _apply_list_options(commands, source_hash, sort_mode, top)
|
|
1413
1481
|
|
|
1482
|
+
if json_output:
|
|
1483
|
+
print_commands_json(commands, compact=compact, pretty=pretty)
|
|
1484
|
+
return
|
|
1485
|
+
|
|
1414
1486
|
if compact:
|
|
1415
1487
|
print(" ".join(cmd.name for cmd in commands))
|
|
1416
1488
|
return
|
|
@@ -1502,6 +1574,7 @@ def execute_graphql(
|
|
|
1502
1574
|
fields_override: str | None = None,
|
|
1503
1575
|
oauth_provider: "httpx.Auth | None" = None,
|
|
1504
1576
|
head: int | None = None,
|
|
1577
|
+
json_output: bool = False,
|
|
1505
1578
|
):
|
|
1506
1579
|
"""Build and execute a GraphQL query/mutation."""
|
|
1507
1580
|
document, variables, field_name = _build_graphql_document(
|
|
@@ -1525,13 +1598,13 @@ def execute_graphql(
|
|
|
1525
1598
|
print(f"GraphQL error: {msgs}", file=sys.stderr)
|
|
1526
1599
|
sys.exit(1)
|
|
1527
1600
|
# Partial errors — include them in output
|
|
1528
|
-
output_result(result, pretty=pretty, raw=raw, toon=toon, head=head)
|
|
1601
|
+
output_result(result, pretty=pretty, raw=raw, toon=toon, head=head, json_output=json_output)
|
|
1529
1602
|
return
|
|
1530
1603
|
|
|
1531
1604
|
data = result.get("data", {})
|
|
1532
1605
|
# Extract the specific field's data
|
|
1533
1606
|
field_data = data.get(field_name, data)
|
|
1534
|
-
output_result(field_data, pretty=pretty, raw=raw, toon=toon, head=head)
|
|
1607
|
+
output_result(field_data, pretty=pretty, raw=raw, toon=toon, head=head, json_output=json_output)
|
|
1535
1608
|
|
|
1536
1609
|
|
|
1537
1610
|
def handle_graphql(
|
|
@@ -1552,6 +1625,7 @@ def handle_graphql(
|
|
|
1552
1625
|
sort_mode: str | None = None,
|
|
1553
1626
|
top: int | None = None,
|
|
1554
1627
|
compact: bool = False,
|
|
1628
|
+
json_output: bool = False,
|
|
1555
1629
|
):
|
|
1556
1630
|
"""Top-level handler for --graphql mode."""
|
|
1557
1631
|
src_hash = _source_hash_for(url)
|
|
@@ -1561,6 +1635,7 @@ def handle_graphql(
|
|
|
1561
1635
|
list_kwargs = dict(
|
|
1562
1636
|
verbose=verbose, compact=compact,
|
|
1563
1637
|
source_hash=src_hash, sort_mode=sort_mode, top=top,
|
|
1638
|
+
json_output=json_output, pretty=pretty,
|
|
1564
1639
|
)
|
|
1565
1640
|
|
|
1566
1641
|
if list_mode:
|
|
@@ -1568,10 +1643,10 @@ def handle_graphql(
|
|
|
1568
1643
|
return
|
|
1569
1644
|
|
|
1570
1645
|
if not remaining:
|
|
1571
|
-
if not compact:
|
|
1646
|
+
if not compact and not json_output:
|
|
1572
1647
|
print("Available operations:")
|
|
1573
1648
|
list_graphql_commands(commands, **list_kwargs)
|
|
1574
|
-
if not compact:
|
|
1649
|
+
if not compact and not json_output:
|
|
1575
1650
|
print("\nUse --list for the same output, or provide a subcommand.")
|
|
1576
1651
|
return
|
|
1577
1652
|
|
|
@@ -1587,6 +1662,7 @@ def handle_graphql(
|
|
|
1587
1662
|
execute_graphql(
|
|
1588
1663
|
args, cmd, url, schema, auth_headers, pretty, raw, toon=toon,
|
|
1589
1664
|
fields_override=fields_override, oauth_provider=oauth_provider,
|
|
1665
|
+
json_output=json_output,
|
|
1590
1666
|
)
|
|
1591
1667
|
|
|
1592
1668
|
# Record usage after successful execution
|
|
@@ -2040,9 +2116,15 @@ def list_openapi_commands(
|
|
|
2040
2116
|
source_hash: str = "",
|
|
2041
2117
|
sort_mode: str | None = None,
|
|
2042
2118
|
top: int | None = None,
|
|
2119
|
+
json_output: bool = False,
|
|
2120
|
+
pretty: bool = False,
|
|
2043
2121
|
):
|
|
2044
2122
|
commands = _apply_list_options(commands, source_hash, sort_mode, top)
|
|
2045
2123
|
|
|
2124
|
+
if json_output:
|
|
2125
|
+
print_commands_json(commands, compact=compact, pretty=pretty)
|
|
2126
|
+
return
|
|
2127
|
+
|
|
2046
2128
|
if compact:
|
|
2047
2129
|
print(" ".join(cmd.name for cmd in commands))
|
|
2048
2130
|
return
|
|
@@ -2073,9 +2155,15 @@ def list_mcp_commands(
|
|
|
2073
2155
|
source_hash: str = "",
|
|
2074
2156
|
sort_mode: str | None = None,
|
|
2075
2157
|
top: int | None = None,
|
|
2158
|
+
json_output: bool = False,
|
|
2159
|
+
pretty: bool = False,
|
|
2076
2160
|
):
|
|
2077
2161
|
commands = _apply_list_options(commands, source_hash, sort_mode, top)
|
|
2078
2162
|
|
|
2163
|
+
if json_output:
|
|
2164
|
+
print_commands_json(commands, compact=compact, pretty=pretty)
|
|
2165
|
+
return
|
|
2166
|
+
|
|
2079
2167
|
if compact:
|
|
2080
2168
|
print(" ".join(cmd.name for cmd in commands))
|
|
2081
2169
|
return
|
|
@@ -2186,6 +2274,7 @@ def execute_openapi(
|
|
|
2186
2274
|
toon: bool = False,
|
|
2187
2275
|
oauth_provider: "httpx.Auth | None" = None,
|
|
2188
2276
|
head: int | None = None,
|
|
2277
|
+
json_output: bool = False,
|
|
2189
2278
|
):
|
|
2190
2279
|
path, query_params, extra_headers, body, files = _collect_openapi_params(cmd, args)
|
|
2191
2280
|
url = base_url.rstrip("/") + path
|
|
@@ -2227,6 +2316,14 @@ def execute_openapi(
|
|
|
2227
2316
|
for _, file_tuple in files.items():
|
|
2228
2317
|
file_tuple[1].close()
|
|
2229
2318
|
|
|
2319
|
+
if json_output:
|
|
2320
|
+
try:
|
|
2321
|
+
data = resp.json()
|
|
2322
|
+
except Exception:
|
|
2323
|
+
data = resp.text
|
|
2324
|
+
output_result(data, pretty=pretty, head=head, json_output=True)
|
|
2325
|
+
return
|
|
2326
|
+
|
|
2230
2327
|
if raw:
|
|
2231
2328
|
sys.stdout.buffer.write(resp.content)
|
|
2232
2329
|
return
|
|
@@ -2271,6 +2368,7 @@ def run_mcp_http(
|
|
|
2271
2368
|
top: int | None = None,
|
|
2272
2369
|
compact: bool = False,
|
|
2273
2370
|
source_hash: str = "",
|
|
2371
|
+
json_output: bool = False,
|
|
2274
2372
|
):
|
|
2275
2373
|
extra = dict(
|
|
2276
2374
|
resource_action=resource_action,
|
|
@@ -2285,6 +2383,7 @@ def run_mcp_http(
|
|
|
2285
2383
|
top=top,
|
|
2286
2384
|
compact=compact,
|
|
2287
2385
|
source_hash=source_hash,
|
|
2386
|
+
json_output=json_output,
|
|
2288
2387
|
)
|
|
2289
2388
|
|
|
2290
2389
|
async def _run():
|
|
@@ -2374,6 +2473,7 @@ def run_mcp_stdio(
|
|
|
2374
2473
|
top: int | None = None,
|
|
2375
2474
|
compact: bool = False,
|
|
2376
2475
|
source_hash: str = "",
|
|
2476
|
+
json_output: bool = False,
|
|
2377
2477
|
):
|
|
2378
2478
|
extra = dict(
|
|
2379
2479
|
resource_action=resource_action,
|
|
@@ -2388,6 +2488,7 @@ def run_mcp_stdio(
|
|
|
2388
2488
|
top=top,
|
|
2389
2489
|
compact=compact,
|
|
2390
2490
|
source_hash=source_hash,
|
|
2491
|
+
json_output=json_output,
|
|
2391
2492
|
)
|
|
2392
2493
|
|
|
2393
2494
|
import anyio
|
|
@@ -2443,11 +2544,13 @@ async def _mcp_session(
|
|
|
2443
2544
|
top: int | None = None,
|
|
2444
2545
|
compact: bool = False,
|
|
2445
2546
|
source_hash: str = "",
|
|
2547
|
+
json_output: bool = False,
|
|
2446
2548
|
):
|
|
2447
2549
|
# Handle resource operations
|
|
2448
2550
|
if resource_action:
|
|
2449
2551
|
await _handle_resources(
|
|
2450
2552
|
session, resource_action, resource_uri, pretty, raw, toon, head=head,
|
|
2553
|
+
json_output=json_output,
|
|
2451
2554
|
)
|
|
2452
2555
|
return
|
|
2453
2556
|
|
|
@@ -2455,13 +2558,14 @@ async def _mcp_session(
|
|
|
2455
2558
|
if prompt_action:
|
|
2456
2559
|
await _handle_prompts(
|
|
2457
2560
|
session, prompt_action, prompt_name, prompt_arguments,
|
|
2458
|
-
pretty, raw, toon, head=head,
|
|
2561
|
+
pretty, raw, toon, head=head, json_output=json_output,
|
|
2459
2562
|
)
|
|
2460
2563
|
return
|
|
2461
2564
|
|
|
2462
2565
|
list_kwargs = dict(
|
|
2463
2566
|
verbose=verbose, compact=compact,
|
|
2464
2567
|
source_hash=source_hash, sort_mode=sort_mode, top=top,
|
|
2568
|
+
json_output=json_output, pretty=pretty,
|
|
2465
2569
|
)
|
|
2466
2570
|
|
|
2467
2571
|
if list_mode:
|
|
@@ -2478,12 +2582,15 @@ async def _mcp_session(
|
|
|
2478
2582
|
if search_pattern:
|
|
2479
2583
|
commands = _filter_commands(commands, search_pattern)
|
|
2480
2584
|
if not commands:
|
|
2481
|
-
|
|
2585
|
+
if json_output:
|
|
2586
|
+
list_mcp_commands(commands, **list_kwargs)
|
|
2587
|
+
else:
|
|
2588
|
+
print(f"\nNo tools matching '{search_pattern}'.")
|
|
2482
2589
|
return
|
|
2483
|
-
if not compact:
|
|
2590
|
+
if not compact and not json_output:
|
|
2484
2591
|
print(f"\nTools matching '{search_pattern}':")
|
|
2485
2592
|
else:
|
|
2486
|
-
if not compact:
|
|
2593
|
+
if not compact and not json_output:
|
|
2487
2594
|
print("\nAvailable tools:")
|
|
2488
2595
|
list_mcp_commands(commands, **list_kwargs)
|
|
2489
2596
|
return
|
|
@@ -2497,6 +2604,12 @@ async def _mcp_session(
|
|
|
2497
2604
|
|
|
2498
2605
|
result = await session.call_tool(tool_name, arguments or {})
|
|
2499
2606
|
|
|
2607
|
+
if json_output:
|
|
2608
|
+
# Emit the full MCP CallToolResult envelope (content, structuredContent,
|
|
2609
|
+
# isError) using the SDK's own serializer — 100% MCP-compatible.
|
|
2610
|
+
output_result(result.model_dump(mode="json"), pretty=pretty, head=head, json_output=True)
|
|
2611
|
+
return
|
|
2612
|
+
|
|
2500
2613
|
text = _extract_content_parts(result.content)
|
|
2501
2614
|
output_result(text, pretty=pretty, raw=raw, toon=toon, head=head)
|
|
2502
2615
|
|
|
@@ -2514,8 +2627,9 @@ async def _handle_resources(
|
|
|
2514
2627
|
raw: bool,
|
|
2515
2628
|
toon: bool,
|
|
2516
2629
|
head: int | None = None,
|
|
2630
|
+
json_output: bool = False,
|
|
2517
2631
|
):
|
|
2518
|
-
_out = dict(pretty=pretty, raw=raw, toon=toon, head=head)
|
|
2632
|
+
_out = dict(pretty=pretty, raw=raw, toon=toon, head=head, json_output=json_output)
|
|
2519
2633
|
if action == "list":
|
|
2520
2634
|
result = await session.list_resources()
|
|
2521
2635
|
data = [
|
|
@@ -2568,8 +2682,9 @@ async def _handle_prompts(
|
|
|
2568
2682
|
raw: bool,
|
|
2569
2683
|
toon: bool,
|
|
2570
2684
|
head: int | None = None,
|
|
2685
|
+
json_output: bool = False,
|
|
2571
2686
|
):
|
|
2572
|
-
_out = dict(pretty=pretty, raw=raw, toon=toon, head=head)
|
|
2687
|
+
_out = dict(pretty=pretty, raw=raw, toon=toon, head=head, json_output=json_output)
|
|
2573
2688
|
if action == "list":
|
|
2574
2689
|
result = await session.list_prompts()
|
|
2575
2690
|
data = [
|
|
@@ -3110,6 +3225,7 @@ def handle_mcp(
|
|
|
3110
3225
|
sort_mode: str | None = None,
|
|
3111
3226
|
top: int | None = None,
|
|
3112
3227
|
compact: bool = False,
|
|
3228
|
+
json_output: bool = False,
|
|
3113
3229
|
):
|
|
3114
3230
|
# Build a config dict for cache key generation (future-proof)
|
|
3115
3231
|
config_for_cache = {
|
|
@@ -3132,6 +3248,7 @@ def handle_mcp(
|
|
|
3132
3248
|
prompt_name=prompt_name,
|
|
3133
3249
|
prompt_arguments=prompt_arguments,
|
|
3134
3250
|
head=head,
|
|
3251
|
+
json_output=json_output,
|
|
3135
3252
|
)
|
|
3136
3253
|
_dispatch_mcp_call(
|
|
3137
3254
|
source, is_stdio, auth_headers, env_vars,
|
|
@@ -3144,6 +3261,7 @@ def handle_mcp(
|
|
|
3144
3261
|
list_kwargs = dict(
|
|
3145
3262
|
verbose=verbose, compact=compact,
|
|
3146
3263
|
source_hash=src_hash, sort_mode=sort_mode, top=top,
|
|
3264
|
+
json_output=json_output, pretty=pretty,
|
|
3147
3265
|
)
|
|
3148
3266
|
|
|
3149
3267
|
if list_mode:
|
|
@@ -3157,7 +3275,7 @@ def handle_mcp(
|
|
|
3157
3275
|
commands = filter_commands(
|
|
3158
3276
|
commands, bake_config.include, bake_config.exclude, bake_config.methods,
|
|
3159
3277
|
)
|
|
3160
|
-
if not compact:
|
|
3278
|
+
if not compact and not json_output:
|
|
3161
3279
|
print("\nAvailable tools:")
|
|
3162
3280
|
list_mcp_commands(commands, **list_kwargs)
|
|
3163
3281
|
return
|
|
@@ -3169,6 +3287,7 @@ def handle_mcp(
|
|
|
3169
3287
|
verbose=verbose,
|
|
3170
3288
|
sort_mode=sort_mode, top=top, compact=compact,
|
|
3171
3289
|
source_hash=src_hash,
|
|
3290
|
+
json_output=json_output,
|
|
3172
3291
|
)
|
|
3173
3292
|
return
|
|
3174
3293
|
|
|
@@ -3185,10 +3304,10 @@ def handle_mcp(
|
|
|
3185
3304
|
)
|
|
3186
3305
|
|
|
3187
3306
|
if not remaining:
|
|
3188
|
-
if not compact:
|
|
3307
|
+
if not compact and not json_output:
|
|
3189
3308
|
print("Available tools:")
|
|
3190
3309
|
list_mcp_commands(commands, **list_kwargs)
|
|
3191
|
-
if not compact:
|
|
3310
|
+
if not compact and not json_output:
|
|
3192
3311
|
print("\nUse --list for the same output, or provide a subcommand.")
|
|
3193
3312
|
return
|
|
3194
3313
|
|
|
@@ -3215,6 +3334,7 @@ def handle_mcp(
|
|
|
3215
3334
|
source, is_stdio, auth_headers, env_vars,
|
|
3216
3335
|
cmd.tool_name, arguments, False, pretty, raw, key, ttl, refresh,
|
|
3217
3336
|
toon=toon, transport=transport, oauth_provider=oauth_provider,
|
|
3337
|
+
json_output=json_output,
|
|
3218
3338
|
)
|
|
3219
3339
|
|
|
3220
3340
|
# Record usage after successful execution
|
|
@@ -3432,6 +3552,16 @@ def _build_main_parser() -> argparse.ArgumentParser:
|
|
|
3432
3552
|
)
|
|
3433
3553
|
pre.add_argument("--pretty", action="store_true", help="Pretty-print JSON output")
|
|
3434
3554
|
pre.add_argument("--raw", action="store_true", help="Print raw response body")
|
|
3555
|
+
pre.add_argument(
|
|
3556
|
+
"--json",
|
|
3557
|
+
action="store_true",
|
|
3558
|
+
dest="json_output",
|
|
3559
|
+
help=(
|
|
3560
|
+
"Force valid JSON output for every command. --list emits a JSON array of "
|
|
3561
|
+
"commands; MCP tool calls emit the full result envelope including "
|
|
3562
|
+
"structuredContent and isError. Takes precedence over --raw and --toon."
|
|
3563
|
+
),
|
|
3564
|
+
)
|
|
3435
3565
|
pre.add_argument(
|
|
3436
3566
|
"--toon",
|
|
3437
3567
|
action="store_true",
|
|
@@ -3693,32 +3823,28 @@ def _handle_session_operations(
|
|
|
3693
3823
|
|
|
3694
3824
|
# --- Session client mode ---
|
|
3695
3825
|
sess_name = pre_args.session
|
|
3826
|
+
_sess_out = dict(
|
|
3827
|
+
pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3828
|
+
json_output=pre_args.json_output,
|
|
3829
|
+
)
|
|
3696
3830
|
|
|
3697
3831
|
if pre_args.list_resources:
|
|
3698
3832
|
result = _session_request(sess_name, "list_resources")
|
|
3699
|
-
output_result(
|
|
3700
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3701
|
-
)
|
|
3833
|
+
output_result(result, **_sess_out)
|
|
3702
3834
|
return True
|
|
3703
3835
|
if pre_args.list_resource_templates:
|
|
3704
3836
|
result = _session_request(sess_name, "list_resource_templates")
|
|
3705
|
-
output_result(
|
|
3706
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3707
|
-
)
|
|
3837
|
+
output_result(result, **_sess_out)
|
|
3708
3838
|
return True
|
|
3709
3839
|
if pre_args.read_resource:
|
|
3710
3840
|
result = _session_request(
|
|
3711
3841
|
sess_name, "read_resource", {"uri": pre_args.read_resource}
|
|
3712
3842
|
)
|
|
3713
|
-
output_result(
|
|
3714
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3715
|
-
)
|
|
3843
|
+
output_result(result, **_sess_out)
|
|
3716
3844
|
return True
|
|
3717
3845
|
if pre_args.list_prompts:
|
|
3718
3846
|
result = _session_request(sess_name, "list_prompts")
|
|
3719
|
-
output_result(
|
|
3720
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3721
|
-
)
|
|
3847
|
+
output_result(result, **_sess_out)
|
|
3722
3848
|
return True
|
|
3723
3849
|
if pre_args.get_prompt:
|
|
3724
3850
|
p_args = {}
|
|
@@ -3731,9 +3857,7 @@ def _handle_session_operations(
|
|
|
3731
3857
|
"get_prompt",
|
|
3732
3858
|
{"name": pre_args.get_prompt, "arguments": p_args},
|
|
3733
3859
|
)
|
|
3734
|
-
output_result(
|
|
3735
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3736
|
-
)
|
|
3860
|
+
output_result(result, **_sess_out)
|
|
3737
3861
|
return True
|
|
3738
3862
|
if pre_args.list_commands:
|
|
3739
3863
|
result = _session_request(sess_name, "list_tools")
|
|
@@ -3741,21 +3865,36 @@ def _handle_session_operations(
|
|
|
3741
3865
|
if search_pattern:
|
|
3742
3866
|
commands = _filter_commands(commands, search_pattern)
|
|
3743
3867
|
if not commands:
|
|
3744
|
-
|
|
3868
|
+
if pre_args.json_output:
|
|
3869
|
+
list_mcp_commands(
|
|
3870
|
+
commands, verbose=pre_args.verbose,
|
|
3871
|
+
json_output=True, pretty=pre_args.pretty,
|
|
3872
|
+
)
|
|
3873
|
+
else:
|
|
3874
|
+
print(f"\nNo tools matching '{search_pattern}'.")
|
|
3745
3875
|
return True
|
|
3746
|
-
|
|
3747
|
-
|
|
3876
|
+
if not pre_args.json_output:
|
|
3877
|
+
print(f"\nTools matching '{search_pattern}':")
|
|
3878
|
+
elif not pre_args.json_output:
|
|
3748
3879
|
print("\nAvailable tools:")
|
|
3749
|
-
list_mcp_commands(
|
|
3880
|
+
list_mcp_commands(
|
|
3881
|
+
commands, verbose=pre_args.verbose,
|
|
3882
|
+
json_output=pre_args.json_output, pretty=pre_args.pretty,
|
|
3883
|
+
)
|
|
3750
3884
|
return True
|
|
3751
3885
|
|
|
3752
3886
|
# Tool call via session
|
|
3753
3887
|
if not remaining:
|
|
3754
3888
|
result = _session_request(sess_name, "list_tools")
|
|
3755
3889
|
commands = extract_mcp_commands(result)
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3890
|
+
if not pre_args.json_output:
|
|
3891
|
+
print("Available tools:")
|
|
3892
|
+
list_mcp_commands(
|
|
3893
|
+
commands, verbose=pre_args.verbose,
|
|
3894
|
+
json_output=pre_args.json_output, pretty=pre_args.pretty,
|
|
3895
|
+
)
|
|
3896
|
+
if not pre_args.json_output:
|
|
3897
|
+
print("\nUse --list for the same output, or provide a subcommand.")
|
|
3759
3898
|
return True
|
|
3760
3899
|
|
|
3761
3900
|
tools = _session_request(sess_name, "list_tools")
|
|
@@ -3781,9 +3920,7 @@ def _handle_session_operations(
|
|
|
3781
3920
|
result = _session_request(
|
|
3782
3921
|
sess_name, "call_tool", {"name": cmd.tool_name, "arguments": arguments}
|
|
3783
3922
|
)
|
|
3784
|
-
output_result(
|
|
3785
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon
|
|
3786
|
-
)
|
|
3923
|
+
output_result(result, **_sess_out)
|
|
3787
3924
|
return True
|
|
3788
3925
|
|
|
3789
3926
|
|
|
@@ -3848,15 +3985,19 @@ def _handle_openapi_mode(
|
|
|
3848
3985
|
list_kwargs = dict(
|
|
3849
3986
|
verbose=pre_args.verbose, compact=pre_args.compact,
|
|
3850
3987
|
source_hash=src_hash, sort_mode=pre_args.sort_mode, top=pre_args.top,
|
|
3988
|
+
json_output=pre_args.json_output, pretty=pre_args.pretty,
|
|
3851
3989
|
)
|
|
3852
3990
|
|
|
3853
3991
|
if pre_args.list_commands:
|
|
3854
3992
|
if search_pattern:
|
|
3855
3993
|
commands = _filter_commands(commands, search_pattern)
|
|
3856
3994
|
if not commands:
|
|
3857
|
-
|
|
3995
|
+
if not pre_args.json_output:
|
|
3996
|
+
print(f"\nNo tools matching '{search_pattern}'.")
|
|
3997
|
+
else:
|
|
3998
|
+
list_openapi_commands(commands, **list_kwargs)
|
|
3858
3999
|
return
|
|
3859
|
-
if not pre_args.compact:
|
|
4000
|
+
if not pre_args.compact and not pre_args.json_output:
|
|
3860
4001
|
print(f"\nTools matching '{search_pattern}':")
|
|
3861
4002
|
list_openapi_commands(commands, **list_kwargs)
|
|
3862
4003
|
return
|
|
@@ -3900,7 +4041,7 @@ def _handle_openapi_mode(
|
|
|
3900
4041
|
execute_openapi(
|
|
3901
4042
|
args, cmd, base_url, auth_headers,
|
|
3902
4043
|
pre_args.pretty, pre_args.raw, toon=pre_args.toon,
|
|
3903
|
-
oauth_provider=oauth_provider,
|
|
4044
|
+
oauth_provider=oauth_provider, json_output=pre_args.json_output,
|
|
3904
4045
|
)
|
|
3905
4046
|
|
|
3906
4047
|
# Record usage after successful execution
|
|
@@ -3960,6 +4101,7 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
3960
4101
|
sort_mode=pre_args.sort_mode,
|
|
3961
4102
|
top=pre_args.top,
|
|
3962
4103
|
compact=pre_args.compact,
|
|
4104
|
+
json_output=pre_args.json_output,
|
|
3963
4105
|
)
|
|
3964
4106
|
return
|
|
3965
4107
|
|
|
@@ -3994,6 +4136,7 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
3994
4136
|
sort_mode=pre_args.sort_mode,
|
|
3995
4137
|
top=pre_args.top,
|
|
3996
4138
|
compact=pre_args.compact,
|
|
4139
|
+
json_output=pre_args.json_output,
|
|
3997
4140
|
)
|
|
3998
4141
|
return
|
|
3999
4142
|
|
|
File without changes
|
|
File without changes
|