mcp2cli 2.2.4__tar.gz → 2.4.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-2.2.4 → mcp2cli-2.4.0}/PKG-INFO +11 -1
- {mcp2cli-2.2.4 → mcp2cli-2.4.0}/README.md +10 -0
- {mcp2cli-2.2.4 → mcp2cli-2.4.0}/pyproject.toml +1 -1
- {mcp2cli-2.2.4 → mcp2cli-2.4.0}/src/mcp2cli/__init__.py +212 -44
- {mcp2cli-2.2.4 → mcp2cli-2.4.0}/src/mcp2cli/__main__.py +0 -0
- {mcp2cli-2.2.4 → mcp2cli-2.4.0}/src/mcp2cli/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp2cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.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>
|
|
@@ -225,6 +225,14 @@ mcp2cli --spec ./spec.json --pretty list-pets
|
|
|
225
225
|
# Raw response body (no JSON parsing)
|
|
226
226
|
mcp2cli --spec ./spec.json --raw get-data
|
|
227
227
|
|
|
228
|
+
# Filter JSON with jq (preferred over Python for JSON processing)
|
|
229
|
+
mcp2cli --spec ./spec.json list-pets --jq '.[].name'
|
|
230
|
+
mcp2cli --spec ./spec.json list-pets --jq '[.[] | select(.status == "available")] | length'
|
|
231
|
+
|
|
232
|
+
# Truncate large responses to first N records
|
|
233
|
+
mcp2cli --spec ./spec.json list-records --head 5
|
|
234
|
+
mcp2cli --spec ./spec.json list-records --head 3 --jq '.' # preview then filter
|
|
235
|
+
|
|
228
236
|
# Pipe-friendly (compact JSON when not a TTY)
|
|
229
237
|
mcp2cli --spec ./spec.json list-pets | jq '.[] | .name'
|
|
230
238
|
|
|
@@ -282,6 +290,8 @@ Options:
|
|
|
282
290
|
--pretty Pretty-print JSON output
|
|
283
291
|
--raw Print raw response body
|
|
284
292
|
--toon Encode output as TOON (token-efficient for LLMs)
|
|
293
|
+
--jq EXPR Filter JSON output through jq expression
|
|
294
|
+
--head N Limit output to first N records (arrays)
|
|
285
295
|
--version Show version
|
|
286
296
|
|
|
287
297
|
Bake mode:
|
|
@@ -206,6 +206,14 @@ mcp2cli --spec ./spec.json --pretty list-pets
|
|
|
206
206
|
# Raw response body (no JSON parsing)
|
|
207
207
|
mcp2cli --spec ./spec.json --raw get-data
|
|
208
208
|
|
|
209
|
+
# Filter JSON with jq (preferred over Python for JSON processing)
|
|
210
|
+
mcp2cli --spec ./spec.json list-pets --jq '.[].name'
|
|
211
|
+
mcp2cli --spec ./spec.json list-pets --jq '[.[] | select(.status == "available")] | length'
|
|
212
|
+
|
|
213
|
+
# Truncate large responses to first N records
|
|
214
|
+
mcp2cli --spec ./spec.json list-records --head 5
|
|
215
|
+
mcp2cli --spec ./spec.json list-records --head 3 --jq '.' # preview then filter
|
|
216
|
+
|
|
209
217
|
# Pipe-friendly (compact JSON when not a TTY)
|
|
210
218
|
mcp2cli --spec ./spec.json list-pets | jq '.[] | .name'
|
|
211
219
|
|
|
@@ -263,6 +271,8 @@ Options:
|
|
|
263
271
|
--pretty Pretty-print JSON output
|
|
264
272
|
--raw Print raw response body
|
|
265
273
|
--toon Encode output as TOON (token-efficient for LLMs)
|
|
274
|
+
--jq EXPR Filter JSON output through jq expression
|
|
275
|
+
--head N Limit output to first N records (arrays)
|
|
266
276
|
--version Show version
|
|
267
277
|
|
|
268
278
|
Bake mode:
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
__version__ = "2.
|
|
5
|
+
__version__ = "2.4.0"
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import copy
|
|
9
9
|
import hashlib
|
|
10
10
|
import json
|
|
11
|
+
import mimetypes
|
|
11
12
|
import os
|
|
12
13
|
import fnmatch
|
|
13
14
|
import re
|
|
@@ -64,6 +65,7 @@ class CommandDef:
|
|
|
64
65
|
# OpenAPI
|
|
65
66
|
method: str | None = None
|
|
66
67
|
path: str | None = None
|
|
68
|
+
content_type: str | None = None # None = json, "multipart/form-data", etc.
|
|
67
69
|
# MCP
|
|
68
70
|
tool_name: str | None = None
|
|
69
71
|
# GraphQL
|
|
@@ -246,7 +248,48 @@ def _toon_encode(json_str: str) -> str | None:
|
|
|
246
248
|
return None
|
|
247
249
|
|
|
248
250
|
|
|
249
|
-
def
|
|
251
|
+
def _run_jq(json_str: str, expr: str) -> str:
|
|
252
|
+
"""Pipe JSON through jq with the given expression. Exits on failure."""
|
|
253
|
+
if not shutil.which("jq"):
|
|
254
|
+
print(
|
|
255
|
+
"Error: --jq requires jq to be installed. "
|
|
256
|
+
"See https://jqlang.github.io/jq/",
|
|
257
|
+
file=sys.stderr,
|
|
258
|
+
)
|
|
259
|
+
sys.exit(1)
|
|
260
|
+
try:
|
|
261
|
+
result = subprocess.run(
|
|
262
|
+
["jq", expr],
|
|
263
|
+
input=json_str,
|
|
264
|
+
capture_output=True,
|
|
265
|
+
text=True,
|
|
266
|
+
timeout=30,
|
|
267
|
+
)
|
|
268
|
+
if result.returncode != 0:
|
|
269
|
+
print(f"jq error: {result.stderr.strip()}", file=sys.stderr)
|
|
270
|
+
sys.exit(1)
|
|
271
|
+
return result.stdout
|
|
272
|
+
except subprocess.TimeoutExpired:
|
|
273
|
+
print("Error: jq timed out", file=sys.stderr)
|
|
274
|
+
sys.exit(1)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _apply_head(data, n: int):
|
|
278
|
+
"""Truncate data to first N elements (array) or return as-is (dict/scalar)."""
|
|
279
|
+
if isinstance(data, list):
|
|
280
|
+
return data[:n]
|
|
281
|
+
return data
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def output_result(
|
|
285
|
+
data,
|
|
286
|
+
*,
|
|
287
|
+
pretty: bool = False,
|
|
288
|
+
raw: bool = False,
|
|
289
|
+
toon: bool = False,
|
|
290
|
+
jq_expr: str | None = None,
|
|
291
|
+
head: int | None = None,
|
|
292
|
+
):
|
|
250
293
|
if raw:
|
|
251
294
|
if isinstance(data, str):
|
|
252
295
|
print(data)
|
|
@@ -259,6 +302,11 @@ def output_result(data, *, pretty: bool = False, raw: bool = False, toon: bool =
|
|
|
259
302
|
except (json.JSONDecodeError, TypeError):
|
|
260
303
|
print(data)
|
|
261
304
|
return
|
|
305
|
+
if head is not None:
|
|
306
|
+
data = _apply_head(data, head)
|
|
307
|
+
if jq_expr:
|
|
308
|
+
print(_run_jq(json.dumps(data), jq_expr), end="")
|
|
309
|
+
return
|
|
262
310
|
if toon:
|
|
263
311
|
encoded = _toon_encode(json.dumps(data))
|
|
264
312
|
if encoded is not None:
|
|
@@ -276,10 +324,11 @@ def output_result(data, *, pretty: bool = False, raw: bool = False, toon: bool =
|
|
|
276
324
|
print(json.dumps(data))
|
|
277
325
|
|
|
278
326
|
|
|
279
|
-
def _build_http_headers(auth_headers: list[tuple[str, str]]) -> dict[str, str]:
|
|
327
|
+
def _build_http_headers(auth_headers: list[tuple[str, str]], multipart: bool = False) -> dict[str, str]:
|
|
280
328
|
"""Build HTTP headers dict from auth_headers with a Content-Type default."""
|
|
281
329
|
headers = dict(auth_headers)
|
|
282
|
-
|
|
330
|
+
if not multipart:
|
|
331
|
+
headers.setdefault("Content-Type", "application/json")
|
|
283
332
|
return headers
|
|
284
333
|
|
|
285
334
|
|
|
@@ -616,19 +665,43 @@ def extract_openapi_commands(spec: dict) -> list[CommandDef]:
|
|
|
616
665
|
)
|
|
617
666
|
params.append(p)
|
|
618
667
|
|
|
619
|
-
# Request body
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
668
|
+
# Request body — negotiate content type
|
|
669
|
+
rb_content = details.get("requestBody", {}).get("content", {})
|
|
670
|
+
multipart_schema = rb_content.get("multipart/form-data", {}).get("schema", {})
|
|
671
|
+
json_schema = rb_content.get("application/json", {}).get("schema", {})
|
|
672
|
+
|
|
673
|
+
mp_props = multipart_schema.get("properties", {})
|
|
674
|
+
has_binary = any(
|
|
675
|
+
p.get("format") == "binary" for p in mp_props.values()
|
|
625
676
|
)
|
|
677
|
+
|
|
678
|
+
if has_binary:
|
|
679
|
+
rb_schema = multipart_schema
|
|
680
|
+
cmd_content_type = "multipart/form-data"
|
|
681
|
+
elif json_schema:
|
|
682
|
+
rb_schema = json_schema
|
|
683
|
+
cmd_content_type = None
|
|
684
|
+
elif mp_props:
|
|
685
|
+
rb_schema = multipart_schema
|
|
686
|
+
cmd_content_type = "multipart/form-data"
|
|
687
|
+
else:
|
|
688
|
+
rb_schema = {}
|
|
689
|
+
cmd_content_type = None
|
|
690
|
+
|
|
626
691
|
required_fields = set(rb_schema.get("required", []))
|
|
627
692
|
properties = rb_schema.get("properties", {})
|
|
628
693
|
has_body = bool(properties)
|
|
629
694
|
|
|
630
695
|
for prop_name, prop_schema in properties.items():
|
|
631
|
-
|
|
696
|
+
is_binary = (
|
|
697
|
+
cmd_content_type == "multipart/form-data"
|
|
698
|
+
and prop_schema.get("format") == "binary"
|
|
699
|
+
)
|
|
700
|
+
if is_binary:
|
|
701
|
+
loc, py_type, suffix = "file", str, " (file path)"
|
|
702
|
+
else:
|
|
703
|
+
py_type, suffix = schema_type_to_python(prop_schema)
|
|
704
|
+
loc = "body"
|
|
632
705
|
p = ParamDef(
|
|
633
706
|
name=to_kebab(prop_name),
|
|
634
707
|
original_name=prop_name,
|
|
@@ -636,7 +709,7 @@ def extract_openapi_commands(spec: dict) -> list[CommandDef]:
|
|
|
636
709
|
required=prop_name in required_fields,
|
|
637
710
|
description=(prop_schema.get("description") or prop_name) + suffix,
|
|
638
711
|
choices=prop_schema.get("enum"),
|
|
639
|
-
location=
|
|
712
|
+
location=loc,
|
|
640
713
|
)
|
|
641
714
|
params.append(p)
|
|
642
715
|
|
|
@@ -648,6 +721,7 @@ def extract_openapi_commands(spec: dict) -> list[CommandDef]:
|
|
|
648
721
|
has_body=has_body,
|
|
649
722
|
method=method,
|
|
650
723
|
path=path,
|
|
724
|
+
content_type=cmd_content_type,
|
|
651
725
|
)
|
|
652
726
|
)
|
|
653
727
|
|
|
@@ -1103,6 +1177,8 @@ def execute_graphql(
|
|
|
1103
1177
|
toon: bool = False,
|
|
1104
1178
|
fields_override: str | None = None,
|
|
1105
1179
|
oauth_provider: "httpx.Auth | None" = None,
|
|
1180
|
+
jq_expr: str | None = None,
|
|
1181
|
+
head: int | None = None,
|
|
1106
1182
|
):
|
|
1107
1183
|
"""Build and execute a GraphQL query/mutation."""
|
|
1108
1184
|
document, variables, field_name = _build_graphql_document(
|
|
@@ -1126,13 +1202,13 @@ def execute_graphql(
|
|
|
1126
1202
|
print(f"GraphQL error: {msgs}", file=sys.stderr)
|
|
1127
1203
|
sys.exit(1)
|
|
1128
1204
|
# Partial errors — include them in output
|
|
1129
|
-
output_result(result, pretty=pretty, raw=raw, toon=toon)
|
|
1205
|
+
output_result(result, pretty=pretty, raw=raw, toon=toon, jq_expr=jq_expr, head=head)
|
|
1130
1206
|
return
|
|
1131
1207
|
|
|
1132
1208
|
data = result.get("data", {})
|
|
1133
1209
|
# Extract the specific field's data
|
|
1134
1210
|
field_data = data.get(field_name, data)
|
|
1135
|
-
output_result(field_data, pretty=pretty, raw=raw, toon=toon)
|
|
1211
|
+
output_result(field_data, pretty=pretty, raw=raw, toon=toon, jq_expr=jq_expr, head=head)
|
|
1136
1212
|
|
|
1137
1213
|
|
|
1138
1214
|
def handle_graphql(
|
|
@@ -1148,6 +1224,8 @@ def handle_graphql(
|
|
|
1148
1224
|
toon: bool = False,
|
|
1149
1225
|
fields_override: str | None = None,
|
|
1150
1226
|
oauth_provider: "httpx.Auth | None" = None,
|
|
1227
|
+
jq_expr: str | None = None,
|
|
1228
|
+
head: int | None = None,
|
|
1151
1229
|
):
|
|
1152
1230
|
"""Top-level handler for --graphql mode."""
|
|
1153
1231
|
schema = load_graphql_schema(url, auth_headers, cache_key, ttl, refresh, oauth_provider=oauth_provider)
|
|
@@ -1175,6 +1253,7 @@ def handle_graphql(
|
|
|
1175
1253
|
execute_graphql(
|
|
1176
1254
|
args, cmd, url, schema, auth_headers, pretty, raw, toon=toon,
|
|
1177
1255
|
fields_override=fields_override, oauth_provider=oauth_provider,
|
|
1256
|
+
jq_expr=jq_expr, head=head,
|
|
1178
1257
|
)
|
|
1179
1258
|
|
|
1180
1259
|
|
|
@@ -1611,16 +1690,17 @@ def _filter_commands(commands: list[CommandDef], pattern: str) -> list[CommandDe
|
|
|
1611
1690
|
def _collect_openapi_params(
|
|
1612
1691
|
cmd: CommandDef,
|
|
1613
1692
|
args: argparse.Namespace,
|
|
1614
|
-
) -> tuple[str, dict[str, str], dict[str, str], dict | None]:
|
|
1693
|
+
) -> tuple[str, dict[str, str], dict[str, str], dict | None, dict | None]:
|
|
1615
1694
|
"""Collect OpenAPI params from parsed args, separated by location.
|
|
1616
1695
|
|
|
1617
|
-
Returns (path, query_params, extra_headers, body_or_none)
|
|
1618
|
-
has ``{param}`` placeholders substituted with actual values.
|
|
1696
|
+
Returns (path, query_params, extra_headers, body_or_none, files_or_none)
|
|
1697
|
+
where *path* has ``{param}`` placeholders substituted with actual values.
|
|
1619
1698
|
"""
|
|
1620
1699
|
path = cmd.path or ""
|
|
1621
1700
|
query_params: dict[str, str] = {}
|
|
1622
1701
|
extra_headers: dict[str, str] = {}
|
|
1623
1702
|
body: dict | None = None
|
|
1703
|
+
files: dict | None = None
|
|
1624
1704
|
|
|
1625
1705
|
for p in cmd.params:
|
|
1626
1706
|
if p.location == "path":
|
|
@@ -1650,6 +1730,17 @@ def _collect_openapi_params(
|
|
|
1650
1730
|
continue
|
|
1651
1731
|
if p.location == "path":
|
|
1652
1732
|
continue
|
|
1733
|
+
if p.location == "file":
|
|
1734
|
+
if val is not None:
|
|
1735
|
+
fp = Path(val)
|
|
1736
|
+
if not fp.is_file():
|
|
1737
|
+
print(f"Error: file not found: {val}", file=sys.stderr)
|
|
1738
|
+
sys.exit(1)
|
|
1739
|
+
mime = mimetypes.guess_type(val)[0] or "application/octet-stream"
|
|
1740
|
+
if files is None:
|
|
1741
|
+
files = {}
|
|
1742
|
+
files[p.original_name] = (fp.name, open(fp, "rb"), mime)
|
|
1743
|
+
continue
|
|
1653
1744
|
if val is not None:
|
|
1654
1745
|
body[p.original_name] = val
|
|
1655
1746
|
if not body:
|
|
@@ -1661,7 +1752,7 @@ def _collect_openapi_params(
|
|
|
1661
1752
|
if val is not None:
|
|
1662
1753
|
query_params[p.original_name] = val
|
|
1663
1754
|
|
|
1664
|
-
return path, query_params, extra_headers, body
|
|
1755
|
+
return path, query_params, extra_headers, body, files
|
|
1665
1756
|
|
|
1666
1757
|
|
|
1667
1758
|
def execute_openapi(
|
|
@@ -1673,22 +1764,48 @@ def execute_openapi(
|
|
|
1673
1764
|
raw: bool,
|
|
1674
1765
|
toon: bool = False,
|
|
1675
1766
|
oauth_provider: "httpx.Auth | None" = None,
|
|
1767
|
+
jq_expr: str | None = None,
|
|
1768
|
+
head: int | None = None,
|
|
1676
1769
|
):
|
|
1677
|
-
path, query_params, extra_headers, body = _collect_openapi_params(cmd, args)
|
|
1770
|
+
path, query_params, extra_headers, body, files = _collect_openapi_params(cmd, args)
|
|
1678
1771
|
url = base_url.rstrip("/") + path
|
|
1679
1772
|
|
|
1680
|
-
|
|
1773
|
+
is_multipart = files is not None or cmd.content_type == "multipart/form-data"
|
|
1774
|
+
headers = _build_http_headers(auth_headers, multipart=is_multipart)
|
|
1681
1775
|
headers.update(extra_headers)
|
|
1682
1776
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1777
|
+
try:
|
|
1778
|
+
with httpx.Client(timeout=60, auth=oauth_provider) as client:
|
|
1779
|
+
if files is not None:
|
|
1780
|
+
resp = client.request(
|
|
1781
|
+
(cmd.method or "get").upper(),
|
|
1782
|
+
url,
|
|
1783
|
+
headers=headers,
|
|
1784
|
+
params=query_params or None,
|
|
1785
|
+
data=body,
|
|
1786
|
+
files=files,
|
|
1787
|
+
)
|
|
1788
|
+
elif cmd.content_type == "multipart/form-data":
|
|
1789
|
+
resp = client.request(
|
|
1790
|
+
(cmd.method or "get").upper(),
|
|
1791
|
+
url,
|
|
1792
|
+
headers=headers,
|
|
1793
|
+
params=query_params or None,
|
|
1794
|
+
data=body,
|
|
1795
|
+
)
|
|
1796
|
+
else:
|
|
1797
|
+
resp = client.request(
|
|
1798
|
+
(cmd.method or "get").upper(),
|
|
1799
|
+
url,
|
|
1800
|
+
headers=headers,
|
|
1801
|
+
params=query_params or None,
|
|
1802
|
+
json=body,
|
|
1803
|
+
)
|
|
1804
|
+
_handle_http_error(resp)
|
|
1805
|
+
finally:
|
|
1806
|
+
if files:
|
|
1807
|
+
for _, file_tuple in files.items():
|
|
1808
|
+
file_tuple[1].close()
|
|
1692
1809
|
|
|
1693
1810
|
if raw:
|
|
1694
1811
|
sys.stdout.buffer.write(resp.content)
|
|
@@ -1700,7 +1817,7 @@ def execute_openapi(
|
|
|
1700
1817
|
print(resp.text)
|
|
1701
1818
|
return
|
|
1702
1819
|
|
|
1703
|
-
output_result(data, pretty=pretty, toon=toon)
|
|
1820
|
+
output_result(data, pretty=pretty, toon=toon, jq_expr=jq_expr, head=head)
|
|
1704
1821
|
|
|
1705
1822
|
|
|
1706
1823
|
# ---------------------------------------------------------------------------
|
|
@@ -1728,6 +1845,8 @@ def run_mcp_http(
|
|
|
1728
1845
|
prompt_name: str | None = None,
|
|
1729
1846
|
prompt_arguments: dict | None = None,
|
|
1730
1847
|
search_pattern: str | None = None,
|
|
1848
|
+
jq_expr: str | None = None,
|
|
1849
|
+
head: int | None = None,
|
|
1731
1850
|
):
|
|
1732
1851
|
extra = dict(
|
|
1733
1852
|
resource_action=resource_action,
|
|
@@ -1736,6 +1855,8 @@ def run_mcp_http(
|
|
|
1736
1855
|
prompt_name=prompt_name,
|
|
1737
1856
|
prompt_arguments=prompt_arguments,
|
|
1738
1857
|
search_pattern=search_pattern,
|
|
1858
|
+
jq_expr=jq_expr,
|
|
1859
|
+
head=head,
|
|
1739
1860
|
)
|
|
1740
1861
|
|
|
1741
1862
|
async def _run():
|
|
@@ -1819,6 +1940,8 @@ def run_mcp_stdio(
|
|
|
1819
1940
|
prompt_name: str | None = None,
|
|
1820
1941
|
prompt_arguments: dict | None = None,
|
|
1821
1942
|
search_pattern: str | None = None,
|
|
1943
|
+
jq_expr: str | None = None,
|
|
1944
|
+
head: int | None = None,
|
|
1822
1945
|
):
|
|
1823
1946
|
extra = dict(
|
|
1824
1947
|
resource_action=resource_action,
|
|
@@ -1827,6 +1950,8 @@ def run_mcp_stdio(
|
|
|
1827
1950
|
prompt_name=prompt_name,
|
|
1828
1951
|
prompt_arguments=prompt_arguments,
|
|
1829
1952
|
search_pattern=search_pattern,
|
|
1953
|
+
jq_expr=jq_expr,
|
|
1954
|
+
head=head,
|
|
1830
1955
|
)
|
|
1831
1956
|
|
|
1832
1957
|
import anyio
|
|
@@ -1876,18 +2001,22 @@ async def _mcp_session(
|
|
|
1876
2001
|
prompt_name: str | None = None,
|
|
1877
2002
|
prompt_arguments: dict | None = None,
|
|
1878
2003
|
search_pattern: str | None = None,
|
|
2004
|
+
jq_expr: str | None = None,
|
|
2005
|
+
head: int | None = None,
|
|
1879
2006
|
):
|
|
1880
2007
|
# Handle resource operations
|
|
1881
2008
|
if resource_action:
|
|
1882
2009
|
await _handle_resources(
|
|
1883
|
-
session, resource_action, resource_uri, pretty, raw, toon
|
|
2010
|
+
session, resource_action, resource_uri, pretty, raw, toon,
|
|
2011
|
+
jq_expr=jq_expr, head=head,
|
|
1884
2012
|
)
|
|
1885
2013
|
return
|
|
1886
2014
|
|
|
1887
2015
|
# Handle prompt operations
|
|
1888
2016
|
if prompt_action:
|
|
1889
2017
|
await _handle_prompts(
|
|
1890
|
-
session, prompt_action, prompt_name, prompt_arguments, pretty, raw, toon
|
|
2018
|
+
session, prompt_action, prompt_name, prompt_arguments, pretty, raw, toon,
|
|
2019
|
+
jq_expr=jq_expr, head=head,
|
|
1891
2020
|
)
|
|
1892
2021
|
return
|
|
1893
2022
|
|
|
@@ -1923,7 +2052,7 @@ async def _mcp_session(
|
|
|
1923
2052
|
result = await session.call_tool(tool_name, arguments or {})
|
|
1924
2053
|
|
|
1925
2054
|
text = _extract_content_parts(result.content)
|
|
1926
|
-
output_result(text, pretty=pretty, raw=raw, toon=toon)
|
|
2055
|
+
output_result(text, pretty=pretty, raw=raw, toon=toon, jq_expr=jq_expr, head=head)
|
|
1927
2056
|
|
|
1928
2057
|
|
|
1929
2058
|
# ---------------------------------------------------------------------------
|
|
@@ -1932,8 +2061,10 @@ async def _mcp_session(
|
|
|
1932
2061
|
|
|
1933
2062
|
|
|
1934
2063
|
async def _handle_resources(
|
|
1935
|
-
session, action: str, uri: str | None, pretty: bool, raw: bool, toon: bool
|
|
2064
|
+
session, action: str, uri: str | None, pretty: bool, raw: bool, toon: bool,
|
|
2065
|
+
jq_expr: str | None = None, head: int | None = None,
|
|
1936
2066
|
):
|
|
2067
|
+
_out = dict(pretty=pretty, raw=raw, toon=toon, jq_expr=jq_expr, head=head)
|
|
1937
2068
|
if action == "list":
|
|
1938
2069
|
result = await session.list_resources()
|
|
1939
2070
|
data = [
|
|
@@ -1945,7 +2076,7 @@ async def _handle_resources(
|
|
|
1945
2076
|
}
|
|
1946
2077
|
for r in result.resources
|
|
1947
2078
|
]
|
|
1948
|
-
output_result(data,
|
|
2079
|
+
output_result(data, **_out)
|
|
1949
2080
|
elif action == "templates":
|
|
1950
2081
|
result = await session.list_resource_templates()
|
|
1951
2082
|
data = [
|
|
@@ -1957,7 +2088,7 @@ async def _handle_resources(
|
|
|
1957
2088
|
}
|
|
1958
2089
|
for t in result.resourceTemplates
|
|
1959
2090
|
]
|
|
1960
|
-
output_result(data,
|
|
2091
|
+
output_result(data, **_out)
|
|
1961
2092
|
elif action == "read":
|
|
1962
2093
|
from pydantic import AnyUrl
|
|
1963
2094
|
|
|
@@ -1969,7 +2100,7 @@ async def _handle_resources(
|
|
|
1969
2100
|
elif hasattr(content, "blob"):
|
|
1970
2101
|
parts.append(content.blob)
|
|
1971
2102
|
text = "\n".join(parts) if parts else ""
|
|
1972
|
-
output_result(text,
|
|
2103
|
+
output_result(text, **_out)
|
|
1973
2104
|
|
|
1974
2105
|
|
|
1975
2106
|
# ---------------------------------------------------------------------------
|
|
@@ -1985,7 +2116,10 @@ async def _handle_prompts(
|
|
|
1985
2116
|
pretty: bool,
|
|
1986
2117
|
raw: bool,
|
|
1987
2118
|
toon: bool,
|
|
2119
|
+
jq_expr: str | None = None,
|
|
2120
|
+
head: int | None = None,
|
|
1988
2121
|
):
|
|
2122
|
+
_out = dict(pretty=pretty, raw=raw, toon=toon, jq_expr=jq_expr, head=head)
|
|
1989
2123
|
if action == "list":
|
|
1990
2124
|
result = await session.list_prompts()
|
|
1991
2125
|
data = [
|
|
@@ -2003,7 +2137,7 @@ async def _handle_prompts(
|
|
|
2003
2137
|
}
|
|
2004
2138
|
for p in result.prompts
|
|
2005
2139
|
]
|
|
2006
|
-
output_result(data,
|
|
2140
|
+
output_result(data, **_out)
|
|
2007
2141
|
elif action == "get":
|
|
2008
2142
|
result = await session.get_prompt(name, arguments or {})
|
|
2009
2143
|
messages = []
|
|
@@ -2016,7 +2150,7 @@ async def _handle_prompts(
|
|
|
2016
2150
|
{"role": msg.role, "content": json.dumps(content.model_dump())}
|
|
2017
2151
|
)
|
|
2018
2152
|
data = {"description": result.description or "", "messages": messages}
|
|
2019
|
-
output_result(data,
|
|
2153
|
+
output_result(data, **_out)
|
|
2020
2154
|
|
|
2021
2155
|
|
|
2022
2156
|
# ---------------------------------------------------------------------------
|
|
@@ -2521,6 +2655,8 @@ def handle_mcp(
|
|
|
2521
2655
|
prompt_arguments: dict | None = None,
|
|
2522
2656
|
search_pattern: str | None = None,
|
|
2523
2657
|
bake_config: BakeConfig | None = None,
|
|
2658
|
+
jq_expr: str | None = None,
|
|
2659
|
+
head: int | None = None,
|
|
2524
2660
|
):
|
|
2525
2661
|
key = cache_key_override or cache_key_for(source)
|
|
2526
2662
|
|
|
@@ -2532,6 +2668,8 @@ def handle_mcp(
|
|
|
2532
2668
|
prompt_action=prompt_action,
|
|
2533
2669
|
prompt_name=prompt_name,
|
|
2534
2670
|
prompt_arguments=prompt_arguments,
|
|
2671
|
+
jq_expr=jq_expr,
|
|
2672
|
+
head=head,
|
|
2535
2673
|
)
|
|
2536
2674
|
_dispatch_mcp_call(
|
|
2537
2675
|
source, is_stdio, auth_headers, env_vars,
|
|
@@ -2560,6 +2698,7 @@ def handle_mcp(
|
|
|
2560
2698
|
None, None, True, pretty, raw, key, ttl, refresh,
|
|
2561
2699
|
toon=toon, transport=transport, oauth_provider=oauth_provider,
|
|
2562
2700
|
search_pattern=search_pattern,
|
|
2701
|
+
jq_expr=jq_expr, head=head,
|
|
2563
2702
|
)
|
|
2564
2703
|
return
|
|
2565
2704
|
|
|
@@ -2604,6 +2743,7 @@ def handle_mcp(
|
|
|
2604
2743
|
source, is_stdio, auth_headers, env_vars,
|
|
2605
2744
|
cmd.tool_name, arguments, False, pretty, raw, key, ttl, refresh,
|
|
2606
2745
|
toon=toon, transport=transport, oauth_provider=oauth_provider,
|
|
2746
|
+
jq_expr=jq_expr, head=head,
|
|
2607
2747
|
)
|
|
2608
2748
|
|
|
2609
2749
|
|
|
@@ -2786,6 +2926,19 @@ def _build_main_parser() -> argparse.ArgumentParser:
|
|
|
2786
2926
|
"of large result sets. Requires @toon-format/cli (npm install -g @toon-format/cli)."
|
|
2787
2927
|
),
|
|
2788
2928
|
)
|
|
2929
|
+
pre.add_argument(
|
|
2930
|
+
"--jq",
|
|
2931
|
+
default=None,
|
|
2932
|
+
metavar="EXPR",
|
|
2933
|
+
help="Filter JSON output through jq (e.g. '.[] | .name'). Requires jq installed.",
|
|
2934
|
+
)
|
|
2935
|
+
pre.add_argument(
|
|
2936
|
+
"--head",
|
|
2937
|
+
type=int,
|
|
2938
|
+
default=None,
|
|
2939
|
+
metavar="N",
|
|
2940
|
+
help="Limit output to first N records (arrays) or N lines (text)",
|
|
2941
|
+
)
|
|
2789
2942
|
pre.add_argument(
|
|
2790
2943
|
"--fields",
|
|
2791
2944
|
default=None,
|
|
@@ -3005,13 +3158,15 @@ def _handle_session_operations(
|
|
|
3005
3158
|
if pre_args.list_resources:
|
|
3006
3159
|
result = _session_request(sess_name, "list_resources")
|
|
3007
3160
|
output_result(
|
|
3008
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon
|
|
3161
|
+
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3162
|
+
jq_expr=pre_args.jq, head=pre_args.head,
|
|
3009
3163
|
)
|
|
3010
3164
|
return True
|
|
3011
3165
|
if pre_args.list_resource_templates:
|
|
3012
3166
|
result = _session_request(sess_name, "list_resource_templates")
|
|
3013
3167
|
output_result(
|
|
3014
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon
|
|
3168
|
+
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3169
|
+
jq_expr=pre_args.jq, head=pre_args.head,
|
|
3015
3170
|
)
|
|
3016
3171
|
return True
|
|
3017
3172
|
if pre_args.read_resource:
|
|
@@ -3019,13 +3174,15 @@ def _handle_session_operations(
|
|
|
3019
3174
|
sess_name, "read_resource", {"uri": pre_args.read_resource}
|
|
3020
3175
|
)
|
|
3021
3176
|
output_result(
|
|
3022
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon
|
|
3177
|
+
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3178
|
+
jq_expr=pre_args.jq, head=pre_args.head,
|
|
3023
3179
|
)
|
|
3024
3180
|
return True
|
|
3025
3181
|
if pre_args.list_prompts:
|
|
3026
3182
|
result = _session_request(sess_name, "list_prompts")
|
|
3027
3183
|
output_result(
|
|
3028
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon
|
|
3184
|
+
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3185
|
+
jq_expr=pre_args.jq, head=pre_args.head,
|
|
3029
3186
|
)
|
|
3030
3187
|
return True
|
|
3031
3188
|
if pre_args.get_prompt:
|
|
@@ -3040,7 +3197,8 @@ def _handle_session_operations(
|
|
|
3040
3197
|
{"name": pre_args.get_prompt, "arguments": p_args},
|
|
3041
3198
|
)
|
|
3042
3199
|
output_result(
|
|
3043
|
-
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon
|
|
3200
|
+
result, pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
3201
|
+
jq_expr=pre_args.jq, head=pre_args.head,
|
|
3044
3202
|
)
|
|
3045
3203
|
return True
|
|
3046
3204
|
if pre_args.list_commands:
|
|
@@ -3202,6 +3360,7 @@ def _handle_openapi_mode(
|
|
|
3202
3360
|
args, cmd, base_url, auth_headers,
|
|
3203
3361
|
pre_args.pretty, pre_args.raw, toon=pre_args.toon,
|
|
3204
3362
|
oauth_provider=oauth_provider,
|
|
3363
|
+
jq_expr=pre_args.jq, head=pre_args.head,
|
|
3205
3364
|
)
|
|
3206
3365
|
|
|
3207
3366
|
|
|
@@ -3215,6 +3374,11 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
3215
3374
|
pre_args, leftover = pre.parse_known_args(global_argv)
|
|
3216
3375
|
remaining = leftover + tool_argv
|
|
3217
3376
|
|
|
3377
|
+
# Validate mutually exclusive output flags
|
|
3378
|
+
if pre_args.jq and pre_args.toon:
|
|
3379
|
+
print("Error: --jq and --toon are mutually exclusive.", file=sys.stderr)
|
|
3380
|
+
sys.exit(1)
|
|
3381
|
+
|
|
3218
3382
|
# --search implies --list
|
|
3219
3383
|
search_pattern = pre_args.search_pattern
|
|
3220
3384
|
if search_pattern:
|
|
@@ -3253,6 +3417,8 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
3253
3417
|
toon=pre_args.toon,
|
|
3254
3418
|
fields_override=pre_args.fields,
|
|
3255
3419
|
oauth_provider=oauth_provider,
|
|
3420
|
+
jq_expr=pre_args.jq,
|
|
3421
|
+
head=pre_args.head,
|
|
3256
3422
|
)
|
|
3257
3423
|
return
|
|
3258
3424
|
|
|
@@ -3282,6 +3448,8 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
3282
3448
|
prompt_arguments=prompt_arguments,
|
|
3283
3449
|
search_pattern=search_pattern,
|
|
3284
3450
|
bake_config=bake_config,
|
|
3451
|
+
jq_expr=pre_args.jq,
|
|
3452
|
+
head=pre_args.head,
|
|
3285
3453
|
)
|
|
3286
3454
|
return
|
|
3287
3455
|
|
|
File without changes
|
|
File without changes
|