mcp2cli 3.6.0__tar.gz → 3.7.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.6.0 → mcp2cli-3.7.0}/PKG-INFO +27 -1
- {mcp2cli-3.6.0 → mcp2cli-3.7.0}/README.md +26 -0
- {mcp2cli-3.6.0 → mcp2cli-3.7.0}/pyproject.toml +1 -1
- {mcp2cli-3.6.0 → mcp2cli-3.7.0}/pyproject.toml.orig +1 -1
- {mcp2cli-3.6.0 → mcp2cli-3.7.0}/src/mcp2cli/__init__.py +173 -11
- {mcp2cli-3.6.0 → mcp2cli-3.7.0}/src/mcp2cli/__main__.py +0 -0
- {mcp2cli-3.6.0 → mcp2cli-3.7.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.7.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>
|
|
@@ -158,6 +158,30 @@ mcp2cli --mcp-stdio "node server.js" --env API_KEY=sk-... --env DEBUG=1 \
|
|
|
158
158
|
search --query "test"
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
### MCP roots and completion
|
|
162
|
+
|
|
163
|
+
Expose one or more filesystem roots when a server scopes operations to a
|
|
164
|
+
workspace. Paths are converted to `file://` URIs; explicit roots must also use
|
|
165
|
+
the `file://` scheme.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
mcp2cli --mcp-stdio "npx @modelcontextprotocol/server-filesystem /tmp" \
|
|
169
|
+
--root "$PWD" --root file:///var/shared --list
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Request prompt-argument or resource-template completions with
|
|
173
|
+
`REF:ARG=PREFIX`:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
mcp2cli --mcp https://example.com/mcp \
|
|
177
|
+
--complete "greeting:name=San"
|
|
178
|
+
mcp2cli --mcp https://example.com/mcp \
|
|
179
|
+
--complete "file:///docs/{topic}:topic=api"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Both options work when starting a persistent session; roots are retained by
|
|
183
|
+
the session daemon and completion requests can be sent through `--session`.
|
|
184
|
+
|
|
161
185
|
### OpenAPI mode
|
|
162
186
|
|
|
163
187
|
```bash
|
|
@@ -338,6 +362,8 @@ Options:
|
|
|
338
362
|
--base-url URL Override base URL from spec
|
|
339
363
|
--transport TYPE MCP HTTP transport: auto|sse|streamable (default: auto)
|
|
340
364
|
--env KEY=VALUE Env var for MCP stdio server (repeatable)
|
|
365
|
+
--root PATH|FILE_URI Expose a filesystem root to an MCP server (repeatable)
|
|
366
|
+
--complete SPEC Complete an MCP prompt or resource-template argument
|
|
341
367
|
--oauth Enable OAuth (authorization code + PKCE flow)
|
|
342
368
|
--oauth-client-id ID OAuth client ID (supports env:/file: prefixes)
|
|
343
369
|
--oauth-client-secret S OAuth client secret (supports env:/file: prefixes)
|
|
@@ -139,6 +139,30 @@ mcp2cli --mcp-stdio "node server.js" --env API_KEY=sk-... --env DEBUG=1 \
|
|
|
139
139
|
search --query "test"
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
### MCP roots and completion
|
|
143
|
+
|
|
144
|
+
Expose one or more filesystem roots when a server scopes operations to a
|
|
145
|
+
workspace. Paths are converted to `file://` URIs; explicit roots must also use
|
|
146
|
+
the `file://` scheme.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
mcp2cli --mcp-stdio "npx @modelcontextprotocol/server-filesystem /tmp" \
|
|
150
|
+
--root "$PWD" --root file:///var/shared --list
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Request prompt-argument or resource-template completions with
|
|
154
|
+
`REF:ARG=PREFIX`:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
mcp2cli --mcp https://example.com/mcp \
|
|
158
|
+
--complete "greeting:name=San"
|
|
159
|
+
mcp2cli --mcp https://example.com/mcp \
|
|
160
|
+
--complete "file:///docs/{topic}:topic=api"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Both options work when starting a persistent session; roots are retained by
|
|
164
|
+
the session daemon and completion requests can be sent through `--session`.
|
|
165
|
+
|
|
142
166
|
### OpenAPI mode
|
|
143
167
|
|
|
144
168
|
```bash
|
|
@@ -319,6 +343,8 @@ Options:
|
|
|
319
343
|
--base-url URL Override base URL from spec
|
|
320
344
|
--transport TYPE MCP HTTP transport: auto|sse|streamable (default: auto)
|
|
321
345
|
--env KEY=VALUE Env var for MCP stdio server (repeatable)
|
|
346
|
+
--root PATH|FILE_URI Expose a filesystem root to an MCP server (repeatable)
|
|
347
|
+
--complete SPEC Complete an MCP prompt or resource-template argument
|
|
322
348
|
--oauth Enable OAuth (authorization code + PKCE flow)
|
|
323
349
|
--oauth-client-id ID OAuth client ID (supports env:/file: prefixes)
|
|
324
350
|
--oauth-client-secret S OAuth client secret (supports env:/file: prefixes)
|
|
@@ -38,6 +38,8 @@ CACHE_DIR = Path(
|
|
|
38
38
|
os.environ.get("MCP2CLI_CACHE_DIR", Path.home() / ".cache" / "mcp2cli")
|
|
39
39
|
)
|
|
40
40
|
DEFAULT_CACHE_TTL = 3600
|
|
41
|
+
# Client-side capability the user opted into (see --root).
|
|
42
|
+
_ROOTS: list[str] = []
|
|
41
43
|
USAGE_FILE = CACHE_DIR / "usage.json"
|
|
42
44
|
CONFIG_DIR = Path(
|
|
43
45
|
os.environ.get("MCP2CLI_CONFIG_DIR", Path.home() / ".config" / "mcp2cli")
|
|
@@ -322,6 +324,7 @@ _MCP_RENAMED_FIELDS = {
|
|
|
322
324
|
"mimeType": "mime_type",
|
|
323
325
|
"structuredContent": "structured_content",
|
|
324
326
|
"isError": "is_error",
|
|
327
|
+
"hasMore": "has_more",
|
|
325
328
|
}
|
|
326
329
|
|
|
327
330
|
|
|
@@ -2884,6 +2887,46 @@ def _run_mcp_clean(fn, source: str):
|
|
|
2884
2887
|
sys.exit(1)
|
|
2885
2888
|
|
|
2886
2889
|
|
|
2890
|
+
def _normalize_root(raw: str) -> str:
|
|
2891
|
+
"""Return a validated file URI for one ``--root`` value."""
|
|
2892
|
+
if "://" in raw:
|
|
2893
|
+
if not raw.casefold().startswith("file://"):
|
|
2894
|
+
raise ValueError(
|
|
2895
|
+
f"--root expects a filesystem path or file:// URI, got {raw!r}"
|
|
2896
|
+
)
|
|
2897
|
+
uri = raw
|
|
2898
|
+
else:
|
|
2899
|
+
uri = Path(raw).expanduser().resolve().as_uri()
|
|
2900
|
+
|
|
2901
|
+
from mcp import types
|
|
2902
|
+
|
|
2903
|
+
try:
|
|
2904
|
+
return str(types.Root(uri=uri).uri)
|
|
2905
|
+
except (TypeError, ValueError) as exc:
|
|
2906
|
+
raise ValueError(f"invalid --root value {raw!r}: {exc}") from exc
|
|
2907
|
+
|
|
2908
|
+
|
|
2909
|
+
def _roots_callback(root_uris: list[str] | None = None):
|
|
2910
|
+
"""Answer ``roots/list`` from validated file URIs, if any were given."""
|
|
2911
|
+
configured_roots = tuple(_ROOTS if root_uris is None else root_uris)
|
|
2912
|
+
if not configured_roots:
|
|
2913
|
+
return None
|
|
2914
|
+
|
|
2915
|
+
from mcp import types
|
|
2916
|
+
|
|
2917
|
+
async def list_roots(context=None):
|
|
2918
|
+
roots = [
|
|
2919
|
+
types.Root(
|
|
2920
|
+
uri=uri,
|
|
2921
|
+
name=Path(urlparse(uri).path).name or uri,
|
|
2922
|
+
)
|
|
2923
|
+
for uri in configured_roots
|
|
2924
|
+
]
|
|
2925
|
+
return types.ListRootsResult(roots=roots)
|
|
2926
|
+
|
|
2927
|
+
return list_roots
|
|
2928
|
+
|
|
2929
|
+
|
|
2887
2930
|
def run_mcp_http(
|
|
2888
2931
|
url: str,
|
|
2889
2932
|
auth_headers: list[tuple[str, str]],
|
|
@@ -2903,6 +2946,7 @@ def run_mcp_http(
|
|
|
2903
2946
|
prompt_action: str | None = None,
|
|
2904
2947
|
prompt_name: str | None = None,
|
|
2905
2948
|
prompt_arguments: dict | None = None,
|
|
2949
|
+
complete_spec: str | None = None,
|
|
2906
2950
|
search_pattern: str | None = None,
|
|
2907
2951
|
head: int | None = None,
|
|
2908
2952
|
verbose: bool = False,
|
|
@@ -2918,6 +2962,7 @@ def run_mcp_http(
|
|
|
2918
2962
|
prompt_action=prompt_action,
|
|
2919
2963
|
prompt_name=prompt_name,
|
|
2920
2964
|
prompt_arguments=prompt_arguments,
|
|
2965
|
+
complete_spec=complete_spec,
|
|
2921
2966
|
search_pattern=search_pattern,
|
|
2922
2967
|
head=head,
|
|
2923
2968
|
verbose=verbose,
|
|
@@ -2937,7 +2982,7 @@ def run_mcp_http(
|
|
|
2937
2982
|
async with _streamable_streams(
|
|
2938
2983
|
url, headers=headers, auth=oauth_provider
|
|
2939
2984
|
) as (read, write):
|
|
2940
|
-
async with ClientSession(read, write) as session:
|
|
2985
|
+
async with ClientSession(read, write, list_roots_callback=_roots_callback()) as session:
|
|
2941
2986
|
await session.initialize()
|
|
2942
2987
|
return await _mcp_session(
|
|
2943
2988
|
session,
|
|
@@ -2960,7 +3005,7 @@ def run_mcp_http(
|
|
|
2960
3005
|
read,
|
|
2961
3006
|
write,
|
|
2962
3007
|
):
|
|
2963
|
-
async with ClientSession(read, write) as session:
|
|
3008
|
+
async with ClientSession(read, write, list_roots_callback=_roots_callback()) as session:
|
|
2964
3009
|
await session.initialize()
|
|
2965
3010
|
return await _mcp_session(
|
|
2966
3011
|
session,
|
|
@@ -3008,6 +3053,7 @@ def run_mcp_stdio(
|
|
|
3008
3053
|
prompt_action: str | None = None,
|
|
3009
3054
|
prompt_name: str | None = None,
|
|
3010
3055
|
prompt_arguments: dict | None = None,
|
|
3056
|
+
complete_spec: str | None = None,
|
|
3011
3057
|
search_pattern: str | None = None,
|
|
3012
3058
|
head: int | None = None,
|
|
3013
3059
|
verbose: bool = False,
|
|
@@ -3023,6 +3069,7 @@ def run_mcp_stdio(
|
|
|
3023
3069
|
prompt_action=prompt_action,
|
|
3024
3070
|
prompt_name=prompt_name,
|
|
3025
3071
|
prompt_arguments=prompt_arguments,
|
|
3072
|
+
complete_spec=complete_spec,
|
|
3026
3073
|
search_pattern=search_pattern,
|
|
3027
3074
|
head=head,
|
|
3028
3075
|
verbose=verbose,
|
|
@@ -3044,7 +3091,7 @@ def run_mcp_stdio(
|
|
|
3044
3091
|
params = StdioServerParameters(command=parts[0], args=parts[1:], env=env)
|
|
3045
3092
|
|
|
3046
3093
|
async with stdio_client(params) as (read, write):
|
|
3047
|
-
async with ClientSession(read, write) as session:
|
|
3094
|
+
async with ClientSession(read, write, list_roots_callback=_roots_callback()) as session:
|
|
3048
3095
|
await session.initialize()
|
|
3049
3096
|
return await _mcp_session(
|
|
3050
3097
|
session,
|
|
@@ -3081,6 +3128,7 @@ async def _mcp_session(
|
|
|
3081
3128
|
prompt_action: str | None = None,
|
|
3082
3129
|
prompt_name: str | None = None,
|
|
3083
3130
|
prompt_arguments: dict | None = None,
|
|
3131
|
+
complete_spec: str | None = None,
|
|
3084
3132
|
search_pattern: str | None = None,
|
|
3085
3133
|
head: int | None = None,
|
|
3086
3134
|
verbose: bool = False,
|
|
@@ -3090,6 +3138,14 @@ async def _mcp_session(
|
|
|
3090
3138
|
source_hash: str = "",
|
|
3091
3139
|
json_output: bool = False,
|
|
3092
3140
|
):
|
|
3141
|
+
# Handle completion requests
|
|
3142
|
+
if complete_spec:
|
|
3143
|
+
await _handle_completion(
|
|
3144
|
+
session, complete_spec, pretty, raw, toon, head=head,
|
|
3145
|
+
json_output=json_output,
|
|
3146
|
+
)
|
|
3147
|
+
return
|
|
3148
|
+
|
|
3093
3149
|
# Handle resource operations
|
|
3094
3150
|
if resource_action:
|
|
3095
3151
|
await _handle_resources(
|
|
@@ -3232,6 +3288,67 @@ async def _handle_resources(
|
|
|
3232
3288
|
# ---------------------------------------------------------------------------
|
|
3233
3289
|
|
|
3234
3290
|
|
|
3291
|
+
def parse_complete_spec(spec: str) -> tuple[str, str, str]:
|
|
3292
|
+
"""Parse ``REF:ARG=PREFIX`` into (ref, argument, prefix).
|
|
3293
|
+
|
|
3294
|
+
``REF`` is a prompt name, or a resource URI template when it contains
|
|
3295
|
+
``://`` — in which case the scheme's own colons must not be mistaken for
|
|
3296
|
+
the ref/arg separator.
|
|
3297
|
+
"""
|
|
3298
|
+
ref_part, sep, prefix = spec.partition("=")
|
|
3299
|
+
if not sep:
|
|
3300
|
+
print(
|
|
3301
|
+
"Error: --complete expects REF:ARG=PREFIX (e.g. 'my-prompt:city=San')",
|
|
3302
|
+
file=sys.stderr,
|
|
3303
|
+
)
|
|
3304
|
+
sys.exit(1)
|
|
3305
|
+
ref, sep, argument = ref_part.rpartition(":")
|
|
3306
|
+
if not sep or not ref or not argument:
|
|
3307
|
+
print(
|
|
3308
|
+
f"Error: --complete could not split {ref_part!r} into REF:ARG",
|
|
3309
|
+
file=sys.stderr,
|
|
3310
|
+
)
|
|
3311
|
+
sys.exit(1)
|
|
3312
|
+
return ref, argument, prefix
|
|
3313
|
+
|
|
3314
|
+
|
|
3315
|
+
async def _completion_data(session, spec: str) -> dict:
|
|
3316
|
+
"""Run ``completion/complete`` and return its stable wire-shaped payload."""
|
|
3317
|
+
from mcp import types
|
|
3318
|
+
|
|
3319
|
+
ref_name, argument, prefix = parse_complete_spec(spec)
|
|
3320
|
+
if "://" in ref_name or "{" in ref_name:
|
|
3321
|
+
ref = types.ResourceTemplateReference(
|
|
3322
|
+
type="ref/resource", uri=ref_name
|
|
3323
|
+
)
|
|
3324
|
+
else:
|
|
3325
|
+
ref = types.PromptReference(type="ref/prompt", name=ref_name)
|
|
3326
|
+
|
|
3327
|
+
result = await session.complete(ref, {"name": argument, "value": prefix})
|
|
3328
|
+
completion = result.completion
|
|
3329
|
+
return {
|
|
3330
|
+
"values": list(completion.values or []),
|
|
3331
|
+
"total": completion.total,
|
|
3332
|
+
"hasMore": _mcp_attr(completion, "hasMore"),
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
|
|
3336
|
+
async def _handle_completion(
|
|
3337
|
+
session,
|
|
3338
|
+
spec: str,
|
|
3339
|
+
pretty: bool,
|
|
3340
|
+
raw: bool,
|
|
3341
|
+
toon: bool,
|
|
3342
|
+
head: int | None = None,
|
|
3343
|
+
json_output: bool = False,
|
|
3344
|
+
):
|
|
3345
|
+
"""Run ``completion/complete`` for a prompt or resource-template argument."""
|
|
3346
|
+
data = await _completion_data(session, spec)
|
|
3347
|
+
output_result(
|
|
3348
|
+
data, pretty=pretty, raw=raw, toon=toon, head=head, json_output=json_output
|
|
3349
|
+
)
|
|
3350
|
+
|
|
3351
|
+
|
|
3235
3352
|
async def _handle_prompts(
|
|
3236
3353
|
session,
|
|
3237
3354
|
action: str,
|
|
@@ -3354,6 +3471,7 @@ def session_start(
|
|
|
3354
3471
|
auth_headers: list[tuple[str, str]],
|
|
3355
3472
|
env_vars: dict[str, str],
|
|
3356
3473
|
transport: str = "auto",
|
|
3474
|
+
roots: list[str] | None = None,
|
|
3357
3475
|
):
|
|
3358
3476
|
"""Start a persistent session daemon."""
|
|
3359
3477
|
SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
|
|
@@ -3384,6 +3502,7 @@ def session_start(
|
|
|
3384
3502
|
"auth_headers": auth_headers,
|
|
3385
3503
|
"env_vars": env_vars,
|
|
3386
3504
|
"transport": transport,
|
|
3505
|
+
"roots": list(roots or []),
|
|
3387
3506
|
}
|
|
3388
3507
|
)
|
|
3389
3508
|
|
|
@@ -3536,6 +3655,10 @@ async def _dispatch_get_prompt(session, params):
|
|
|
3536
3655
|
return {"description": result.description or "", "messages": messages}
|
|
3537
3656
|
|
|
3538
3657
|
|
|
3658
|
+
async def _dispatch_complete(session, params):
|
|
3659
|
+
return await _completion_data(session, params["spec"])
|
|
3660
|
+
|
|
3661
|
+
|
|
3539
3662
|
_SESSION_DISPATCH = {
|
|
3540
3663
|
"list_tools": _dispatch_list_tools,
|
|
3541
3664
|
"call_tool": _dispatch_call_tool,
|
|
@@ -3544,6 +3667,7 @@ _SESSION_DISPATCH = {
|
|
|
3544
3667
|
"list_resource_templates": _dispatch_list_resource_templates,
|
|
3545
3668
|
"list_prompts": _dispatch_list_prompts,
|
|
3546
3669
|
"get_prompt": _dispatch_get_prompt,
|
|
3670
|
+
"complete": _dispatch_complete,
|
|
3547
3671
|
}
|
|
3548
3672
|
|
|
3549
3673
|
|
|
@@ -3556,6 +3680,7 @@ def _run_session_daemon(config_json: str):
|
|
|
3556
3680
|
auth_headers = [tuple(h) for h in config["auth_headers"]]
|
|
3557
3681
|
env_vars = config["env_vars"]
|
|
3558
3682
|
transport = config["transport"]
|
|
3683
|
+
roots = config.get("roots", [])
|
|
3559
3684
|
|
|
3560
3685
|
sock_path = _session_sock_path(name)
|
|
3561
3686
|
meta_path = _session_meta_path(name)
|
|
@@ -3676,7 +3801,7 @@ def _run_session_daemon(config_json: str):
|
|
|
3676
3801
|
env = {**os.environ, **env_vars}
|
|
3677
3802
|
params = StdioServerParameters(command=parts[0], args=parts[1:], env=env)
|
|
3678
3803
|
async with stdio_client(params) as (read, write):
|
|
3679
|
-
async with ClientSession(read, write) as session:
|
|
3804
|
+
async with ClientSession(read, write, list_roots_callback=_roots_callback(roots)) as session:
|
|
3680
3805
|
await _run_with_session(session)
|
|
3681
3806
|
else:
|
|
3682
3807
|
headers = dict(auth_headers) if auth_headers else None
|
|
@@ -3686,14 +3811,14 @@ def _run_session_daemon(config_json: str):
|
|
|
3686
3811
|
read,
|
|
3687
3812
|
write,
|
|
3688
3813
|
):
|
|
3689
|
-
async with ClientSession(read, write) as session:
|
|
3814
|
+
async with ClientSession(read, write, list_roots_callback=_roots_callback(roots)) as session:
|
|
3690
3815
|
await _run_with_session(session)
|
|
3691
3816
|
|
|
3692
3817
|
async def _via_sse():
|
|
3693
3818
|
from mcp.client.sse import sse_client
|
|
3694
3819
|
|
|
3695
3820
|
async with sse_client(source, headers=headers) as (read, write):
|
|
3696
|
-
async with ClientSession(read, write) as session:
|
|
3821
|
+
async with ClientSession(read, write, list_roots_callback=_roots_callback(roots)) as session:
|
|
3697
3822
|
await _run_with_session(session)
|
|
3698
3823
|
|
|
3699
3824
|
if transport == "sse":
|
|
@@ -3821,6 +3946,7 @@ def handle_mcp(
|
|
|
3821
3946
|
prompt_action: str | None = None,
|
|
3822
3947
|
prompt_name: str | None = None,
|
|
3823
3948
|
prompt_arguments: dict | None = None,
|
|
3949
|
+
complete_spec: str | None = None,
|
|
3824
3950
|
search_pattern: str | None = None,
|
|
3825
3951
|
bake_config: BakeConfig | None = None,
|
|
3826
3952
|
head: int | None = None,
|
|
@@ -3842,14 +3968,15 @@ def handle_mcp(
|
|
|
3842
3968
|
key = cache_key_override or cache_key_for(config_for_cache)
|
|
3843
3969
|
src_hash = _source_hash_for(source)
|
|
3844
3970
|
|
|
3845
|
-
# Resource/prompt operations skip the tool flow entirely
|
|
3846
|
-
if resource_action or prompt_action:
|
|
3971
|
+
# Resource/prompt/completion operations skip the tool flow entirely
|
|
3972
|
+
if resource_action or prompt_action or complete_spec:
|
|
3847
3973
|
extra = dict(
|
|
3848
3974
|
resource_action=resource_action,
|
|
3849
3975
|
resource_uri=resource_uri,
|
|
3850
3976
|
prompt_action=prompt_action,
|
|
3851
3977
|
prompt_name=prompt_name,
|
|
3852
3978
|
prompt_arguments=prompt_arguments,
|
|
3979
|
+
complete_spec=complete_spec,
|
|
3853
3980
|
head=head,
|
|
3854
3981
|
json_output=json_output,
|
|
3855
3982
|
)
|
|
@@ -3977,7 +4104,7 @@ def _fetch_mcp_tools(
|
|
|
3977
4104
|
env = {**os.environ, **env_vars}
|
|
3978
4105
|
params = StdioServerParameters(command=parts[0], args=parts[1:], env=env)
|
|
3979
4106
|
async with stdio_client(params) as (read, write):
|
|
3980
|
-
async with ClientSession(read, write) as session:
|
|
4107
|
+
async with ClientSession(read, write, list_roots_callback=_roots_callback()) as session:
|
|
3981
4108
|
await session.initialize()
|
|
3982
4109
|
await _extract_tools(session)
|
|
3983
4110
|
else:
|
|
@@ -3989,7 +4116,7 @@ def _fetch_mcp_tools(
|
|
|
3989
4116
|
async with _streamable_streams(
|
|
3990
4117
|
source, headers=headers, auth=oauth_provider
|
|
3991
4118
|
) as (read, write):
|
|
3992
|
-
async with ClientSession(read, write) as session:
|
|
4119
|
+
async with ClientSession(read, write, list_roots_callback=_roots_callback()) as session:
|
|
3993
4120
|
await session.initialize()
|
|
3994
4121
|
await _extract_tools(session)
|
|
3995
4122
|
|
|
@@ -4000,7 +4127,7 @@ def _fetch_mcp_tools(
|
|
|
4000
4127
|
read,
|
|
4001
4128
|
write,
|
|
4002
4129
|
):
|
|
4003
|
-
async with ClientSession(read, write) as session:
|
|
4130
|
+
async with ClientSession(read, write, list_roots_callback=_roots_callback()) as session:
|
|
4004
4131
|
await session.initialize()
|
|
4005
4132
|
await _extract_tools(session)
|
|
4006
4133
|
|
|
@@ -4199,6 +4326,26 @@ def _build_main_parser() -> argparse.ArgumentParser:
|
|
|
4199
4326
|
default=[],
|
|
4200
4327
|
help="Environment variable KEY=VALUE for MCP stdio (repeatable)",
|
|
4201
4328
|
)
|
|
4329
|
+
pre.add_argument(
|
|
4330
|
+
"--root",
|
|
4331
|
+
action="append",
|
|
4332
|
+
default=[],
|
|
4333
|
+
metavar="PATH|FILE_URI",
|
|
4334
|
+
help=(
|
|
4335
|
+
"Expose a filesystem path or file:// URI to the server (repeatable). "
|
|
4336
|
+
"Workspace-scoped servers request these via roots/list."
|
|
4337
|
+
),
|
|
4338
|
+
)
|
|
4339
|
+
pre.add_argument(
|
|
4340
|
+
"--complete",
|
|
4341
|
+
default=None,
|
|
4342
|
+
metavar="REF:ARG=PREFIX",
|
|
4343
|
+
help=(
|
|
4344
|
+
"Ask the server to complete an argument value, e.g. "
|
|
4345
|
+
"--complete 'my-prompt:city=San'. REF is a prompt name, or a "
|
|
4346
|
+
"resource URI template when it contains '://'."
|
|
4347
|
+
),
|
|
4348
|
+
)
|
|
4202
4349
|
pre.add_argument(
|
|
4203
4350
|
"--oauth",
|
|
4204
4351
|
action="store_true",
|
|
@@ -4426,6 +4573,7 @@ def _handle_session_operations(
|
|
|
4426
4573
|
auth_headers,
|
|
4427
4574
|
env_vars,
|
|
4428
4575
|
transport=pre_args.transport,
|
|
4576
|
+
roots=_ROOTS,
|
|
4429
4577
|
)
|
|
4430
4578
|
return True
|
|
4431
4579
|
|
|
@@ -4438,6 +4586,13 @@ def _handle_session_operations(
|
|
|
4438
4586
|
pretty=pre_args.pretty, raw=pre_args.raw, toon=pre_args.toon,
|
|
4439
4587
|
json_output=pre_args.json_output,
|
|
4440
4588
|
)
|
|
4589
|
+
if pre_args.complete:
|
|
4590
|
+
result = _session_request(
|
|
4591
|
+
sess_name, "complete", {"spec": pre_args.complete}
|
|
4592
|
+
)
|
|
4593
|
+
output_result(result, head=pre_args.head, **_sess_out)
|
|
4594
|
+
return True
|
|
4595
|
+
|
|
4441
4596
|
|
|
4442
4597
|
if pre_args.list_resources:
|
|
4443
4598
|
result = _session_request(sess_name, "list_resources")
|
|
@@ -4693,6 +4848,12 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
4693
4848
|
pre_args, leftover = pre.parse_known_args(global_argv)
|
|
4694
4849
|
remaining = leftover + tool_argv
|
|
4695
4850
|
|
|
4851
|
+
try:
|
|
4852
|
+
_ROOTS[:] = [_normalize_root(raw) for raw in pre_args.root]
|
|
4853
|
+
except ValueError as exc:
|
|
4854
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
4855
|
+
sys.exit(1)
|
|
4856
|
+
|
|
4696
4857
|
# --search implies --list
|
|
4697
4858
|
search_pattern = pre_args.search_pattern
|
|
4698
4859
|
if search_pattern:
|
|
@@ -4764,6 +4925,7 @@ def _main_impl(argv: list[str], bake_config: BakeConfig | None = None):
|
|
|
4764
4925
|
prompt_action=prompt_action,
|
|
4765
4926
|
prompt_name=prompt_name,
|
|
4766
4927
|
prompt_arguments=prompt_arguments,
|
|
4928
|
+
complete_spec=pre_args.complete,
|
|
4767
4929
|
search_pattern=search_pattern,
|
|
4768
4930
|
bake_config=bake_config,
|
|
4769
4931
|
head=pre_args.head,
|
|
File without changes
|
|
File without changes
|