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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp2cli
3
- Version: 3.1.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp2cli"
3
- version = "3.1.0"
3
+ version = "3.2.0"
4
4
  description = "Turn any MCP server or OpenAPI spec into a CLI"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -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
- if pretty or sys.stdout.isatty():
314
- print(json.dumps(data, indent=2))
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
- print(json.dumps(data))
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
- print(f"\nNo tools matching '{search_pattern}'.")
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
- print(f"\nNo tools matching '{search_pattern}'.")
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
- print(f"\nTools matching '{search_pattern}':")
3747
- else:
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(commands, verbose=pre_args.verbose)
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
- print("Available tools:")
3757
- list_mcp_commands(commands, verbose=pre_args.verbose)
3758
- print("\nUse --list for the same output, or provide a subcommand.")
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
- print(f"\nNo tools matching '{search_pattern}'.")
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