import type { Route } from "@playwright/test"; import { expect, test } from "./helpers/run-checks-fixtures"; import { enableScopedE2EAuth, scopedPath } from "./helpers/auth"; type MockServer = { id: string; name: string; enabled: boolean; transport: "stdio" | "http"; command: string | null; args: string[]; envPlain: Record; envSecretNames: string[]; url: string | null; headerPlain: Record; headerSecretNames: string[]; extra: Record; createdAt: string; updatedAt: string; }; const connectedUser = { id: "user-1", email: "alex@example.com", username: "alex", name: "Alex", avatar_url: "https://example.com/avatar.png", github_connected: true, github_app_connected: true, github_app_available: true, github_connection_mode: "app" as const, platform_access: { allowPlatformAi: true, allowPlatformSandbox: true, }, }; async function fulfillJson(route: Route, data: unknown, status = 200) { await route.fulfill({ status, contentType: "application/json", body: JSON.stringify(data), }); } test("Connections MCP tab supports add, edit, secret overwrite, and delete without exposing refs", async ({ page, }) => { await enableScopedE2EAuth(page); const postBodies: unknown[] = []; const patchBodies: unknown[] = []; let servers: MockServer[] = []; await page.route("**/api/auth/user", (route) => fulfillJson(route, { user: connectedUser }) ); await page.route(/\/api\/repos(?:\?.*)?$/, (route) => fulfillJson(route, [])); await page.route( /\/api\/mcp-servers(?:\/[^/?]+)?(?:\?.*)?$/, async (route) => { const request = route.request(); const url = new URL(request.url()); const pathSegments = url.pathname.split("/").filter(Boolean); const maybeId = pathSegments.length === 3 ? pathSegments[2] : null; if (request.method() === "GET") { if (maybeId) { const server = servers.find((entry) => entry.id === maybeId); if (!server) { await fulfillJson(route, { error: "Server not found" }, 404); return; } await fulfillJson(route, { server }); return; } await fulfillJson(route, { servers }); return; } if (request.method() === "POST") { const body = JSON.parse(request.postData() || "{}") as Record< string, unknown >; postBodies.push(body); expect(body).not.toHaveProperty("env_refs"); expect(body).not.toHaveProperty("header_refs"); const now = new Date().toISOString(); const server: MockServer = { id: "server-1", name: String(body.name), enabled: body.enabled !== false, transport: body.transport === "http" ? "http" : "stdio", command: typeof body.command === "string" ? body.command : null, args: Array.isArray(body.args) ? (body.args as string[]) : [], envPlain: (body.envPlain as Record) ?? {}, envSecretNames: Object.keys( (body.envSecrets as Record) ?? {} ), url: typeof body.url === "string" ? body.url : null, headerPlain: (body.headerPlain as Record) ?? {}, headerSecretNames: Object.keys( (body.headerSecrets as Record) ?? {} ), extra: (body.extra as Record) ?? {}, createdAt: now, updatedAt: now, }; servers = [server]; await fulfillJson(route, { server }, 201); return; } if (request.method() === "PATCH" && maybeId) { const body = JSON.parse(request.postData() || "{}") as Record< string, unknown >; patchBodies.push(body); expect(body).not.toHaveProperty("env_refs"); expect(body).not.toHaveProperty("header_refs"); const current = servers.find((entry) => entry.id === maybeId); expect(current).toBeTruthy(); const envSecrets = (body.envSecrets as Record) ?? {}; const nextEnvSecretNames = new Set(current?.envSecretNames); for (const [key, value] of Object.entries(envSecrets)) { if (value === null) { nextEnvSecretNames.delete(key); } else { nextEnvSecretNames.add(key); } } const headerSecrets = (body.headerSecrets as Record) ?? {}; const nextHeaderSecretNames = new Set(current?.headerSecretNames); for (const [key, value] of Object.entries(headerSecrets)) { if (value === null) { nextHeaderSecretNames.delete(key); } else { nextHeaderSecretNames.add(key); } } const updated: MockServer = { id: maybeId, name: String(body.name), enabled: body.enabled !== false, transport: body.transport === "http" ? "http" : "stdio", command: typeof body.command === "string" ? body.command : null, args: Array.isArray(body.args) ? (body.args as string[]) : [], envPlain: (body.envPlain as Record) ?? {}, envSecretNames: [...nextEnvSecretNames], url: typeof body.url === "string" ? body.url : null, headerPlain: (body.headerPlain as Record) ?? {}, headerSecretNames: [...nextHeaderSecretNames], extra: (body.extra as Record) ?? {}, createdAt: current?.createdAt ?? new Date().toISOString(), updatedAt: new Date().toISOString(), }; servers = [updated]; await fulfillJson(route, { server: updated }); return; } if (request.method() === "DELETE" && maybeId) { servers = servers.filter((entry) => entry.id !== maybeId); await fulfillJson(route, { ok: true }); return; } await fulfillJson(route, { error: "Unsupported route in test" }, 500); } ); await page.goto(scopedPath("connections?tab=mcp")); await page.getByRole("button", { name: "Add server" }).click(); await expect( page.getByRole("tab", { name: "Streamable HTTP" }) ).toHaveAttribute("aria-selected", "true"); await expect( page.getByText( "Available in web chat and the CLI. Web chat requires a public server URL." ) ).toBeVisible(); await page.getByRole("tab", { name: "Local (CLI only)" }).click(); await expect( page.getByText( "Runs on your computer through the CLI. Not available in web chat." ) ).toBeVisible(); await page.getByLabel("Name", { exact: true }).fill("supabase"); await page.getByLabel("Command").fill("npx"); await page .getByLabel("Args") .fill("-y\n@supabase/mcp-server-supabase@latest"); await page.getByRole("button", { name: "Add secret environment" }).click(); await page.getByPlaceholder("Environment name").fill("SUPABASE_ACCESS_TOKEN"); await page.getByPlaceholder("Secret value").fill("sbp_initial_123"); await page.getByRole("button", { name: "Add secret environment" }).click(); await page .getByPlaceholder("Environment name") .last() .fill("SUPABASE_PROJECT_TOKEN"); await page .getByPlaceholder("Secret value") .last() .fill("project_preview_456"); const primarySecret = page.getByLabel("SUPABASE_ACCESS_TOKEN", { exact: true, }); const secondarySecret = page.getByLabel("SUPABASE_PROJECT_TOKEN", { exact: true, }); await page .getByRole("button", { name: "Show SUPABASE_ACCESS_TOKEN", exact: true }) .click(); await expect(primarySecret).toHaveAttribute("type", "text"); await expect(primarySecret).toHaveValue("sbp_initial_123"); expect(postBodies).toHaveLength(0); await expect(secondarySecret).toHaveAttribute("type", "password"); await expect(secondarySecret).toHaveValue("project_preview_456"); await page .getByRole("button", { name: "Hide SUPABASE_ACCESS_TOKEN", exact: true }) .click(); await expect(primarySecret).toHaveAttribute("type", "password"); await page.getByRole("button", { name: "Create server" }).click(); await expect(page.getByText("supabase")).toBeVisible(); await expect(page.getByText("CLI only", { exact: true })).toBeVisible(); expect(postBodies).toHaveLength(1); await page.getByRole("button", { name: "Edit" }).click(); await expect( page.getByRole("dialog").getByText("Saved", { exact: true }) ).toHaveCount(2); await expect(primarySecret).toHaveValue(""); await expect(secondarySecret).toHaveValue(""); await expect( page.getByRole("button", { name: "Saved SUPABASE_ACCESS_TOKEN cannot be shown", exact: true, }) ).toBeDisabled(); await page.getByRole("button", { name: "Save changes" }).click(); await page.getByRole("button", { name: "Edit" }).click(); await primarySecret.fill("sbp_updated_456"); await page .getByRole("button", { name: "Show SUPABASE_ACCESS_TOKEN", exact: true }) .click(); await expect(primarySecret).toHaveValue("sbp_updated_456"); await expect(primarySecret).toHaveAttribute("type", "text"); await page.getByRole("button", { name: "Save changes" }).click(); expect(patchBodies).toHaveLength(2); expect(patchBodies[0]).toMatchObject({ envSecrets: {}, }); expect(patchBodies[1]).toMatchObject({ envSecrets: { SUPABASE_ACCESS_TOKEN: "sbp_updated_456", }, }); expect(patchBodies[1]).not.toHaveProperty( "envSecrets.SUPABASE_PROJECT_TOKEN" ); await page.getByRole("button", { name: "Delete" }).click(); await page .getByRole("alertdialog") .getByRole("button", { name: "Delete" }) .click(); await expect(page.getByText("No synced MCP servers yet.")).toBeVisible(); await page.getByRole("button", { name: "Add server" }).click(); await page.getByLabel("Name", { exact: true }).fill("remote-docs"); await page .getByLabel("URL", { exact: true }) .fill("https://docs.example.com/mcp"); await page.getByRole("button", { name: "Add secret header" }).click(); await page.getByPlaceholder("Header name").fill("Authorization"); await page.getByPlaceholder("Secret value").fill("Bearer saved-http-secret"); await page.getByRole("button", { name: "Create server" }).click(); await expect(page.getByText("remote-docs", { exact: true })).toBeVisible(); expect(postBodies[1]).toMatchObject({ transport: "http", enabled: true, url: "https://docs.example.com/mcp", headerSecrets: { Authorization: "Bearer saved-http-secret" }, }); await page.getByRole("button", { name: "Edit" }).click(); await expect(page.getByPlaceholder("Overwrite saved secret")).toHaveValue(""); await page.getByRole("switch", { name: "Server enabled" }).click(); await page.getByRole("button", { name: "Save changes" }).click(); await expect(page.getByText("Disabled", { exact: true })).toBeVisible(); expect(patchBodies[2]).toMatchObject({ enabled: false, headerSecrets: {} }); });