tests/e2e/mcp-servers-settings.spec.ts
Rama main · réplica de lectura del repositorio original.
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<string, string>;
envSecretNames: string[];
url: string | null;
headerPlain: Record<string, string>;
headerSecretNames: string[];
extra: Record<string, unknown>;
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<string, string>) ?? {},
envSecretNames: Object.keys(
(body.envSecrets as Record<string, string>) ?? {}
),
url: typeof body.url === "string" ? body.url : null,
headerPlain: (body.headerPlain as Record<string, string>) ?? {},
headerSecretNames: Object.keys(
(body.headerSecrets as Record<string, string>) ?? {}
),
extra: (body.extra as Record<string, unknown>) ?? {},
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<string, string | null>) ?? {};
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<string, string | null>) ?? {};
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<string, string>) ?? {},
envSecretNames: [...nextEnvSecretNames],
url: typeof body.url === "string" ? body.url : null,
headerPlain: (body.headerPlain as Record<string, string>) ?? {},
headerSecretNames: [...nextHeaderSecretNames],
extra: (body.extra as Record<string, unknown>) ?? {},
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: {} });
});