Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/harness-sidecar-release-gate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ on:
- 'tests/cli/test_managed_sidecar_source.py'
- 'tests/cli/test_legacy_runtime_recovery.py'
- 'tests/cli/test_generated_agent_backend_codegen_extended.py'
- 'tests/cli/test_generated_agent_mcp.py'
- 'tests/cli/test_studio_rbac.py'
- 'tests/extensions/harness/**'
- 'tests/integrations/agentkit/test_app.py'
- 'veadk/cli/generated_agent_codegen.py'
- 'veadk/cli/generated_agent_mcp.py'
- 'veadk/cli/cli_frontend.py'
- 'veadk/cli/managed_sidecar_source.py'
- 'veadk/cli/legacy_runtime_recovery.py'
Expand All @@ -28,10 +30,12 @@ on:
- 'tests/cli/test_managed_sidecar_source.py'
- 'tests/cli/test_legacy_runtime_recovery.py'
- 'tests/cli/test_generated_agent_backend_codegen_extended.py'
- 'tests/cli/test_generated_agent_mcp.py'
- 'tests/cli/test_studio_rbac.py'
- 'tests/extensions/harness/**'
- 'tests/integrations/agentkit/test_app.py'
- 'veadk/cli/generated_agent_codegen.py'
- 'veadk/cli/generated_agent_mcp.py'
- 'veadk/cli/cli_frontend.py'
- 'veadk/cli/managed_sidecar_source.py'
- 'veadk/cli/legacy_runtime_recovery.py'
Expand Down Expand Up @@ -78,14 +82,17 @@ jobs:
--include='*/veadk/extensions/harness/sidecar.py' \
--fail-under=91 --show-missing

- name: Verify customer Studio Agent-update regressions
- name: Verify Studio six-case lifecycle and credential regressions
run: |
python -m pytest -q \
tests/cli/test_legacy_runtime_recovery.py::test_changed_unnamed_mcp_url_reuses_same_published_tool_slot \
tests/cli/test_legacy_runtime_recovery.py::test_changed_unnamed_mcp_url_rejects_moved_credential_slot
tests/cli/test_legacy_runtime_recovery.py -k 'six_case_ or changed_unnamed_mcp_url'
python -m pytest -q \
tests/cli/test_studio_rbac.py::test_sidecar_update_resolves_or_explicitly_reuses_stored_mcp_credentials \
-k changed-unnamed-explicit-reuse
tests/cli/test_generated_agent_mcp.py \
tests/cli/test_generated_agent_backend_codegen_extended.py::test_generated_debug_applies_published_mcp_credential_contract_before_discovery \
tests/cli/test_studio_rbac.py::test_new_deployment_only_updates_non_default_instance_range \
tests/cli/test_studio_rbac.py::test_sidecar_deployment_uses_agentkit_cli_structured_release \
tests/cli/test_studio_rbac.py::test_application_owned_mcp_update_routes_cover_reuse_and_additions \
tests/cli/test_studio_rbac.py::test_sidecar_update_resolves_or_explicitly_reuses_stored_mcp_credentials

- name: Install frontend dependencies
working-directory: frontend
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/adk/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4942,7 +4942,16 @@ export async function generateAgentDraftFromRequirement(

export async function createGeneratedAgentTestRun(
draft: AgentDraft,
runtime?: { runtimeId: string; region: string },
runtime?: {
runtimeId: string;
region: string;
mcpCredentialReuses?: Array<{
agentName: string;
name: string;
url: string;
sourceAuthTokenEnv: string;
}>;
},
): Promise<GeneratedAgentTestRun> {
const res = await apiFetch("/web/generated-agent-test-runs", {
method: "POST",
Expand All @@ -4951,6 +4960,7 @@ export async function createGeneratedAgentTestRun(
draft,
runtimeId: runtime?.runtimeId,
runtimeRegion: runtime?.region,
mcpCredentialReuses: runtime?.mcpCredentialReuses,
}),
});
if (!res.ok) {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/create/CustomCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4503,6 +4503,7 @@ export function CustomCreate({
? {
runtimeId: deploymentTarget.runtimeId,
region: deploymentTarget.region,
mcpCredentialReuses: mcpCredentialReuseValues(variantDraft),
}
: undefined,
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/create/mcpAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export function mcpUrlNeedsPathWarning(value: string): boolean {
if (!value.trim()) return false;
try {
const path = new URL(value).pathname.replace(/\/+$/, "");
return !path.endsWith("/mcp");
return path === "";
} catch {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/i18n/resources/en-US/create.json
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@
"removeTool": "Remove MCP tool",
"namePlaceholder": "Name (optional)",
"urlPlaceholder": "MCP service URL",
"pathWarning": "This address does not end with /mcp. Confirm that it is the complete MCP service URL.",
"pathWarning": "This is a gateway root address. Use it only when the root path is the MCP endpoint; otherwise include the complete service path.",
"configuredPlaceholder": "Authentication is stored securely",
"tokenPlaceholder": "Bearer token (optional)",
"changedUrlWarning": "The MCP address changed. Choose how to handle the stored authentication.",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/i18n/resources/zh-CN/create.json
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@
"removeTool": "删除 MCP 工具",
"namePlaceholder": "名称(可选)",
"urlPlaceholder": "MCP 服务地址",
"pathWarning": "此地址未以 /mcp 结尾,请确认它是完整的 MCP 服务地址。",
"pathWarning": "当前填写的是网关根地址。仅当根路径就是 MCP Endpoint 时可直接使用;否则请补充完整服务路径。",
"configuredPlaceholder": "已安全保存认证信息",
"tokenPlaceholder": "Bearer Token(可选)",
"changedUrlWarning": "MCP 地址已变化,请确认如何处理已保存的认证信息。",
Expand Down
4 changes: 4 additions & 0 deletions frontend/tests/deploymentConfigUi.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ test("offers code execution with its sandbox configuration", () => {
customCreateSource,
/createGeneratedAgentTestRun\([\s\S]*?debugRuntimeDraft\(variantDraft, transientModelSecretValues\)[\s\S]*?runtimeId: deploymentTarget\.runtimeId[\s\S]*?region: deploymentTarget\.region/,
);
assert.match(
customCreateSource,
/createGeneratedAgentTestRun\([\s\S]*?debugRuntimeDraft\(variantDraft, transientModelSecretValues\)[\s\S]*?mcpCredentialReuseValues\(variantDraft\)/,
);
assert.match(
customCreateSource,
/if \(isImeCompositionEvent\(e\.nativeEvent\)\) return;[\s\S]*?e\.key === "Enter"/,
Expand Down
12 changes: 7 additions & 5 deletions frontend/tests/mcpAuth.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,11 @@ test("submits an explicit reuse decision for an unnamed MCP without inventing a
]);
});

test("describes the MCP suffix check instead of claiming every address has no path", () => {
test("describes the MCP root-path check without requiring a /mcp suffix", () => {
for (const locale of ["zh-CN", "en-US"]) {
const warning = createMessages[locale].traditional.mcp.pathWarning;
assert.match(warning, /\/mcp/);
assert.doesNotMatch(warning, /没有路径|has no path/i);
assert.match(warning, /根地址|root address/i);
assert.doesNotMatch(warning, /必须|must|\/mcp/i);
}
});

Expand Down Expand Up @@ -607,13 +607,15 @@ test("resolves new Sidecar MCP credentials from prior tool inputs only", () => {
);
});

test("warns about non-standard MCP paths without rewriting them", () => {
test("warns only for a gateway root and accepts custom MCP endpoint paths", () => {
assert.equal(mcpUrlNeedsPathWarning("https://example.com/mcp"), false);
assert.equal(
mcpUrlNeedsPathWarning("https://example.com/gateway/mcp/?region=cn"),
false,
);
assert.equal(mcpUrlNeedsPathWarning("https://example.com/custom-path"), true);
assert.equal(mcpUrlNeedsPathWarning("https://example.com/mysqldiag"), false);
assert.equal(mcpUrlNeedsPathWarning("https://example.com/athena-mcp"), false);
assert.equal(mcpUrlNeedsPathWarning("https://example.com/"), true);
});

test("YAML export preserves MCP tokens as runtime environment values", () => {
Expand Down
148 changes: 129 additions & 19 deletions tests/cli/test_generated_agent_backend_codegen_extended.py
Original file line number Diff line number Diff line change
Expand Up @@ -1221,21 +1221,103 @@ async def fail_mcp_discovery(draft):
(
"credential_storage",
"tool_name",
"published_url",
"edited_url",
"expected_status",
"expect_credential",
"explicit_reuse",
"has_published_draft",
),
[
("reference-env", "jvmdiag", "https://8.8.8.8/mcp", 200, True),
("reference-env", "", "https://8.8.8.8/mcp", 200, True),
("servers-json", "jvmdiag", "https://8.8.8.8/mcp", 200, True),
("servers-json", "", "https://8.8.8.8/mcp", 200, True),
(
"reference-env",
"jvmdiag",
"https://8.8.8.8/mcp",
"https://8.8.8.8/mcp",
200,
True,
False,
True,
),
(
"reference-env",
"",
"https://8.8.8.8/mcp",
"https://8.8.8.8/mcp",
200,
True,
False,
True,
),
(
"servers-json",
"jvmdiag",
"https://8.8.8.8/mcp",
"https://8.8.8.8/mcp",
200,
True,
False,
True,
),
(
"servers-json",
"",
"https://8.8.8.8/mcp",
"https://8.8.8.8/mcp",
200,
True,
False,
True,
),
(
"servers-json",
"jvmdiag",
"https://8.8.8.8/mcp",
"https://8.8.8.8/changed-mcp",
422,
409,
False,
False,
True,
),
(
"missing",
"",
"https://8.8.8.8/mysqldiag",
"https://8.8.8.8/mysqldiag",
409,
False,
False,
True,
),
(
"servers-json",
"",
"https://8.8.8.8/mcp",
"https://8.8.8.8/changed-mcp",
200,
True,
True,
True,
),
(
"reference-env",
"",
"https://8.8.8.8/mcp",
"https://8.8.8.8/changed-mcp",
409,
False,
True,
False,
),
(
"reference-env",
"",
"https://8.8.8.8/mcp?legacy=1",
"https://8.8.8.8/changed-mcp",
409,
False,
True,
True,
),
],
)
Expand All @@ -1244,9 +1326,12 @@ def test_generated_debug_applies_published_mcp_credential_contract_before_discov
tmp_path: Path,
credential_storage: str,
tool_name: str,
published_url: str,
edited_url: str,
expected_status: int,
expect_credential: bool,
explicit_reuse: bool,
has_published_draft: bool,
) -> None:
from agentkit.sdk.runtime.client import AgentkitRuntimeClient
from veadk.cli.generated_agent_mcp import McpDebugConnectionError
Expand All @@ -1261,7 +1346,7 @@ def test_generated_debug_applies_published_mcp_credential_contract_before_discov
{
"name": tool_name,
"transport": "http",
"url": "https://8.8.8.8/mcp",
"url": published_url,
"authTokenEnv": credential_reference,
}
],
Expand All @@ -1275,13 +1360,15 @@ def test_generated_debug_applies_published_mcp_credential_contract_before_discov
[
{
"name": tool_name or "mcp",
"url": "https://8.8.8.8/mcp",
"url": published_url,
"headers": {"Authorization": f"Bearer {credential_value}"},
}
]
),
)
]
elif credential_storage == "missing":
runtime_envs = []
runtime = SimpleNamespace(
runtime_id="runtime-debug-mcp",
runtime_name="legacy-agent-runtime",
Expand All @@ -1307,8 +1394,10 @@ def test_generated_debug_applies_published_mcp_credential_contract_before_discov
)

captured_discovery_env: dict[str, str] = {}
discovery_calls: list[bool] = []

async def capture_mcp_discovery(draft, env_values=None):
discovery_calls.append(True)
captured_discovery_env.update(env_values or {})
if not expect_credential:
raise McpDebugConnectionError("changed MCP endpoint rejected")
Expand All @@ -1324,13 +1413,15 @@ async def request(self, _method: str, url: str, **_kwargs: Any):
if url.endswith("/list-apps"):
return _FakeResponse(json_data=["legacy_agent"])
if url.endswith("/web/agent-info/legacy_agent"):
return _FakeResponse(
json_data={
"name": "legacy_agent",
"description": "Existing Agent",
"draft": published_draft,
}
)
agent_info: dict[str, Any] = {
"name": "legacy_agent",
"description": "Existing Agent",
}
if has_published_draft:
agent_info["draft"] = published_draft
return _FakeResponse(json_data=agent_info)
if url.endswith("/web/agent-draft/legacy_agent"):
return _FakeResponse(status_code=404)
raise AssertionError(f"unexpected Runtime request path: {url}")

monkeypatch.setenv("_FAAS_FUNC_ID", "function-test")
Expand All @@ -1352,20 +1443,39 @@ async def request(self, _method: str, url: str, **_kwargs: Any):
with TestClient(app) as client:
edited_draft = json.loads(json.dumps(published_draft))
edited_draft["mcpTools"][0]["url"] = edited_url
payload = {
"draft": edited_draft,
"runtimeId": runtime.runtime_id,
"runtimeRegion": "cn-shanghai",
}
if explicit_reuse:
payload["mcpCredentialReuses"] = [
{
"agentName": "legacy_agent",
"name": tool_name,
"url": edited_url,
"sourceAuthTokenEnv": credential_reference,
}
]
response = client.post(
"/web/generated-agent-test-runs",
json={
"draft": edited_draft,
"runtimeId": runtime.runtime_id,
"runtimeRegion": "cn-shanghai",
},
json=payload,
)

assert response.status_code == expected_status, response.text
if expected_status != 200:
assert _FakeProcess.created == []
assert bool(discovery_calls) is (expected_status in {200, 422})
if "?" in published_url:
assert "MCP 地址无效" in response.json()["detail"]
assert "LegacyRecoveryError" not in response.text
assert "错误 ID" not in response.text
if expect_credential:
assert captured_discovery_env[credential_reference] == credential_value
else:
assert credential_reference not in captured_discovery_env
if credential_storage == "missing":
assert "缺少可用凭证" in response.json()["detail"]
assert credential_value not in response.text


Expand Down
Loading
Loading