Skip to content
Open
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
34 changes: 30 additions & 4 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
getSession,
getStudioAccess,
getRuntimeStudioToolCapabilities,
getRuntimeMcpCredentials,
getRuntimes,
listApps,
listEnvironments,
Expand Down Expand Up @@ -150,7 +151,10 @@ import { AgentCreationModePicker } from "./create/AgentCreationModePicker";
import { CodePackageCreate } from "./create/CodePackageCreate";
import { MigrationWorkspace } from "./migrations/MigrationWorkspace";
import type { AgentDraft } from "./create/types";
import { configuredMcpEnvKeys } from "./create/mcpAuth";
import {
configuredMcpEnvKeys,
hydrateMcpCredentialValues,
} from "./create/mcpAuth";
import {
hydrateRuntimeModelSelection,
isRuntimeModelSelectionEnv,
Expand Down Expand Up @@ -7274,11 +7278,33 @@ export default function App() {
hydratedDraft,
arkModelIds,
);
let editorDraft = classifiedDraft;
if (configuredMcpEnvKeys(classifiedDraft).length > 0) {
try {
const credentials = await getRuntimeMcpCredentials({
runtimeId: capability.runtime.runtimeId,
region: capability.runtime.region,
appName: capability.agent.appName,
etag: capability.etag,
});
editorDraft = hydrateMcpCredentialValues(
classifiedDraft,
credentials,
);
} catch (credentialError) {
setError(
credentialError instanceof Error
? credentialError.message
: appText("errors.runtimeDeploymentConfigUnavailable"),
);
return;
}
}
exitAgentDetailContext();
setImportedDraft(classifiedDraft);
setImportedDraft(editorDraft);
setCustomCreateMode("custom");
setCustomCreationSurface(
classifiedDraft.dynamicAgentDelegation === true
editorDraft.dynamicAgentDelegation === true
? "vulcan"
: "traditional",
);
Expand All @@ -7301,7 +7327,7 @@ export default function App() {
capability.editMode === "source-preserving"
? "source-preserving"
: "regenerate",
configuredMcpEnvKeys: configuredMcpEnvKeys(classifiedDraft),
configuredMcpEnvKeys: configuredMcpEnvKeys(editorDraft),
configuredRuntimeEnvKeys:
capability.runtime.configuredEnvKeys,
});
Expand Down
52 changes: 52 additions & 0 deletions frontend/src/adk/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
import type { AgentProject } from "../create/project";
import type {
AgentDraft,
McpCredentialValue,
NetworkConfig,
SelectedSkill,
} from "../create/types";
Expand Down Expand Up @@ -4578,6 +4579,57 @@ export interface RuntimeUpdateCapability {
} | null;
}

/** Fetch the exact MCP credentials for one authorized update snapshot. */
export async function getRuntimeMcpCredentials({
runtimeId,
region,
appName,
etag,
signal,
}: {
runtimeId: string;
region: string;
appName: string;
etag: string;
signal?: AbortSignal;
}): Promise<McpCredentialValue[]> {
const res = await apiFetch("/web/runtime-mcp-credentials", {
method: "POST",
cache: "no-store",
signal,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ runtimeId, region, appName, etag }),
});
if (!res.ok) {
throw new Error(
await httpErrorMessage(res, adkT("client.loadMcpCredentialsFailed")),
);
}
const payload = (await res.json().catch(() => null)) as {
credentials?: unknown;
} | null;
if (!Array.isArray(payload?.credentials)) {
throw new Error(adkT("client.invalidMcpCredentials"));
}
return payload.credentials.map((item) => {
if (!item || typeof item !== "object") {
throw new Error(adkT("client.invalidMcpCredentials"));
}
const raw = item as Record<string, unknown>;
const credential = {
agentName: raw.agentName,
name: raw.name,
url: raw.url,
authTokenEnv: raw.authTokenEnv,
value: raw.value,
};
if (Object.values(credential).some((value) => typeof value !== "string")) {
throw new Error(adkT("client.invalidMcpCredentials"));
}
return credential as McpCredentialValue;
});
}

interface RuntimeUpdateCapabilityRequest {
runtimeId: string;
region: string;
Expand Down
61 changes: 25 additions & 36 deletions frontend/src/create/CustomCreate.css
Original file line number Diff line number Diff line change
Expand Up @@ -3752,50 +3752,39 @@
line-height: 1.5;
color: hsl(var(--muted-foreground));
}
.cw-mcp-auth-state {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
min-height: 32px;
padding: 7px 10px;
border: 1px solid hsl(var(--border));
border-radius: 8px;
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
font-size: 12px;
line-height: 1.45;
.cw-mcp-token-field {
position: relative;
}
.cw-mcp-auth-state button {
flex: 0 0 auto;
min-height: 28px;
padding: 0 9px;
border: 1px solid hsl(var(--border));
.cw-mcp-token-field .cw-input {
padding-right: 40px;
}
.cw-mcp-token-toggle {
position: absolute;
top: 50%;
right: 4px;
display: inline-grid;
width: 28px;
height: 28px;
padding: 0;
border: 0;
border-radius: 6px;
background: hsl(var(--panel));
color: hsl(var(--foreground));
background: transparent;
color: hsl(var(--muted-foreground));
cursor: pointer;
font: inherit;
place-items: center;
transform: translateY(-50%);
}
.cw-mcp-auth-state button:hover {
background: hsl(var(--accent));
.cw-mcp-token-toggle:hover {
background: hsl(var(--muted));
color: hsl(var(--foreground));
}
.cw-mcp-auth-state button:focus-visible {
.cw-mcp-token-toggle:focus-visible {
outline: 2px solid hsl(var(--primary));
outline-offset: 1px;
}
.cw-mcp-auth-state.is-warning {
align-items: flex-start;
border-color: hsl(var(--destructive) / 0.35);
background: hsl(var(--destructive) / 0.06);
color: hsl(var(--foreground));
}
.cw-mcp-auth-actions {
display: flex;
flex: 0 0 auto;
flex-wrap: wrap;
justify-content: flex-end;
gap: 6px;
.cw-mcp-token-toggle svg {
width: 16px;
height: 16px;
}
.cw-mcp-warning {
display: flex;
Expand Down
Loading
Loading