Skip to content

Commit cc0c57a

Browse files
author
yanan.zhangyn
committed
fix: restore MCP credentials for Studio debug runs
1 parent 49f4b7d commit cc0c57a

2 files changed

Lines changed: 209 additions & 21 deletions

File tree

tests/cli/test_generated_agent_backend_codegen_extended.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,107 @@ async def fail_mcp_discovery(draft):
12171217
assert response.json()["detail"] == original_detail
12181218

12191219

1220+
def test_generated_debug_recovers_unchanged_published_mcp_credential_before_discovery(
1221+
monkeypatch: pytest.MonkeyPatch,
1222+
tmp_path: Path,
1223+
) -> None:
1224+
from agentkit.sdk.runtime.client import AgentkitRuntimeClient
1225+
1226+
credential_reference = "MCP_LEGACY_AGENT_JVMDIAG_AUTH_TOKEN"
1227+
credential_value = "server-retained-debug-secret"
1228+
published_draft = {
1229+
"name": "legacy_agent",
1230+
"description": "Existing Agent",
1231+
"instruction": "Use the diagnostic MCP.",
1232+
"mcpTools": [
1233+
{
1234+
"name": "jvmdiag",
1235+
"transport": "http",
1236+
"url": "https://8.8.8.8/mcp",
1237+
"authTokenEnv": credential_reference,
1238+
}
1239+
],
1240+
}
1241+
runtime = SimpleNamespace(
1242+
runtime_id="runtime-debug-mcp",
1243+
runtime_name="legacy-agent-runtime",
1244+
current_version_number=3,
1245+
tags=[],
1246+
envs=[SimpleNamespace(key=credential_reference, value=credential_value)],
1247+
network_configurations=[
1248+
SimpleNamespace(
1249+
endpoint="https://runtime.example.com",
1250+
network_type="public",
1251+
)
1252+
],
1253+
authorizer_configuration=SimpleNamespace(
1254+
key_auth=SimpleNamespace(api_key="runtime-api-key"),
1255+
custom_jwt_authorizer=None,
1256+
),
1257+
)
1258+
1259+
monkeypatch.setattr(
1260+
AgentkitRuntimeClient,
1261+
"get_runtime",
1262+
lambda _self, _request: runtime,
1263+
)
1264+
1265+
captured_discovery_env: dict[str, str] = {}
1266+
1267+
async def capture_mcp_discovery(draft, env_values=None):
1268+
captured_discovery_env.update(env_values or {})
1269+
return draft
1270+
1271+
monkeypatch.setattr(
1272+
"veadk.cli.generated_agent_mcp.resolve_debug_mcp_endpoints",
1273+
capture_mcp_discovery,
1274+
)
1275+
1276+
class RuntimeDebugClient(_FakeAsyncClient):
1277+
async def request(self, _method: str, url: str, **_kwargs: Any):
1278+
if url.endswith("/list-apps"):
1279+
return _FakeResponse(json_data=["legacy_agent"])
1280+
if url.endswith("/web/agent-info/legacy_agent"):
1281+
return _FakeResponse(
1282+
json_data={
1283+
"name": "legacy_agent",
1284+
"description": "Existing Agent",
1285+
"draft": published_draft,
1286+
}
1287+
)
1288+
raise AssertionError(f"unexpected Runtime request path: {url}")
1289+
1290+
monkeypatch.setenv("_FAAS_FUNC_ID", "function-test")
1291+
app = _generated_debug_app(monkeypatch, tmp_path)
1292+
_FakeProcess.created.clear()
1293+
_FakeAsyncClient.listed_apps = ["legacy_agent"]
1294+
monkeypatch.setattr("subprocess.Popen", _FakeProcess)
1295+
monkeypatch.setattr("httpx.AsyncClient", RuntimeDebugClient)
1296+
real_socket = socket.socket
1297+
monkeypatch.setattr(
1298+
"socket.socket",
1299+
lambda *args, **kwargs: (
1300+
real_socket(*args, **kwargs)
1301+
if len(args) >= 4 or "fileno" in kwargs
1302+
else _FakeSocket(*args, **kwargs)
1303+
),
1304+
)
1305+
1306+
with TestClient(app) as client:
1307+
response = client.post(
1308+
"/web/generated-agent-test-runs",
1309+
json={
1310+
"draft": published_draft,
1311+
"runtimeId": runtime.runtime_id,
1312+
"runtimeRegion": "cn-shanghai",
1313+
},
1314+
)
1315+
1316+
assert response.status_code == 200, response.text
1317+
assert captured_discovery_env[credential_reference] == credential_value
1318+
assert credential_value not in response.text
1319+
1320+
12201321
def test_debug_text_redacts_environment_and_inline_markers(
12211322
monkeypatch: pytest.MonkeyPatch,
12221323
) -> None:

veadk/cli/cli_frontend.py

Lines changed: 108 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4451,6 +4451,7 @@ async def _agentkit_proxy(request: Request, path: str):
44514451
debug_runtime_env_from_draft,
44524452
generate_project_from_draft,
44534453
normalize_and_validate_draft,
4454+
prepare_mcp_auth,
44544455
)
44554456
from veadk.cli.generated_agent_security import (
44564457
DebugPolicyError,
@@ -5077,10 +5078,15 @@ async def _generate_project_and_draft_from_request(
50775078
*,
50785079
debug: bool,
50795080
owner_id: str = "local",
5081+
validated_test_request: GeneratedAgentTestRunRequest | None = None,
5082+
debug_mcp_env_values: Mapping[str, str] | None = None,
50805083
) -> tuple[GeneratedProject, AgentDraft]:
50815084
try:
50825085
if debug:
5083-
req = GeneratedAgentTestRunRequest.model_validate(data)
5086+
req = (
5087+
validated_test_request
5088+
or GeneratedAgentTestRunRequest.model_validate(data)
5089+
)
50845090
else:
50855091
req = GeneratedAgentProjectRequest.model_validate(data)
50865092
draft = normalize_and_validate_draft(req.draft)
@@ -5099,7 +5105,18 @@ async def _generate_project_and_draft_from_request(
50995105
else _cloud_studio_private_networks
51005106
),
51015107
)
5102-
draft = await resolve_debug_mcp_endpoints(draft)
5108+
if debug_mcp_env_values:
5109+
draft = prepare_mcp_auth(draft)
5110+
mcp_env_values = dict(draft.deployment.envValues)
5111+
for key, value in debug_mcp_env_values.items():
5112+
if value and not mcp_env_values.get(key):
5113+
mcp_env_values[key] = value
5114+
draft = await resolve_debug_mcp_endpoints(
5115+
draft,
5116+
mcp_env_values,
5117+
)
5118+
else:
5119+
draft = await resolve_debug_mcp_endpoints(draft)
51035120
else:
51045121
validate_project_policy(draft)
51055122
project = generate_project_from_draft(draft)
@@ -5658,10 +5675,99 @@ async def _create_generated_agent_test_run(request: Request):
56585675
temp_dir = ""
56595676
proc = None
56605677
try:
5678+
try:
5679+
test_request = GeneratedAgentTestRunRequest.model_validate(data)
5680+
except ValidationError as error:
5681+
raise HTTPException(status_code=422, detail=error.errors()) from error
5682+
5683+
runtime_envs: dict[str, str] = {}
5684+
debug_mcp_env_values: dict[str, str] = {}
5685+
runtime_id = test_request.runtimeId.strip()
5686+
runtime_region = _coerce_cloud_region(test_request.runtimeRegion)
5687+
if runtime_id:
5688+
edited_draft = test_request.draft.model_dump(
5689+
mode="json",
5690+
by_alias=True,
5691+
exclude_none=True,
5692+
)
5693+
requested_references = mcp_auth_environment_keys(edited_draft)
5694+
requested_env_values = test_request.draft.deployment.envValues
5695+
stored_references = tuple(
5696+
reference
5697+
for reference in requested_references
5698+
if not requested_env_values.get(reference)
5699+
)
5700+
if stored_references:
5701+
(
5702+
update_capability,
5703+
runtime,
5704+
) = await _runtime_update_capability_details(
5705+
request,
5706+
runtime_id=runtime_id,
5707+
region=runtime_region,
5708+
)
5709+
else:
5710+
update_capability = {}
5711+
runtime = await asyncio.to_thread(
5712+
_authorized_runtime,
5713+
request,
5714+
runtime_id,
5715+
runtime_region,
5716+
coded_access_error=True,
5717+
)
5718+
runtime_envs = {
5719+
str(item.key): str(item.value or "")
5720+
for item in (getattr(runtime, "envs", None) or [])
5721+
if getattr(item, "key", None)
5722+
and not _is_debug_protected_model_env(str(item.key))
5723+
}
5724+
published_agent = update_capability.get("agent")
5725+
published_draft = (
5726+
published_agent.get("draft")
5727+
if isinstance(published_agent, Mapping)
5728+
else None
5729+
)
5730+
if stored_references and isinstance(published_draft, Mapping):
5731+
published_environment = _legacy_runtime_environment(runtime)
5732+
published_references = mcp_auth_environment_keys(published_draft)
5733+
published_reference_values = {
5734+
reference: published_environment[reference]
5735+
for reference in published_references
5736+
if published_environment.get(reference)
5737+
}
5738+
if set(published_references).difference(published_reference_values):
5739+
try:
5740+
recovery, recovered_values = _legacy_mcp_state(
5741+
runtime,
5742+
runtime_region,
5743+
)
5744+
published_reference_values.update(
5745+
mcp_secret_values_for_draft_references(
5746+
draft=published_draft,
5747+
recovery=recovery,
5748+
recovered_values=recovered_values,
5749+
)
5750+
)
5751+
except LegacyRecoveryError as error:
5752+
logger.info(
5753+
"debug MCP credential recovery unavailable "
5754+
"runtime_id=%s region=%s code=%s",
5755+
runtime_id,
5756+
runtime_region,
5757+
error.code,
5758+
)
5759+
debug_mcp_env_values = retained_mcp_secret_values(
5760+
published_draft=published_draft,
5761+
edited_draft=edited_draft,
5762+
published_reference_values=published_reference_values,
5763+
)
5764+
56615765
project, draft = await _generate_project_and_draft_from_request(
56625766
data,
56635767
debug=True,
56645768
owner_id=owner_id or "local",
5769+
validated_test_request=test_request,
5770+
debug_mcp_env_values=debug_mcp_env_values,
56655771
)
56665772
sidecar_env: dict[str, str] = {}
56675773
sidecar_plan: dict[str, Any] | None = None
@@ -5698,25 +5804,6 @@ async def _create_generated_agent_test_run(request: Request):
56985804
status_code=409,
56995805
detail="Harness Sidecar 配置已更新,请重新解析后再启动调试。",
57005806
)
5701-
runtime_envs: dict[str, str] = {}
5702-
runtime_id = str(data.get("runtimeId") or "").strip()
5703-
if runtime_id:
5704-
runtime_region = (
5705-
str(data.get("runtimeRegion") or "cn-beijing").strip()
5706-
or "cn-beijing"
5707-
)
5708-
runtime = _authorized_runtime(
5709-
request,
5710-
runtime_id,
5711-
runtime_region,
5712-
coded_access_error=True,
5713-
)
5714-
runtime_envs = {
5715-
str(item.key): str(item.value or "")
5716-
for item in (getattr(runtime, "envs", None) or [])
5717-
if getattr(item, "key", None)
5718-
and not _is_debug_protected_model_env(str(item.key))
5719-
}
57205807
temp_dir = tempfile.mkdtemp(prefix="veadk_generated_agent_test_")
57215808
app_name = _write_generated_project(project, temp_dir)
57225809
staged_environment_skills = ""

0 commit comments

Comments
 (0)