mcp2cli 3.1.0__tar.gz → 3.3.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.3.0}/PKG-INFO +34 -1
- {mcp2cli-3.1.0 → mcp2cli-3.3.0}/README.md +33 -0
- {mcp2cli-3.1.0 → mcp2cli-3.3.0}/pyproject.toml +1 -1
- {mcp2cli-3.1.0 → mcp2cli-3.3.0}/src/mcp2cli/__init__.py +317 -55
- {mcp2cli-3.1.0 → mcp2cli-3.3.0}/src/mcp2cli/__main__.py +0 -0
- {mcp2cli-3.1.0 → mcp2cli-3.3.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.3.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]:
|
|
@@ -602,6 +668,62 @@ def _find_free_port() -> int:
|
|
|
602
668
|
return s.getsockname()[1]
|
|
603
669
|
|
|
604
670
|
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def _get_cached_redirect_uri(storage: "FileTokenStorage") -> str | None:
|
|
674
|
+
"""Return the cached client's redirect_uri, if any.
|
|
675
|
+
|
|
676
|
+
Reads client.json from disk without async. Returns None when
|
|
677
|
+
no cached client exists or the redirect_uri cannot be parsed.
|
|
678
|
+
|
|
679
|
+
Used by build_oauth_provider to reuse the registered redirect
|
|
680
|
+
port from a prior DCR run (issue #54).
|
|
681
|
+
"""
|
|
682
|
+
if not storage._client_path.exists():
|
|
683
|
+
return None
|
|
684
|
+
try:
|
|
685
|
+
data = json.loads(storage._client_path.read_text())
|
|
686
|
+
uris = data.get("redirect_uris") or []
|
|
687
|
+
if uris:
|
|
688
|
+
return uris[0]
|
|
689
|
+
except Exception:
|
|
690
|
+
pass
|
|
691
|
+
return None
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def _port_available(host: str, port: int) -> bool:
|
|
695
|
+
"""Check whether *port* is free on *host*.
|
|
696
|
+
|
|
697
|
+
Note: this is a best-effort probe — a TOCTOU race exists where another
|
|
698
|
+
process could bind the port between this check and the caller's use.
|
|
699
|
+
Callers must handle bind failures gracefully.
|
|
700
|
+
"""
|
|
701
|
+
try:
|
|
702
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
703
|
+
s.bind((host, port))
|
|
704
|
+
return True
|
|
705
|
+
except OSError:
|
|
706
|
+
return False
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def _restore_token_expiry_from_sidecar(context) -> None:
|
|
710
|
+
"""Recompute ``token_expiry_time`` from the persisted ``expires_at`` sidecar.
|
|
711
|
+
|
|
712
|
+
The upstream SDK's ``_initialize`` reloads ``current_tokens`` from disk but
|
|
713
|
+
never restores ``token_expiry_time`` — the persisted ``OAuthToken`` only
|
|
714
|
+
carries the relative ``expires_in``. Without this, ``is_token_valid()``
|
|
715
|
+
treats an already-expired access token as valid on a fresh process and
|
|
716
|
+
sends a stale Bearer, wasting a 401 round-trip before re-auth (issues #50
|
|
717
|
+
and #57). Shared by the authorization-code and client-credentials
|
|
718
|
+
providers.
|
|
719
|
+
"""
|
|
720
|
+
storage = context.storage
|
|
721
|
+
if isinstance(storage, FileTokenStorage) and context.current_tokens:
|
|
722
|
+
expires_at = storage.get_expires_at()
|
|
723
|
+
if expires_at is not None:
|
|
724
|
+
context.token_expiry_time = expires_at
|
|
725
|
+
|
|
726
|
+
|
|
605
727
|
def build_oauth_provider(
|
|
606
728
|
server_url: str,
|
|
607
729
|
*,
|
|
@@ -642,7 +764,22 @@ def build_oauth_provider(
|
|
|
642
764
|
ClientCredentialsOAuthProvider,
|
|
643
765
|
)
|
|
644
766
|
|
|
645
|
-
|
|
767
|
+
class _RobustClientCredentialsProvider(ClientCredentialsOAuthProvider):
|
|
768
|
+
"""Client-credentials provider that restores expiry across restarts.
|
|
769
|
+
|
|
770
|
+
Issue #57: the upstream ``_initialize`` reloads tokens from disk
|
|
771
|
+
without recomputing ``token_expiry_time``, so an expired access
|
|
772
|
+
token looks valid after a process restart and a stale Bearer is
|
|
773
|
+
sent — triggering a 401 before the provider re-authenticates.
|
|
774
|
+
Restore the expiry from the sidecar so expired tokens are detected
|
|
775
|
+
proactively.
|
|
776
|
+
"""
|
|
777
|
+
|
|
778
|
+
async def _initialize(self) -> None:
|
|
779
|
+
await super()._initialize()
|
|
780
|
+
_restore_token_expiry_from_sidecar(self.context)
|
|
781
|
+
|
|
782
|
+
return _RobustClientCredentialsProvider(
|
|
646
783
|
server_url=server_url,
|
|
647
784
|
storage=storage,
|
|
648
785
|
client_id=client_id,
|
|
@@ -680,13 +817,19 @@ def build_oauth_provider(
|
|
|
680
817
|
|
|
681
818
|
async def _initialize(self) -> None:
|
|
682
819
|
await super()._initialize()
|
|
683
|
-
|
|
684
|
-
if isinstance(storage, FileTokenStorage) and self.context.current_tokens:
|
|
685
|
-
expires_at = storage.get_expires_at()
|
|
686
|
-
if expires_at is not None:
|
|
687
|
-
self.context.token_expiry_time = expires_at
|
|
820
|
+
_restore_token_expiry_from_sidecar(self.context)
|
|
688
821
|
|
|
689
822
|
async def _handle_refresh_response(self, response) -> bool:
|
|
823
|
+
# Issue #58: RFC 6749 §5.1 permits a refresh response to omit
|
|
824
|
+
# refresh_token, in which case the previously issued one stays
|
|
825
|
+
# valid. The upstream SDK replaces current_tokens wholesale, so
|
|
826
|
+
# refresh_token becomes None and every later refresh fails for
|
|
827
|
+
# the lack of one. Remember the old token to carry it forward.
|
|
828
|
+
old_refresh_token = (
|
|
829
|
+
self.context.current_tokens.refresh_token
|
|
830
|
+
if self.context.current_tokens
|
|
831
|
+
else None
|
|
832
|
+
)
|
|
690
833
|
ok = await super()._handle_refresh_response(response)
|
|
691
834
|
if not ok:
|
|
692
835
|
# Refresh failed. The cached DCR client_id may have been
|
|
@@ -698,6 +841,16 @@ def build_oauth_provider(
|
|
|
698
841
|
if isinstance(storage, FileTokenStorage):
|
|
699
842
|
storage.clear_client_info()
|
|
700
843
|
storage.clear_tokens()
|
|
844
|
+
return ok
|
|
845
|
+
# Carry the prior refresh token forward when the server did not
|
|
846
|
+
# issue a new one, then re-persist so it survives a restart.
|
|
847
|
+
tokens = self.context.current_tokens
|
|
848
|
+
if tokens is not None and not tokens.refresh_token and old_refresh_token:
|
|
849
|
+
tokens = tokens.model_copy(update={"refresh_token": old_refresh_token})
|
|
850
|
+
self.context.current_tokens = tokens
|
|
851
|
+
storage = self.context.storage
|
|
852
|
+
if isinstance(storage, FileTokenStorage):
|
|
853
|
+
await storage.set_tokens(tokens)
|
|
701
854
|
return ok
|
|
702
855
|
|
|
703
856
|
_LOOPBACK_HOSTS = {"localhost", "127.0.0.1", "::1"}
|
|
@@ -728,9 +881,36 @@ def build_oauth_provider(
|
|
|
728
881
|
callback_host = parsed.hostname
|
|
729
882
|
port = parsed.port
|
|
730
883
|
else:
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
884
|
+
# Issue #54: When DCR is used without an explicit redirect_uri,
|
|
885
|
+
# _find_free_port() picks a new random port on every run. If a
|
|
886
|
+
# cached client.json already exists (from a prior run), its
|
|
887
|
+
# registered redirect_uris[0] carries the ORIGINAL port. Sending
|
|
888
|
+
# an auth request with a different port causes the auth server to
|
|
889
|
+
# reject the mismatch ("callback URL is invalid").
|
|
890
|
+
#
|
|
891
|
+
# Fix: reuse the cached redirect_uri if its port is still available.
|
|
892
|
+
# If the cached port is occupied (unlikely for loopback), clear the
|
|
893
|
+
# stale client so DCR re-registers with the fresh port.
|
|
894
|
+
cached_uri = _get_cached_redirect_uri(storage)
|
|
895
|
+
if cached_uri is not None:
|
|
896
|
+
from urllib.parse import urlparse as _urlparse
|
|
897
|
+
_parsed = _urlparse(cached_uri)
|
|
898
|
+
_cached_port = _parsed.port
|
|
899
|
+
_cached_host = _parsed.hostname or "127.0.0.1"
|
|
900
|
+
if _cached_port and _port_available(_cached_host, _cached_port):
|
|
901
|
+
callback_host = _cached_host
|
|
902
|
+
port = _cached_port
|
|
903
|
+
redirect_uri = cached_uri
|
|
904
|
+
else:
|
|
905
|
+
# Port no longer free — clear stale client and re-register
|
|
906
|
+
storage.clear_client_info()
|
|
907
|
+
port = _find_free_port()
|
|
908
|
+
callback_host = "127.0.0.1"
|
|
909
|
+
redirect_uri = f"http://127.0.0.1:{port}/callback"
|
|
910
|
+
else:
|
|
911
|
+
port = _find_free_port()
|
|
912
|
+
callback_host = "127.0.0.1"
|
|
913
|
+
redirect_uri = f"http://127.0.0.1:{port}/callback"
|
|
734
914
|
|
|
735
915
|
client_metadata = OAuthClientMetadata(
|
|
736
916
|
client_name=client_name,
|
|
@@ -780,7 +960,7 @@ def build_oauth_provider(
|
|
|
780
960
|
server = HTTPServer((callback_host, port), _CallbackHandler)
|
|
781
961
|
|
|
782
962
|
async def redirect_handler(auth_url: str) -> None:
|
|
783
|
-
print(
|
|
963
|
+
print("Opening browser for authorization...", file=sys.stderr)
|
|
784
964
|
print(f"If browser doesn't open, visit: {auth_url}", file=sys.stderr)
|
|
785
965
|
webbrowser.open(auth_url)
|
|
786
966
|
|
|
@@ -1407,10 +1587,16 @@ def list_graphql_commands(
|
|
|
1407
1587
|
source_hash: str = "",
|
|
1408
1588
|
sort_mode: str | None = None,
|
|
1409
1589
|
top: int | None = None,
|
|
1590
|
+
json_output: bool = False,
|
|
1591
|
+
pretty: bool = False,
|
|
1410
1592
|
):
|
|
1411
1593
|
"""Group commands by operation type and print."""
|
|
1412
1594
|
commands = _apply_list_options(commands, source_hash, sort_mode, top)
|
|
1413
1595
|
|
|
1596
|
+
if json_output:
|
|
1597
|
+
print_commands_json(commands, compact=compact, pretty=pretty)
|
|
1598
|
+
return
|
|
1599
|
+
|
|
1414
1600
|
if compact:
|
|
1415
1601
|
print(" ".join(cmd.name for cmd in commands))
|
|
1416
1602
|
return
|
|
@@ -1502,6 +1688,7 @@ def execute_graphql(
|
|
|
1502
1688
|
fields_override: str | None = None,
|
|
1503
1689
|
oauth_provider: "httpx.Auth | None" = None,
|
|
1504
1690
|
head: int | None = None,
|
|
1691
|
+
json_output: bool = False,
|
|
1505
1692
|
):
|
|
1506
1693
|
"""Build and execute a GraphQL query/mutation."""
|
|
1507
1694
|
document, variables, field_name = _build_graphql_document(
|
|
@@ -1525,13 +1712,13 @@ def execute_graphql(
|
|
|
1525
1712
|
print(f"GraphQL error: {msgs}", file=sys.stderr)
|
|
1526
1713
|
sys.exit(1)
|
|
1527
1714
|
# Partial errors — include them in output
|
|
1528
|
-
output_result(result, pretty=pretty, raw=raw, toon=toon, head=head)
|
|
1715
|
+
output_result(result, pretty=pretty, raw=raw, toon=toon, head=head, json_output=json_output)
|
|
1529
1716
|
return
|
|
1530
1717
|
|
|
1531
1718
|
data = result.get("data", {})
|
|
1532
1719
|
# Extract the specific field's data
|
|
1533
1720
|
field_data = data.get(field_name, data)
|
|
1534
|
-
output_result(field_data, pretty=pretty, raw=raw, toon=toon, head=head)
|
|
1721
|
+
output_result(field_data, pretty=pretty, raw=raw, toon=toon, head=head, json_output=json_output)
|
|
1535
1722
|
|
|
1536
1723
|
|
|
1537
1724
|
def handle_graphql(
|
|
@@ -1552,6 +1739,7 @@ def handle_graphql(
|
|
|
1552
1739
|
sort_mode: str | None = None,
|
|
1553
1740
|
top: int | None = None,
|
|
1554
1741
|
compact: bool = False,
|
|
1742
|
+
json_output: bool = False,
|
|
1555
1743
|
):
|
|
1556
1744
|
"""Top-level handler for --graphql mode."""
|
|
1557
1745
|
src_hash = _source_hash_for(url)
|
|
@@ -1561,6 +1749,7 @@ def handle_graphql(
|
|
|
1561
1749
|
list_kwargs = dict(
|
|
1562
1750
|
verbose=verbose, compact=compact,
|
|
1563
1751
|
source_hash=src_hash, sort_mode=sort_mode, top=top,
|
|
1752
|
+
json_output=json_output, pretty=pretty,
|
|
1564
1753
|
)
|
|
1565
1754
|
|
|
1566
1755
|
if list_mode:
|
|
@@ -1568,10 +1757,10 @@ def handle_graphql(
|
|
|
1568
1757
|
return
|
|
1569
1758
|
|
|
1570
1759
|
if not remaining:
|
|
1571
|
-
if not compact:
|
|
1760
|
+
if not compact and not json_output:
|
|
1572
1761
|
print("Available operations:")
|
|
1573
1762
|
list_graphql_commands(commands, **list_kwargs)
|
|
1574
|
-
if not compact:
|
|
1763
|
+
if not compact and not json_output:
|
|
1575
1764
|
print("\nUse --list for the same output, or provide a subcommand.")
|
|
1576
1765
|
return
|
|
1577
1766
|
|
|
@@ -1587,6 +1776,7 @@ def handle_graphql(
|
|
|
1587
1776
|
execute_graphql(
|
|
1588
1777
|
args, cmd, url, schema, auth_headers, pretty, raw, toon=toon,
|
|
1589
1778
|
fields_override=fields_override, oauth_provider=oauth_provider,
|
|
1779
|
+
json_output=json_output,
|
|
1590
1780
|
)
|
|
1591
1781
|
|
|
1592
1782
|
# Record usage after successful execution
|
|
@@ -2040,9 +2230,15 @@ def list_openapi_commands(
|
|
|
2040
2230
|
source_hash: str = "",
|
|
2041
2231
|
sort_mode: str | None = None,
|
|
2042
2232
|
top: int | None = None,
|
|
2233
|
+
json_output: bool = False,
|
|
2234
|
+
pretty: bool = False,
|
|
2043
2235
|
):
|
|
2044
2236
|
commands = _apply_list_options(commands, source_hash, sort_mode, top)
|
|
2045
2237
|
|
|
2238
|
+
if json_output:
|
|
2239
|
+
print_commands_json(commands, compact=compact, pretty=pretty)
|
|
2240
|
+
return
|
|
2241
|
+
|
|
2046
2242
|
if compact:
|
|
2047
2243
|
print(" ".join(cmd.name for cmd in commands))
|
|
2048
2244
|
return
|
|
@@ -2073,9 +2269,15 @@ def list_mcp_commands(
|
|
|
2073
2269
|
source_hash: str = "",
|
|
2074
2270
|
sort_mode: str | None = None,
|
|
2075
2271
|
top: int | None = None,
|
|
2272
|
+
json_output: bool = False,
|
|
2273
|
+
pretty: bool = False,
|
|
2076
2274
|
):
|
|
2077
2275
|
commands = _apply_list_options(commands, source_hash, sort_mode, top)
|
|
2078
2276
|
|
|
2277
|
+
if json_output:
|
|
2278
|
+
print_commands_json(commands, compact=compact, pretty=pretty)
|
|
2279
|
+
return
|
|
2280
|
+
|
|
2079
2281
|
if compact:
|
|
2080
2282
|
print(" ".join(cmd.name for cmd in commands))
|
|
2081
2283
|
return
|
|
@@ -2186,6 +2388,7 @@ def execute_openapi(
|
|
|
2186
2388
|
toon: bool = False,
|
|
2187
2389
|
oauth_provider: "httpx.Auth | None" = None,
|
|
2188
2390
|
head: int | None = None,
|
|
2391
|
+
json_output: bool = False,
|
|
2189
2392
|
):
|
|
2190
2393
|
path, query_params, extra_headers, body, files = _collect_openapi_params(cmd, args)
|
|
2191
2394
|
url = base_url.rstrip("/") + path
|
|
@@ -2227,6 +2430,14 @@ def execute_openapi(
|
|
|
2227
2430
|
for _, file_tuple in files.items():
|
|
2228
2431
|
file_tuple[1].close()
|
|
2229
2432
|
|
|
2433
|
+
if json_output:
|
|
2434
|
+
try:
|
|
2435
|
+
data = resp.json()
|
|
2436
|
+
except Exception:
|
|
2437
|
+
data = resp.text
|
|
2438
|
+
output_result(data, pretty=pretty, head=head, json_output=True)
|
|
2439
|
+
return
|
|
2440
|
+
|
|
2230
2441
|
if raw:
|
|
2231
2442
|
sys.stdout.buffer.write(resp.content)
|
|
2232
2443
|
return
|
|
@@ -2271,6 +2482,7 @@ def run_mcp_http(
|
|
|
2271
2482
|
top: int | None = None,
|
|
2272
2483
|
compact: bool = False,
|
|
2273
2484
|
source_hash: str = "",
|
|
2485
|
+
json_output: bool = False,
|
|
2274
2486
|
):
|
|
2275
2487
|
extra = dict(
|
|
2276
2488
|
resource_action=resource_action,
|
|
@@ -2285,6 +2497,7 @@ def run_mcp_http(
|
|
|
2285
2497
|
top=top,
|
|
2286
2498
|
compact=compact,
|
|
2287
2499
|
source_hash=source_hash,
|
|
2500
|
+
json_output=json_output,
|
|
2288
2501
|
)
|
|
2289
2502
|
|
|
2290
2503
|
async def _run():
|
|
@@ -2374,6 +2587,7 @@ def run_mcp_stdio(
|
|
|
2374
2587
|
top: int | None = None,
|
|
2375
2588
|
compact: bool = False,
|
|
2376
2589
|
source_hash: str = "",
|
|
2590
|
+
json_output: bool = False,
|
|
2377
2591
|
):
|
|
2378
2592
|
extra = dict(
|
|
2379
2593
|
resource_action=resource_action,
|
|
@@ -2388,6 +2602,7 @@ def run_mcp_stdio(
|
|
|
2388
2602
|
top=top,
|
|
2389
2603
|
compact=compact,
|
|
2390
2604
|
source_hash=source_hash,
|
|
2605
|
+
json_output=json_output,
|
|
2391
2606
|
)
|
|
2392
2607
|
|
|
2393
2608
|
import anyio
|
|
@@ -2443,11 +2658,13 @@ async def _mcp_session(
|
|
|
2443
2658
|
top: int | None = None,
|
|
2444
2659
|
compact: bool = False,
|
|
2445
2660
|
source_hash: str = "",
|
|
2661
|
+
json_output: bool = False,
|
|
2446
2662
|
):
|
|
2447
2663
|
# Handle resource operations
|
|
2448
2664
|
if resource_action:
|
|
2449
2665
|
await _handle_resources(
|
|
2450
2666
|
session, resource_action, resource_uri, pretty, raw, toon, head=head,
|
|
2667
|
+
json_output=json_output,
|
|
2451
2668
|
)
|
|
2452
2669
|
return
|
|
2453
2670
|
|
|
@@ -2455,13 +2672,14 @@ async def _mcp_session(
|
|
|
2455
2672
|
if prompt_action:
|
|
2456
2673
|
await _handle_prompts(
|
|
2457
2674
|
session, prompt_action, prompt_name, prompt_arguments,
|
|
2458
|
-
pretty, raw, toon, head=head,
|
|
2675
|
+
pretty, raw, toon, head=head, json_output=json_output,
|
|
2459
2676
|
)
|
|
2460
2677
|
return
|
|
2461
2678
|
|
|
2462
2679
|
list_kwargs = dict(
|
|
2463
2680
|
verbose=verbose, compact=compact,
|
|
2464
2681
|
source_hash=source_hash, sort_mode=sort_mode, top=top,
|
|
2682
|
+
json_output=json_output, pretty=pretty,
|
|
2465
2683
|
)
|
|
2466
2684
|
|
|
2467
2685
|
if list_mode:
|
|
@@ -2478,12 +2696,15 @@ async def _mcp_session(
|
|
|
2478
2696
|
if search_pattern:
|
|
2479
2697
|
commands = _filter_commands(commands, search_pattern)
|
|
2480
2698
|
if not commands:
|
|
2481
|
-
|
|
2699
|
+
if json_output:
|
|
2700
|
+
list_mcp_commands(commands, **list_kwargs)
|
|
2701
|
+
else:
|
|
2702
|
+
print(f"\nNo tools matching '{search_pattern}'.")
|
|
2482
2703
|
return
|
|
2483
|
-
if not compact:
|
|
2704
|
+
if not compact and not json_output:
|
|
2484
2705
|
print(f"\nTools matching '{search_pattern}':")
|
|
2485
2706
|
else:
|
|
2486
|
-
if not compact:
|
|
2707
|
+
if not compact and not json_output:
|
|
2487
2708
|
print("\nAvailable tools:")
|
|
2488
2709
|
list_mcp_commands(commands, **list_kwargs)
|
|
2489
2710
|
return
|
|
@@ -2497,6 +2718,12 @@ async def _mcp_session(
|
|
|
2497
2718
|
|
|
2498
2719
|
result = await session.call_tool(tool_name, arguments or {})
|
|
2499
2720
|
|
|
2721
|
+
if json_output:
|
|
2722
|
+
# Emit the full MCP CallToolResult envelope (content, structuredContent,
|
|
2723
|
+
# isError) using the SDK's own serializer — 100% MCP-compatible.
|
|
2724
|
+
output_result(result.model_dump(mode="json"), pretty=pretty, head=head, json_output=True)
|
|
2725
|
+
return
|
|
2726
|
+
|
|
2500
2727
|
text = _extract_content_parts(result.content)
|
|
2501
2728
|
output_result(text, pretty=pretty, raw=raw, toon=toon, head=head)
|
|
2502
2729
|
|
|
@@ -2514,8 +2741,9 @@ async def _handle_resources(
|
|
|
2514
2741
|
raw: bool,
|
|
2515
2742
|
toon: bool,
|
|
2516
2743
|
head: int | None = None,
|
|
2744
|
+
json_output: bool = False,
|
|
2517
2745
|
):
|
|
2518
|
-
_out = dict(pretty=pretty, raw=raw, toon=toon, head=head)
|
|
2746
|
+
_out = dict(pretty=pretty, raw=raw, toon=toon, head=head, json_output=json_output)
|
|
2519
2747
|
if action == "list":
|
|
2520
2748
|
result = await session.list_resources()
|
|
2521
2749
|
data = [
|
|
@@ -2568,8 +2796,9 @@ async def _handle_prompts(
|
|
|
2568
2796
|
raw: bool,
|
|
2569
2797
|
toon: bool,
|
|
2570
2798
|
head: int | None = None,
|
|
2799
|
+
json_output: bool = False,
|
|
2571
2800
|
):
|
|
2572
|
-
_out = dict(pretty=pretty, raw=raw, toon=toon, head=head)
|
|
2801
|
+
_out = dict(pretty=pretty, raw=raw, toon=toon, head=head, json_output=json_output)
|
|
2573
2802
|
if action == "list":
|
|
2574
2803
|
result = await session.list_prompts()
|
|
2575
2804
|
data = [
|
|
@@ -2617,6 +2846,8 @@ def _session_meta_path(name: str) -> Path:
|
|
|
2617
2846
|
def _session_sock_path(name: str) -> Path:
|
|
2618
2847
|
return SESSIONS_DIR / f"{name}.sock"
|
|
2619
2848
|
|
|
2849
|
+
def _session_log_path(name: str) -> Path:
|
|
2850
|
+
return SESSIONS_DIR / f"{name}.log"
|
|
2620
2851
|
|
|
2621
2852
|
def _session_is_alive(meta: dict) -> bool:
|
|
2622
2853
|
pid = meta.get("pid")
|
|
@@ -2650,6 +2881,7 @@ def session_stop(name: str):
|
|
|
2650
2881
|
"""Stop a named session."""
|
|
2651
2882
|
meta_path = _session_meta_path(name)
|
|
2652
2883
|
sock_path = _session_sock_path(name)
|
|
2884
|
+
log_path = _session_log_path(name)
|
|
2653
2885
|
if meta_path.exists():
|
|
2654
2886
|
try:
|
|
2655
2887
|
meta = json.loads(meta_path.read_text())
|
|
@@ -2667,6 +2899,7 @@ def session_stop(name: str):
|
|
|
2667
2899
|
pass
|
|
2668
2900
|
meta_path.unlink(missing_ok=True)
|
|
2669
2901
|
sock_path.unlink(missing_ok=True)
|
|
2902
|
+
log_path.unlink(missing_ok=True)
|
|
2670
2903
|
|
|
2671
2904
|
|
|
2672
2905
|
def session_start(
|
|
@@ -2709,6 +2942,7 @@ def session_start(
|
|
|
2709
2942
|
}
|
|
2710
2943
|
)
|
|
2711
2944
|
|
|
2945
|
+
log_path = _session_log_path(name)
|
|
2712
2946
|
proc = subprocess.Popen(
|
|
2713
2947
|
[
|
|
2714
2948
|
sys.executable,
|
|
@@ -2717,7 +2951,7 @@ def session_start(
|
|
|
2717
2951
|
],
|
|
2718
2952
|
start_new_session=True,
|
|
2719
2953
|
stdout=subprocess.DEVNULL,
|
|
2720
|
-
stderr=
|
|
2954
|
+
stderr=open(log_path, "a"),
|
|
2721
2955
|
stdin=subprocess.DEVNULL,
|
|
2722
2956
|
)
|
|
2723
2957
|
|
|
@@ -3110,6 +3344,7 @@ def handle_mcp(
|
|
|
3110
3344
|
sort_mode: str | None = None,
|
|
3111
3345
|
top: int | None = None,
|
|
3112
3346
|
compact: bool = False,
|
|
3347
|
+
json_output: bool = False,
|
|
3113
3348
|
):
|
|
3114
3349
|
# Build a config dict for cache key generation (future-proof)
|
|
3115
3350
|
config_for_cache = {
|
|
@@ -3132,6 +3367,7 @@ def handle_mcp(
|
|
|
3132
3367
|
prompt_name=prompt_name,
|
|
3133
3368
|
prompt_arguments=prompt_arguments,
|
|
3134
3369
|
head=head,
|
|
3370
|
+
json_output=json_output,
|
|
3135
3371
|
)
|
|
3136
3372
|
_dispatch_mcp_call(
|
|
3137
3373
|
source, is_stdio, auth_headers, env_vars,
|
|
@@ -3144,6 +3380,7 @@ def handle_mcp(
|
|
|
3144
3380
|
list_kwargs = dict(
|
|
3145
3381
|
verbose=verbose, compact=compact,
|
|
3146
3382
|
source_hash=src_hash, sort_mode=sort_mode, top=top,
|
|
3383
|
+
json_output=json_output, pretty=pretty,
|
|
3147
3384
|
)
|
|
3148
3385
|
|
|
3149
3386
|
if list_mode:
|
|
@@ -3157,7 +3394,7 @@ def handle_mcp(
|
|
|
3157
3394
|
commands = filter_commands(
|
|
3158
3395
|
commands, bake_config.include, bake_config.exclude, bake_config.methods,
|
|
3159
3396
|
)
|
|
3160
|
-
if not compact:
|
|
3397
|
+
if not compact and not json_output:
|
|
3161
3398
|
print("\nAvailable tools:")
|
|
3162
3399
|
list_mcp_commands(commands, **list_kwargs)
|
|
3163
3400
|
return
|
|
@@ -3169,6 +3406,7 @@ def handle_mcp(
|
|
|
3169
3406
|
verbose=verbose,
|
|
3170
3407
|
sort_mode=sort_mode, top=top, compact=compact,
|
|
3171
3408
|
source_hash=src_hash,
|
|
3409
|
+
json_output=json_output,
|
|
3172
3410
|
)
|
|
3173
3411
|
return
|
|
3174
3412
|
|
|
@@ -3185,10 +3423,10 @@ def handle_mcp(
|
|
|
3185
3423
|
)
|
|
3186
3424
|
|
|
3187
3425
|
if not remaining:
|
|
3188
|
-
if not compact:
|
|
3426
|
+
if not compact and not json_output:
|
|
3189
3427
|
print("Available tools:")
|
|
3190
3428
|
list_mcp_commands(commands, **list_kwargs)
|
|
3191
|
-
if not compact:
|
|
3429
|
+
if not compact and not json_output:
|
|
3192
3430
|
print("\nUse --list for the same output, or provide a subcommand.")
|
|
3193
3431
|
return
|
|
3194
3432
|
|
|
@@ -3215,6 +3453,7 @@ def handle_mcp(
|
|
|
3215
3453
|
source, is_stdio, auth_headers, env_vars,
|
|
3216
3454
|
cmd.tool_name, arguments, False, pretty, raw, key, ttl, refresh,
|
|
3217
3455
|
toon=toon, transport=transport, oauth_provider=oauth_provider,
|
|
3456
|
+
json_output=json_output,
|
|
3218
3457
|
)
|
|
3219
3458
|
|
|
3220
3459
|
# Record usage after successful execution
|
|
@@ -3432,6 +3671,16 @@ def _build_main_parser() -> argparse.ArgumentParser:
|
|
|
3432
3671
|
)
|
|
3433
3672
|
pre.add_argument("--pretty", action="store_true", help="Pretty-print JSON output")
|
|
3434
3673
|
pre.add_argument("--raw", action="store_true", help="Print raw response body")
|
|
3674
|
+
pre.add_argument(
|
|
3675
|
+
"--json",
|
|
3676
|
+
action="store_true",
|
|
3677
|
+
dest="json_output",
|
|
3678
|
+
help=(
|
|
3679
|
+
"Force valid JSON output for every command. --list emits a JSON array of "
|
|
3680
|
+
"commands; MCP tool calls emit the full result envelope including "
|
|
3681
|
+
"structuredContent and isError. Takes precedence over --raw and --toon."
|
|
3682
|
+
),
|
|
3683
|
+
)
|
|
3435
3684
|
pre.add_argument(
|
|
3436
3685
|
"--toon",
|
|
3437
3686
|
action="store_true",
|
|
@@ -3693,32 +3942,28 @@ def _handle_session_operations(
|
|
|
3693
3942
|
|
|
3694
3943
|
# --- Session client mode ---
|
|
3695
3944
|
sess_name = pre_args.session
|
|
3945
|
+
_sess_out = dict(
|
|
3946
|
+
pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3947
|
+
json_output=pre_args.json_output,
|
|
3948
|
+
)
|
|
3696
3949
|
|
|
3697
3950
|
if pre_args.list_resources:
|
|
3698
3951
|
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
|
-
)
|
|
3952
|
+
output_result(result, **_sess_out)
|
|
3702
3953
|
return True
|
|
3703
3954
|
if pre_args.list_resource_templates:
|
|
3704
3955
|
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
|
-
)
|
|
3956
|
+
output_result(result, **_sess_out)
|
|
3708
3957
|
return True
|
|
3709
3958
|
if pre_args.read_resource:
|
|
3710
3959
|
result = _session_request(
|
|
3711
3960
|
sess_name, "read_resource", {"uri": pre_args.read_resource}
|
|
3712
3961
|
)
|
|
3713
|
-
output_result(
|
|
3714
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3715
|
-
)
|
|
3962
|
+
output_result(result, **_sess_out)
|
|
3716
3963
|
return True
|
|
3717
3964
|
if pre_args.list_prompts:
|
|
3718
3965
|
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
|
-
)
|
|
3966
|
+
output_result(result, **_sess_out)
|
|
3722
3967
|
return True
|
|
3723
3968
|
if pre_args.get_prompt:
|
|
3724
3969
|
p_args = {}
|
|
@@ -3731,9 +3976,7 @@ def _handle_session_operations(
|
|
|
3731
3976
|
"get_prompt",
|
|
3732
3977
|
{"name": pre_args.get_prompt, "arguments": p_args},
|
|
3733
3978
|
)
|
|
3734
|
-
output_result(
|
|
3735
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3736
|
-
)
|
|
3979
|
+
output_result(result, **_sess_out)
|
|
3737
3980
|
return True
|
|
3738
3981
|
if pre_args.list_commands:
|
|
3739
3982
|
result = _session_request(sess_name, "list_tools")
|
|
@@ -3741,21 +3984,36 @@ def _handle_session_operations(
|
|
|
3741
3984
|
if search_pattern:
|
|
3742
3985
|
commands = _filter_commands(commands, search_pattern)
|
|
3743
3986
|
if not commands:
|
|
3744
|
-
|
|
3987
|
+
if pre_args.json_output:
|
|
3988
|
+
list_mcp_commands(
|
|
3989
|
+
commands, verbose=pre_args.verbose,
|
|
3990
|
+
json_output=True, pretty=pre_args.pretty,
|
|
3991
|
+
)
|
|
3992
|
+
else:
|
|
3993
|
+
print(f"\nNo tools matching '{search_pattern}'.")
|
|
3745
3994
|
return True
|
|
3746
|
-
|
|
3747
|
-
|
|
3995
|
+
if not pre_args.json_output:
|
|
3996
|
+
print(f"\nTools matching '{search_pattern}':")
|
|
3997
|
+
elif not pre_args.json_output:
|
|
3748
3998
|
print("\nAvailable tools:")
|
|
3749
|
-
list_mcp_commands(
|
|
3999
|
+
list_mcp_commands(
|
|
4000
|
+
commands, verbose=pre_args.verbose,
|
|
4001
|
+
json_output=pre_args.json_output, pretty=pre_args.pretty,
|
|
4002
|
+
)
|
|
3750
4003
|
return True
|
|
3751
4004
|
|
|
3752
4005
|
# Tool call via session
|
|
3753
4006
|
if not remaining:
|
|
3754
4007
|
result = _session_request(sess_name, "list_tools")
|
|
3755
4008
|
commands = extract_mcp_commands(result)
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
4009
|
+
if not pre_args.json_output:
|
|
4010
|
+
print("Available tools:")
|
|
4011
|
+
list_mcp_commands(
|
|
4012
|
+
commands, verbose=pre_args.verbose,
|
|
4013
|
+
json_output=pre_args.json_output, pretty=pre_args.pretty,
|
|
4014
|
+
)
|
|
4015
|
+
if not pre_args.json_output:
|
|
4016
|
+
print("\nUse --list for the same output, or provide a subcommand.")
|
|
3759
4017
|
return True
|
|
3760
4018
|
|
|
3761
4019
|
tools = _session_request(sess_name, "list_tools")
|
|
@@ -3781,9 +4039,7 @@ def _handle_session_operations(
|
|
|
3781
4039
|
result = _session_request(
|
|
3782
4040
|
sess_name, "call_tool", {"name": cmd.tool_name, "arguments": arguments}
|
|
3783
4041
|
)
|
|
3784
|
-
output_result(
|
|
3785
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon
|
|
3786
|
-
)
|
|
4042
|
+
output_result(result, **_sess_out)
|
|
3787
4043
|
return True
|
|
3788
4044
|
|
|
3789
4045
|
|
|
@@ -3848,15 +4104,19 @@ def _handle_openapi_mode(
|
|
|
3848
4104
|
list_kwargs = dict(
|
|
3849
4105
|
verbose=pre_args.verbose, compact=pre_args.compact,
|
|
3850
4106
|
source_hash=src_hash, sort_mode=pre_args.sort_mode, top=pre_args.top,
|
|
4107
|
+
json_output=pre_args.json_output, pretty=pre_args.pretty,
|
|
3851
4108
|
)
|
|
3852
4109
|
|
|
3853
4110
|
if pre_args.list_commands:
|
|
3854
4111
|
if search_pattern:
|
|
3855
4112
|
commands = _filter_commands(commands, search_pattern)
|
|
3856
4113
|
if not commands:
|
|
3857
|
-
|
|
4114
|
+
if not pre_args.json_output:
|
|
4115
|
+
print(f"\nNo tools matching '{search_pattern}'.")
|
|
4116
|
+
else:
|
|
4117
|
+
list_openapi_commands(commands, **list_kwargs)
|
|
3858
4118
|
return
|
|
3859
|
-
if not pre_args.compact:
|
|
4119
|
+
if not pre_args.compact and not pre_args.json_output:
|
|
3860
4120
|
print(f"\nTools matching '{search_pattern}':")
|
|
3861
4121
|
list_openapi_commands(commands, **list_kwargs)
|
|
3862
4122
|
return
|
|
@@ -3900,7 +4160,7 @@ def _handle_openapi_mode(
|
|
|
3900
4160
|
execute_openapi(
|
|
3901
4161
|
args, cmd, base_url, auth_headers,
|
|
3902
4162
|
pre_args.pretty, pre_args.raw, toon=pre_args.toon,
|
|
3903
|
-
oauth_provider=oauth_provider,
|
|
4163
|
+
oauth_provider=oauth_provider, json_output=pre_args.json_output,
|
|
3904
4164
|
)
|
|
3905
4165
|
|
|
3906
4166
|
# Record usage after successful execution
|
|
@@ -3960,6 +4220,7 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
3960
4220
|
sort_mode=pre_args.sort_mode,
|
|
3961
4221
|
top=pre_args.top,
|
|
3962
4222
|
compact=pre_args.compact,
|
|
4223
|
+
json_output=pre_args.json_output,
|
|
3963
4224
|
)
|
|
3964
4225
|
return
|
|
3965
4226
|
|
|
@@ -3994,6 +4255,7 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
3994
4255
|
sort_mode=pre_args.sort_mode,
|
|
3995
4256
|
top=pre_args.top,
|
|
3996
4257
|
compact=pre_args.compact,
|
|
4258
|
+
json_output=pre_args.json_output,
|
|
3997
4259
|
)
|
|
3998
4260
|
return
|
|
3999
4261
|
|
|
File without changes
|
|
File without changes
|