sandrpod-cli 0.2.2__tar.gz → 0.2.4__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.
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/PKG-INFO +1 -1
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli/client.py +17 -3
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli/main.py +38 -7
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli.egg-info/PKG-INFO +1 -1
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/setup.py +1 -1
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli/README.md +0 -0
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli/__init__.py +0 -0
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli/__main__.py +0 -0
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli/py.typed +0 -0
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli.egg-info/SOURCES.txt +0 -0
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli.egg-info/dependency_links.txt +0 -0
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli.egg-info/entry_points.txt +0 -0
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli.egg-info/requires.txt +0 -0
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/sandrpod_cli.egg-info/top_level.txt +0 -0
- {sandrpod_cli-0.2.2 → sandrpod_cli-0.2.4}/setup.cfg +0 -0
|
@@ -15,13 +15,21 @@ class CLIClient:
|
|
|
15
15
|
api_url: str = "http://localhost:8080",
|
|
16
16
|
timeout: int = 30,
|
|
17
17
|
token: Optional[str] = None,
|
|
18
|
+
mcp_token: Optional[str] = None,
|
|
18
19
|
):
|
|
19
20
|
self.api_url = api_url.rstrip("/")
|
|
20
21
|
self.timeout = timeout
|
|
22
|
+
self.mcp_token = mcp_token
|
|
21
23
|
self.session = requests.Session()
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
+
# Two-tier auth (see docs MCP_AUTH_HEADER_CONFLICT_FIX): the platform token
|
|
25
|
+
# goes in X-Sandrpod-Token, the server's preferred header. Authorization:
|
|
26
|
+
# Bearer is kept as a legacy fallback for older servers — but crucially it
|
|
27
|
+
# stays free to carry the *personal* mcp_token through to the sandbox's
|
|
28
|
+
# agent bridge on /mcp calls (the server passes Authorization through the
|
|
29
|
+
# tunnel untouched). Content-Type is set per-request so multipart uploads
|
|
30
|
+
# can override it.
|
|
24
31
|
if token:
|
|
32
|
+
self.session.headers["X-Sandrpod-Token"] = token
|
|
25
33
|
self.session.headers["Authorization"] = f"Bearer {token}"
|
|
26
34
|
|
|
27
35
|
def _request(self, method: str, path: str, timeout: int = None, **kwargs) -> requests.Response:
|
|
@@ -321,7 +329,13 @@ class CLIClient:
|
|
|
321
329
|
"""沙箱原生 MCP bridge 的实时清单 (聚合 server、工具数、配置文件路径)"""
|
|
322
330
|
# /mcp (not /toolbox/mcp): the streaming bridge proxy, which flushes SSE —
|
|
323
331
|
# the path a real MCP client uses. Works for poder + agent sandboxes.
|
|
324
|
-
|
|
332
|
+
# When the agent guards its bridge with a personal mcp_token, carry it in
|
|
333
|
+
# Authorization: Bearer — the server auths via X-Sandrpod-Token and passes
|
|
334
|
+
# this through to the agent's mcpTokenMiddleware untouched.
|
|
335
|
+
headers = {}
|
|
336
|
+
if self.mcp_token:
|
|
337
|
+
headers["Authorization"] = f"Bearer {self.mcp_token}"
|
|
338
|
+
resp = self._request("GET", f"/api/v1/sandboxes/{name}/mcp/manifest", headers=headers)
|
|
325
339
|
return resp.json()
|
|
326
340
|
|
|
327
341
|
def mcp_url(self, name: str) -> str:
|
|
@@ -61,6 +61,20 @@ def get_configured_token():
|
|
|
61
61
|
return config.get("api_token")
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def get_configured_mcp_token():
|
|
65
|
+
"""获取个人 MCP token(agent bridge 的共享密钥),优先级:环境变量 > 配置文件。
|
|
66
|
+
|
|
67
|
+
仅用于 /mcp 相关调用(mcp tools/url),当员工机 agent 以 --mcp-token 启动、
|
|
68
|
+
对 /mcp 面加了共享密钥时需要。它作为 Authorization: Bearer 透传给 agent 校验,
|
|
69
|
+
与平台 token(X-Sandrpod-Token)互不冲突。
|
|
70
|
+
"""
|
|
71
|
+
env_token = os.environ.get("SANDRPOD_MCP_TOKEN")
|
|
72
|
+
if env_token:
|
|
73
|
+
return env_token
|
|
74
|
+
config = load_config()
|
|
75
|
+
return config.get("mcp_token")
|
|
76
|
+
|
|
77
|
+
|
|
64
78
|
def save_config_api_url(url):
|
|
65
79
|
"""保存 API URL 到配置文件"""
|
|
66
80
|
config = load_config()
|
|
@@ -78,15 +92,18 @@ def save_config_token(token):
|
|
|
78
92
|
@click.group()
|
|
79
93
|
@click.option("--api-url", default=None, help="API URL (overrides config)")
|
|
80
94
|
@click.option("--timeout", default=30, help="Request timeout")
|
|
95
|
+
@click.option("--mcp-token", default=None, help="个人 MCP token(agent bridge 共享密钥;也可用 SANDRPOD_MCP_TOKEN)")
|
|
81
96
|
@click.pass_context
|
|
82
|
-
def cli(ctx, api_url, timeout):
|
|
97
|
+
def cli(ctx, api_url, timeout, mcp_token):
|
|
83
98
|
"""SandrPod CLI - Sandbox Management"""
|
|
84
99
|
ctx.ensure_object(dict)
|
|
85
100
|
# 如果没有传入 --api-url,使用配置的 URL
|
|
86
101
|
if api_url is None:
|
|
87
102
|
api_url = get_configured_api_url()
|
|
88
103
|
token = get_configured_token()
|
|
89
|
-
|
|
104
|
+
if mcp_token is None:
|
|
105
|
+
mcp_token = get_configured_mcp_token()
|
|
106
|
+
ctx.obj["client"] = CLIClient(api_url=api_url, timeout=timeout, token=token, mcp_token=mcp_token)
|
|
90
107
|
|
|
91
108
|
|
|
92
109
|
# ========== Config Commands ==========
|
|
@@ -945,7 +962,13 @@ def _kv(pairs):
|
|
|
945
962
|
|
|
946
963
|
|
|
947
964
|
def _mcp_config_path(client, name, override):
|
|
948
|
-
"""mcp.json 路径: --config-path > manifest.config_path >
|
|
965
|
+
"""mcp.json 路径: --config-path > manifest.config_path > 按 substrate 猜默认。
|
|
966
|
+
|
|
967
|
+
优先用 bridge 在 /mcp/manifest 里自报的绝对路径(精确;需较新的
|
|
968
|
+
agent/toolbox)。旧 bridge 不报 config_path 时按 substrate 回退:
|
|
969
|
+
poder(容器)沙箱用 /workspace/.sandrpod/mcp.json,direct agent(本机)
|
|
970
|
+
用 ~/.sandrpod/mcp.json(agent 的 DefaultConfigPath)。
|
|
971
|
+
"""
|
|
949
972
|
if override:
|
|
950
973
|
return override
|
|
951
974
|
try:
|
|
@@ -954,6 +977,12 @@ def _mcp_config_path(client, name, override):
|
|
|
954
977
|
return manifest["config_path"]
|
|
955
978
|
except Exception:
|
|
956
979
|
pass
|
|
980
|
+
try:
|
|
981
|
+
sb = client.get_sandbox(name)
|
|
982
|
+
if str(sb.get("proxy_url", "")).startswith("direct://"):
|
|
983
|
+
return os.path.expanduser("~/.sandrpod/mcp.json")
|
|
984
|
+
except Exception:
|
|
985
|
+
pass
|
|
957
986
|
return DEFAULT_MCP_CONFIG
|
|
958
987
|
|
|
959
988
|
|
|
@@ -1082,12 +1111,14 @@ def mcp_rm(ctx, name, server, config_path):
|
|
|
1082
1111
|
@click.argument("name")
|
|
1083
1112
|
@click.pass_context
|
|
1084
1113
|
def mcp_url(ctx, name):
|
|
1085
|
-
"""打印沙箱原生 MCP 端点 URL
|
|
1114
|
+
"""打印沙箱原生 MCP 端点 URL + 连接所需的两层鉴权头"""
|
|
1086
1115
|
client = ctx.parent.parent.obj["client"]
|
|
1087
1116
|
click.echo(client.mcp_url(name))
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1117
|
+
# 两层鉴权(见 MCP_AUTH_HEADER_CONFLICT_FIX):平台 token 走 X-Sandrpod-Token,
|
|
1118
|
+
# 个人 MCP token 走 Authorization: Bearer(server 原样透传给 agent 校验)。
|
|
1119
|
+
click.echo("# 连接头(两层鉴权):", err=True)
|
|
1120
|
+
click.echo("# X-Sandrpod-Token: <平台 token>", err=True)
|
|
1121
|
+
click.echo("# Authorization: Bearer <个人 mcp_token> (仅当 agent 以 -mcp-token 启动时需要)", err=True)
|
|
1091
1122
|
|
|
1092
1123
|
|
|
1093
1124
|
@mcp_group.command(name="tools")
|
|
@@ -10,7 +10,7 @@ long_description = _readme.read_text(encoding="utf-8") if _readme.exists() else
|
|
|
10
10
|
|
|
11
11
|
setup(
|
|
12
12
|
name="sandrpod-cli",
|
|
13
|
-
version="0.2.
|
|
13
|
+
version="0.2.4",
|
|
14
14
|
description=(
|
|
15
15
|
"Command-line client for SandrPod — self-hosted, multi-cloud sandbox "
|
|
16
16
|
"infrastructure for AI agents"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|