mcp2cli 2.6.1__tar.gz → 2.8.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.6.1 → mcp2cli-2.8.0}/PKG-INFO +1 -1
- {mcp2cli-2.6.1 → mcp2cli-2.8.0}/pyproject.toml +1 -1
- {mcp2cli-2.6.1 → mcp2cli-2.8.0}/src/mcp2cli/__init__.py +285 -24
- {mcp2cli-2.6.1 → mcp2cli-2.8.0}/README.md +0 -0
- {mcp2cli-2.6.1 → mcp2cli-2.8.0}/src/mcp2cli/__main__.py +0 -0
- {mcp2cli-2.6.1 → mcp2cli-2.8.0}/src/mcp2cli/py.typed +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
__version__ = "2.
|
|
5
|
+
__version__ = "2.7.0"
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import copy
|
|
@@ -26,6 +26,8 @@ from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
|
26
26
|
from pathlib import Path
|
|
27
27
|
from urllib.parse import parse_qs, urlparse
|
|
28
28
|
|
|
29
|
+
from datetime import datetime, timezone
|
|
30
|
+
|
|
29
31
|
import anyio
|
|
30
32
|
import httpx
|
|
31
33
|
|
|
@@ -33,6 +35,7 @@ CACHE_DIR = Path(
|
|
|
33
35
|
os.environ.get("MCP2CLI_CACHE_DIR", Path.home() / ".cache" / "mcp2cli")
|
|
34
36
|
)
|
|
35
37
|
DEFAULT_CACHE_TTL = 3600
|
|
38
|
+
USAGE_FILE = CACHE_DIR / "usage.json"
|
|
36
39
|
CONFIG_DIR = Path(
|
|
37
40
|
os.environ.get("MCP2CLI_CONFIG_DIR", Path.home() / ".config" / "mcp2cli")
|
|
38
41
|
)
|
|
@@ -387,6 +390,94 @@ def save_cache(key: str, data: dict):
|
|
|
387
390
|
(CACHE_DIR / f"{key}.json").write_text(json.dumps(data))
|
|
388
391
|
|
|
389
392
|
|
|
393
|
+
# ---------------------------------------------------------------------------
|
|
394
|
+
# Usage tracking
|
|
395
|
+
# ---------------------------------------------------------------------------
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _load_usage() -> dict:
|
|
399
|
+
"""Load the usage tracking file. Returns empty dict on any failure."""
|
|
400
|
+
if not USAGE_FILE.exists():
|
|
401
|
+
return {}
|
|
402
|
+
try:
|
|
403
|
+
return json.loads(USAGE_FILE.read_text())
|
|
404
|
+
except (json.JSONDecodeError, OSError):
|
|
405
|
+
return {}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _save_usage(data: dict) -> None:
|
|
409
|
+
"""Write usage data. Last-write-wins -- no file locking."""
|
|
410
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
411
|
+
USAGE_FILE.write_text(json.dumps(data, indent=2))
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def record_usage(source_hash: str, tool_name: str) -> None:
|
|
415
|
+
"""Increment the call count and update last_used for a tool."""
|
|
416
|
+
usage = _load_usage()
|
|
417
|
+
bucket = usage.setdefault(source_hash, {})
|
|
418
|
+
entry = bucket.setdefault(tool_name, {"count": 0, "last_used": ""})
|
|
419
|
+
entry["count"] += 1
|
|
420
|
+
entry["last_used"] = datetime.now(timezone.utc).isoformat()
|
|
421
|
+
_save_usage(usage)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _source_hash_for(source: str) -> str:
|
|
425
|
+
"""Derive a stable hash key from a source URL/command string."""
|
|
426
|
+
return hashlib.sha256(source.encode()).hexdigest()[:16]
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def sort_commands(
|
|
430
|
+
commands: list["CommandDef"],
|
|
431
|
+
sort_mode: str,
|
|
432
|
+
source_hash: str,
|
|
433
|
+
) -> list["CommandDef"]:
|
|
434
|
+
"""Sort commands by the given mode using usage data.
|
|
435
|
+
|
|
436
|
+
Modes:
|
|
437
|
+
usage -- most-called first (default when usage data exists)
|
|
438
|
+
recent -- most-recently-used first
|
|
439
|
+
alpha -- alphabetical by name
|
|
440
|
+
default -- original insertion order
|
|
441
|
+
"""
|
|
442
|
+
if sort_mode == "default":
|
|
443
|
+
return commands
|
|
444
|
+
if sort_mode == "alpha":
|
|
445
|
+
return sorted(commands, key=lambda c: c.name)
|
|
446
|
+
|
|
447
|
+
usage = _load_usage().get(source_hash, {})
|
|
448
|
+
if not usage:
|
|
449
|
+
return commands # no data, keep insertion order
|
|
450
|
+
|
|
451
|
+
def _usage_key(c: "CommandDef") -> str:
|
|
452
|
+
return c.tool_name or c.graphql_field_name or c.name
|
|
453
|
+
|
|
454
|
+
if sort_mode == "usage":
|
|
455
|
+
return sorted(
|
|
456
|
+
commands,
|
|
457
|
+
key=lambda c: usage.get(_usage_key(c), {}).get("count", 0),
|
|
458
|
+
reverse=True,
|
|
459
|
+
)
|
|
460
|
+
if sort_mode == "recent":
|
|
461
|
+
return sorted(
|
|
462
|
+
commands,
|
|
463
|
+
key=lambda c: usage.get(_usage_key(c), {}).get("last_used", ""),
|
|
464
|
+
reverse=True,
|
|
465
|
+
)
|
|
466
|
+
return commands
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _resolve_sort_mode(explicit_sort: str | None, source_hash: str) -> str:
|
|
470
|
+
"""Determine the effective sort mode.
|
|
471
|
+
|
|
472
|
+
If the user passed --sort explicitly, use that. Otherwise, default to
|
|
473
|
+
'usage' when usage data exists for this source, else 'default'.
|
|
474
|
+
"""
|
|
475
|
+
if explicit_sort is not None:
|
|
476
|
+
return explicit_sort
|
|
477
|
+
usage = _load_usage().get(source_hash, {})
|
|
478
|
+
return "usage" if usage else "default"
|
|
479
|
+
|
|
480
|
+
|
|
390
481
|
# ---------------------------------------------------------------------------
|
|
391
482
|
# OAuth support
|
|
392
483
|
# ---------------------------------------------------------------------------
|
|
@@ -1225,8 +1316,20 @@ def _wrap_description(description: str, indent: int, total_width: int = 110) ->
|
|
|
1225
1316
|
)
|
|
1226
1317
|
|
|
1227
1318
|
|
|
1228
|
-
def list_graphql_commands(
|
|
1319
|
+
def list_graphql_commands(
|
|
1320
|
+
commands: list[CommandDef],
|
|
1321
|
+
verbose: bool = False,
|
|
1322
|
+
compact: bool = False,
|
|
1323
|
+
source_hash: str = "",
|
|
1324
|
+
sort_mode: str | None = None,
|
|
1325
|
+
top: int | None = None,
|
|
1326
|
+
):
|
|
1229
1327
|
"""Group commands by operation type and print."""
|
|
1328
|
+
commands = _apply_list_options(commands, source_hash, sort_mode, top)
|
|
1329
|
+
|
|
1330
|
+
if compact:
|
|
1331
|
+
print(" ".join(cmd.name for cmd in commands))
|
|
1332
|
+
return
|
|
1230
1333
|
|
|
1231
1334
|
groups: dict[str, list[CommandDef]] = {}
|
|
1232
1335
|
for cmd in commands:
|
|
@@ -1364,19 +1467,30 @@ def handle_graphql(
|
|
|
1364
1467
|
jq_expr: str | None = None,
|
|
1365
1468
|
head: int | None = None,
|
|
1366
1469
|
verbose: bool = False,
|
|
1470
|
+
sort_mode: str | None = None,
|
|
1471
|
+
top: int | None = None,
|
|
1472
|
+
compact: bool = False,
|
|
1367
1473
|
):
|
|
1368
1474
|
"""Top-level handler for --graphql mode."""
|
|
1475
|
+
src_hash = _source_hash_for(url)
|
|
1369
1476
|
schema = load_graphql_schema(url, auth_headers, cache_key, ttl, refresh, oauth_provider=oauth_provider)
|
|
1370
1477
|
commands = extract_graphql_commands(schema)
|
|
1371
1478
|
|
|
1479
|
+
list_kwargs = dict(
|
|
1480
|
+
verbose=verbose, compact=compact,
|
|
1481
|
+
source_hash=src_hash, sort_mode=sort_mode, top=top,
|
|
1482
|
+
)
|
|
1483
|
+
|
|
1372
1484
|
if list_mode:
|
|
1373
|
-
list_graphql_commands(commands,
|
|
1485
|
+
list_graphql_commands(commands, **list_kwargs)
|
|
1374
1486
|
return
|
|
1375
1487
|
|
|
1376
1488
|
if not remaining:
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1489
|
+
if not compact:
|
|
1490
|
+
print("Available operations:")
|
|
1491
|
+
list_graphql_commands(commands, **list_kwargs)
|
|
1492
|
+
if not compact:
|
|
1493
|
+
print("\nUse --list for the same output, or provide a subcommand.")
|
|
1380
1494
|
return
|
|
1381
1495
|
|
|
1382
1496
|
pre_for_gql = argparse.ArgumentParser(add_help=False)
|
|
@@ -1394,6 +1508,9 @@ def handle_graphql(
|
|
|
1394
1508
|
jq_expr=jq_expr, head=head,
|
|
1395
1509
|
)
|
|
1396
1510
|
|
|
1511
|
+
# Record usage after successful execution
|
|
1512
|
+
record_usage(src_hash, cmd.graphql_field_name or cmd.name)
|
|
1513
|
+
|
|
1397
1514
|
|
|
1398
1515
|
# ---------------------------------------------------------------------------
|
|
1399
1516
|
# Command filtering (bake mode)
|
|
@@ -1503,9 +1620,17 @@ _BAKE_NAME_RE = re.compile(r"^[a-z][a-z0-9-]*$")
|
|
|
1503
1620
|
|
|
1504
1621
|
def _handle_bake(argv: list[str]) -> None:
|
|
1505
1622
|
"""Dispatch bake subcommands."""
|
|
1506
|
-
if not argv:
|
|
1507
|
-
print("Usage: mcp2cli bake <
|
|
1508
|
-
|
|
1623
|
+
if not argv or argv[0] in ("-h", "--help"):
|
|
1624
|
+
print("Usage: mcp2cli bake <command> [options]\n")
|
|
1625
|
+
print("Commands:")
|
|
1626
|
+
print(" create Save connection settings as a named baked tool")
|
|
1627
|
+
print(" list List all baked tools")
|
|
1628
|
+
print(" show Show config for a baked tool (secrets masked)")
|
|
1629
|
+
print(" remove Delete a baked tool")
|
|
1630
|
+
print(" update Update settings on an existing baked tool")
|
|
1631
|
+
print(" install Create a ~/.local/bin wrapper script")
|
|
1632
|
+
print("\nRun 'mcp2cli bake <command> --help' for command-specific help.")
|
|
1633
|
+
sys.exit(0 if argv else 1)
|
|
1509
1634
|
sub = argv[0]
|
|
1510
1635
|
rest = argv[1:]
|
|
1511
1636
|
dispatch = {
|
|
@@ -1812,7 +1937,34 @@ def build_argparse(
|
|
|
1812
1937
|
# ---------------------------------------------------------------------------
|
|
1813
1938
|
|
|
1814
1939
|
|
|
1815
|
-
def
|
|
1940
|
+
def _apply_list_options(
|
|
1941
|
+
commands: list[CommandDef],
|
|
1942
|
+
source_hash: str = "",
|
|
1943
|
+
sort_mode: str | None = None,
|
|
1944
|
+
top: int | None = None,
|
|
1945
|
+
) -> list[CommandDef]:
|
|
1946
|
+
"""Apply sort and top-N filtering to a command list."""
|
|
1947
|
+
effective_sort = _resolve_sort_mode(sort_mode, source_hash)
|
|
1948
|
+
commands = sort_commands(commands, effective_sort, source_hash)
|
|
1949
|
+
if top is not None:
|
|
1950
|
+
commands = commands[:top]
|
|
1951
|
+
return commands
|
|
1952
|
+
|
|
1953
|
+
|
|
1954
|
+
def list_openapi_commands(
|
|
1955
|
+
commands: list[CommandDef],
|
|
1956
|
+
verbose: bool = False,
|
|
1957
|
+
compact: bool = False,
|
|
1958
|
+
source_hash: str = "",
|
|
1959
|
+
sort_mode: str | None = None,
|
|
1960
|
+
top: int | None = None,
|
|
1961
|
+
):
|
|
1962
|
+
commands = _apply_list_options(commands, source_hash, sort_mode, top)
|
|
1963
|
+
|
|
1964
|
+
if compact:
|
|
1965
|
+
print(" ".join(cmd.name for cmd in commands))
|
|
1966
|
+
return
|
|
1967
|
+
|
|
1816
1968
|
groups: dict[str, list[CommandDef]] = {}
|
|
1817
1969
|
for cmd in commands:
|
|
1818
1970
|
prefix = cmd.name.split("-", 1)[0] if "-" in cmd.name else "other"
|
|
@@ -1832,7 +1984,20 @@ def list_openapi_commands(commands: list[CommandDef], verbose: bool = False):
|
|
|
1832
1984
|
print(line)
|
|
1833
1985
|
|
|
1834
1986
|
|
|
1835
|
-
def list_mcp_commands(
|
|
1987
|
+
def list_mcp_commands(
|
|
1988
|
+
commands: list[CommandDef],
|
|
1989
|
+
verbose: bool = False,
|
|
1990
|
+
compact: bool = False,
|
|
1991
|
+
source_hash: str = "",
|
|
1992
|
+
sort_mode: str | None = None,
|
|
1993
|
+
top: int | None = None,
|
|
1994
|
+
):
|
|
1995
|
+
commands = _apply_list_options(commands, source_hash, sort_mode, top)
|
|
1996
|
+
|
|
1997
|
+
if compact:
|
|
1998
|
+
print(" ".join(cmd.name for cmd in commands))
|
|
1999
|
+
return
|
|
2000
|
+
|
|
1836
2001
|
for cmd in commands:
|
|
1837
2002
|
if cmd.description:
|
|
1838
2003
|
if verbose:
|
|
@@ -2022,6 +2187,10 @@ def run_mcp_http(
|
|
|
2022
2187
|
jq_expr: str | None = None,
|
|
2023
2188
|
head: int | None = None,
|
|
2024
2189
|
verbose: bool = False,
|
|
2190
|
+
sort_mode: str | None = None,
|
|
2191
|
+
top: int | None = None,
|
|
2192
|
+
compact: bool = False,
|
|
2193
|
+
source_hash: str = "",
|
|
2025
2194
|
):
|
|
2026
2195
|
extra = dict(
|
|
2027
2196
|
resource_action=resource_action,
|
|
@@ -2033,6 +2202,10 @@ def run_mcp_http(
|
|
|
2033
2202
|
jq_expr=jq_expr,
|
|
2034
2203
|
head=head,
|
|
2035
2204
|
verbose=verbose,
|
|
2205
|
+
sort_mode=sort_mode,
|
|
2206
|
+
top=top,
|
|
2207
|
+
compact=compact,
|
|
2208
|
+
source_hash=source_hash,
|
|
2036
2209
|
)
|
|
2037
2210
|
|
|
2038
2211
|
async def _run():
|
|
@@ -2119,6 +2292,10 @@ def run_mcp_stdio(
|
|
|
2119
2292
|
jq_expr: str | None = None,
|
|
2120
2293
|
head: int | None = None,
|
|
2121
2294
|
verbose: bool = False,
|
|
2295
|
+
sort_mode: str | None = None,
|
|
2296
|
+
top: int | None = None,
|
|
2297
|
+
compact: bool = False,
|
|
2298
|
+
source_hash: str = "",
|
|
2122
2299
|
):
|
|
2123
2300
|
extra = dict(
|
|
2124
2301
|
resource_action=resource_action,
|
|
@@ -2130,6 +2307,10 @@ def run_mcp_stdio(
|
|
|
2130
2307
|
jq_expr=jq_expr,
|
|
2131
2308
|
head=head,
|
|
2132
2309
|
verbose=verbose,
|
|
2310
|
+
sort_mode=sort_mode,
|
|
2311
|
+
top=top,
|
|
2312
|
+
compact=compact,
|
|
2313
|
+
source_hash=source_hash,
|
|
2133
2314
|
)
|
|
2134
2315
|
|
|
2135
2316
|
import anyio
|
|
@@ -2182,6 +2363,10 @@ async def _mcp_session(
|
|
|
2182
2363
|
jq_expr: str | None = None,
|
|
2183
2364
|
head: int | None = None,
|
|
2184
2365
|
verbose: bool = False,
|
|
2366
|
+
sort_mode: str | None = None,
|
|
2367
|
+
top: int | None = None,
|
|
2368
|
+
compact: bool = False,
|
|
2369
|
+
source_hash: str = "",
|
|
2185
2370
|
):
|
|
2186
2371
|
# Handle resource operations
|
|
2187
2372
|
if resource_action:
|
|
@@ -2199,6 +2384,11 @@ async def _mcp_session(
|
|
|
2199
2384
|
)
|
|
2200
2385
|
return
|
|
2201
2386
|
|
|
2387
|
+
list_kwargs = dict(
|
|
2388
|
+
verbose=verbose, compact=compact,
|
|
2389
|
+
source_hash=source_hash, sort_mode=sort_mode, top=top,
|
|
2390
|
+
)
|
|
2391
|
+
|
|
2202
2392
|
if list_mode:
|
|
2203
2393
|
result = await session.list_tools()
|
|
2204
2394
|
tools = [
|
|
@@ -2215,10 +2405,12 @@ async def _mcp_session(
|
|
|
2215
2405
|
if not commands:
|
|
2216
2406
|
print(f"\nNo tools matching '{search_pattern}'.")
|
|
2217
2407
|
return
|
|
2218
|
-
|
|
2408
|
+
if not compact:
|
|
2409
|
+
print(f"\nTools matching '{search_pattern}':")
|
|
2219
2410
|
else:
|
|
2220
|
-
|
|
2221
|
-
|
|
2411
|
+
if not compact:
|
|
2412
|
+
print("\nAvailable tools:")
|
|
2413
|
+
list_mcp_commands(commands, **list_kwargs)
|
|
2222
2414
|
return
|
|
2223
2415
|
|
|
2224
2416
|
if tool_name is None:
|
|
@@ -2837,6 +3029,9 @@ def handle_mcp(
|
|
|
2837
3029
|
jq_expr: str | None = None,
|
|
2838
3030
|
head: int | None = None,
|
|
2839
3031
|
verbose: bool = False,
|
|
3032
|
+
sort_mode: str | None = None,
|
|
3033
|
+
top: int | None = None,
|
|
3034
|
+
compact: bool = False,
|
|
2840
3035
|
):
|
|
2841
3036
|
# Build a config dict for cache key generation (future-proof)
|
|
2842
3037
|
config_for_cache = {
|
|
@@ -2846,8 +3041,9 @@ def handle_mcp(
|
|
|
2846
3041
|
'env_vars': env_vars,
|
|
2847
3042
|
'is_stdio': is_stdio,
|
|
2848
3043
|
}
|
|
2849
|
-
|
|
3044
|
+
|
|
2850
3045
|
key = cache_key_override or cache_key_for(config_for_cache)
|
|
3046
|
+
src_hash = _source_hash_for(source)
|
|
2851
3047
|
|
|
2852
3048
|
# Resource/prompt operations skip the tool flow entirely
|
|
2853
3049
|
if resource_action or prompt_action:
|
|
@@ -2868,9 +3064,14 @@ def handle_mcp(
|
|
|
2868
3064
|
)
|
|
2869
3065
|
return
|
|
2870
3066
|
|
|
3067
|
+
list_kwargs = dict(
|
|
3068
|
+
verbose=verbose, compact=compact,
|
|
3069
|
+
source_hash=src_hash, sort_mode=sort_mode, top=top,
|
|
3070
|
+
)
|
|
3071
|
+
|
|
2871
3072
|
if list_mode:
|
|
2872
3073
|
if bake_config and (bake_config.include or bake_config.exclude or bake_config.methods):
|
|
2873
|
-
# Fetch tools, filter, then list
|
|
3074
|
+
# Fetch tools, filter, then list -- don't delegate to unfiltered path
|
|
2874
3075
|
tools = _fetch_or_cache_mcp_tools(
|
|
2875
3076
|
key, ttl, refresh, source, is_stdio, auth_headers, env_vars,
|
|
2876
3077
|
transport=transport, oauth_provider=oauth_provider,
|
|
@@ -2879,8 +3080,9 @@ def handle_mcp(
|
|
|
2879
3080
|
commands = filter_commands(
|
|
2880
3081
|
commands, bake_config.include, bake_config.exclude, bake_config.methods,
|
|
2881
3082
|
)
|
|
2882
|
-
|
|
2883
|
-
|
|
3083
|
+
if not compact:
|
|
3084
|
+
print("\nAvailable tools:")
|
|
3085
|
+
list_mcp_commands(commands, **list_kwargs)
|
|
2884
3086
|
return
|
|
2885
3087
|
_dispatch_mcp_call(
|
|
2886
3088
|
source, is_stdio, auth_headers, env_vars,
|
|
@@ -2889,6 +3091,8 @@ def handle_mcp(
|
|
|
2889
3091
|
search_pattern=search_pattern,
|
|
2890
3092
|
jq_expr=jq_expr, head=head,
|
|
2891
3093
|
verbose=verbose,
|
|
3094
|
+
sort_mode=sort_mode, top=top, compact=compact,
|
|
3095
|
+
source_hash=src_hash,
|
|
2892
3096
|
)
|
|
2893
3097
|
return
|
|
2894
3098
|
|
|
@@ -2905,9 +3109,11 @@ def handle_mcp(
|
|
|
2905
3109
|
)
|
|
2906
3110
|
|
|
2907
3111
|
if not remaining:
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
3112
|
+
if not compact:
|
|
3113
|
+
print("Available tools:")
|
|
3114
|
+
list_mcp_commands(commands, **list_kwargs)
|
|
3115
|
+
if not compact:
|
|
3116
|
+
print("\nUse --list for the same output, or provide a subcommand.")
|
|
2911
3117
|
return
|
|
2912
3118
|
|
|
2913
3119
|
pre = argparse.ArgumentParser(add_help=False)
|
|
@@ -2936,6 +3142,9 @@ def handle_mcp(
|
|
|
2936
3142
|
jq_expr=jq_expr, head=head,
|
|
2937
3143
|
)
|
|
2938
3144
|
|
|
3145
|
+
# Record usage after successful execution
|
|
3146
|
+
record_usage(src_hash, cmd.tool_name or cmd.name)
|
|
3147
|
+
|
|
2939
3148
|
|
|
2940
3149
|
def _fetch_mcp_tools(
|
|
2941
3150
|
source: str,
|
|
@@ -3074,7 +3283,19 @@ def main():
|
|
|
3074
3283
|
|
|
3075
3284
|
def _build_main_parser() -> argparse.ArgumentParser:
|
|
3076
3285
|
"""Build the global ArgumentParser for _main_impl."""
|
|
3077
|
-
pre = argparse.ArgumentParser(
|
|
3286
|
+
pre = argparse.ArgumentParser(
|
|
3287
|
+
add_help=False,
|
|
3288
|
+
allow_abbrev=False,
|
|
3289
|
+
epilog=(
|
|
3290
|
+
"subcommands:\n"
|
|
3291
|
+
" bake Manage baked tool configurations\n"
|
|
3292
|
+
" (create, list, show, remove, update, install)\n"
|
|
3293
|
+
" @<name> Run a previously baked tool\n"
|
|
3294
|
+
"\n"
|
|
3295
|
+
"Run 'mcp2cli bake --help' for bake subcommand details."
|
|
3296
|
+
),
|
|
3297
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
3298
|
+
)
|
|
3078
3299
|
pre.add_argument("--spec", default=None, help="OpenAPI spec URL or file path")
|
|
3079
3300
|
pre.add_argument("--mcp", default=None, help="MCP server URL (HTTP/SSE)")
|
|
3080
3301
|
pre.add_argument("--mcp-stdio", default=None, help="MCP server command (stdio)")
|
|
@@ -3110,6 +3331,30 @@ def _build_main_parser() -> argparse.ArgumentParser:
|
|
|
3110
3331
|
dest="verbose",
|
|
3111
3332
|
help="Show full tool descriptions in --list output, wrapped to terminal width (default: truncated with ...)",
|
|
3112
3333
|
)
|
|
3334
|
+
pre.add_argument(
|
|
3335
|
+
"--sort",
|
|
3336
|
+
choices=["usage", "recent", "alpha", "default"],
|
|
3337
|
+
default=None,
|
|
3338
|
+
dest="sort_mode",
|
|
3339
|
+
help=(
|
|
3340
|
+
"Sort order for --list output. 'usage' sorts by call frequency, "
|
|
3341
|
+
"'recent' by last-used time, 'alpha' alphabetically, 'default' keeps "
|
|
3342
|
+
"insertion order. When omitted, defaults to 'usage' if usage data "
|
|
3343
|
+
"exists, otherwise 'default'."
|
|
3344
|
+
),
|
|
3345
|
+
)
|
|
3346
|
+
pre.add_argument(
|
|
3347
|
+
"--top",
|
|
3348
|
+
type=int,
|
|
3349
|
+
default=None,
|
|
3350
|
+
metavar="N",
|
|
3351
|
+
help="Show only the top N tools in --list output (useful for LLM agents)",
|
|
3352
|
+
)
|
|
3353
|
+
pre.add_argument(
|
|
3354
|
+
"--compact",
|
|
3355
|
+
action="store_true",
|
|
3356
|
+
help="Space-separated tool names only, no descriptions (~2 tokens/tool)",
|
|
3357
|
+
)
|
|
3113
3358
|
pre.add_argument("--pretty", action="store_true", help="Pretty-print JSON output")
|
|
3114
3359
|
pre.add_argument("--raw", action="store_true", help="Print raw response body")
|
|
3115
3360
|
pre.add_argument(
|
|
@@ -3521,6 +3766,7 @@ def _handle_openapi_mode(
|
|
|
3521
3766
|
oauth_provider: "httpx.Auth | None" = None,
|
|
3522
3767
|
) -> None:
|
|
3523
3768
|
"""Execute OpenAPI mode: load spec, build parser, execute."""
|
|
3769
|
+
src_hash = _source_hash_for(pre_args.spec)
|
|
3524
3770
|
spec = load_openapi_spec(
|
|
3525
3771
|
pre_args.spec,
|
|
3526
3772
|
auth_headers,
|
|
@@ -3535,14 +3781,20 @@ def _handle_openapi_mode(
|
|
|
3535
3781
|
commands, bake_config.include, bake_config.exclude, bake_config.methods,
|
|
3536
3782
|
)
|
|
3537
3783
|
|
|
3784
|
+
list_kwargs = dict(
|
|
3785
|
+
verbose=pre_args.verbose, compact=pre_args.compact,
|
|
3786
|
+
source_hash=src_hash, sort_mode=pre_args.sort_mode, top=pre_args.top,
|
|
3787
|
+
)
|
|
3788
|
+
|
|
3538
3789
|
if pre_args.list_commands:
|
|
3539
3790
|
if search_pattern:
|
|
3540
3791
|
commands = _filter_commands(commands, search_pattern)
|
|
3541
3792
|
if not commands:
|
|
3542
3793
|
print(f"\nNo tools matching '{search_pattern}'.")
|
|
3543
3794
|
return
|
|
3544
|
-
|
|
3545
|
-
|
|
3795
|
+
if not pre_args.compact:
|
|
3796
|
+
print(f"\nTools matching '{search_pattern}':")
|
|
3797
|
+
list_openapi_commands(commands, **list_kwargs)
|
|
3546
3798
|
return
|
|
3547
3799
|
|
|
3548
3800
|
if not remaining:
|
|
@@ -3588,6 +3840,9 @@ def _handle_openapi_mode(
|
|
|
3588
3840
|
jq_expr=pre_args.jq, head=pre_args.head,
|
|
3589
3841
|
)
|
|
3590
3842
|
|
|
3843
|
+
# Record usage after successful execution
|
|
3844
|
+
record_usage(src_hash, cmd.tool_name or cmd.graphql_field_name or cmd.name)
|
|
3845
|
+
|
|
3591
3846
|
|
|
3592
3847
|
def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
3593
3848
|
pre = _build_main_parser()
|
|
@@ -3645,6 +3900,9 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
3645
3900
|
jq_expr=pre_args.jq,
|
|
3646
3901
|
head=pre_args.head,
|
|
3647
3902
|
verbose=pre_args.verbose,
|
|
3903
|
+
sort_mode=pre_args.sort_mode,
|
|
3904
|
+
top=pre_args.top,
|
|
3905
|
+
compact=pre_args.compact,
|
|
3648
3906
|
)
|
|
3649
3907
|
return
|
|
3650
3908
|
|
|
@@ -3677,6 +3935,9 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
3677
3935
|
jq_expr=pre_args.jq,
|
|
3678
3936
|
head=pre_args.head,
|
|
3679
3937
|
verbose=pre_args.verbose,
|
|
3938
|
+
sort_mode=pre_args.sort_mode,
|
|
3939
|
+
top=pre_args.top,
|
|
3940
|
+
compact=pre_args.compact,
|
|
3680
3941
|
)
|
|
3681
3942
|
return
|
|
3682
3943
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|