mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-28 01:32:17 +00:00
* test(e2e-ui): check the MCP Tools tab against the upstream's own tools/list DeepWiki renamed ask_question to ask_wiki_question, and the spec hardcoded the old name, so e2e_ui_testing went red on main for something that is not a litellm regression. The spec now asks the upstream server for its tool list with the official MCP TypeScript SDK and expects the tab to show exactly those cards, so a vendor rename cannot turn the job red again. * test(e2e-ui): cite the pinned DeepWiki tool name and drop the helper docstring --------- Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
76 lines
3.6 KiB
TypeScript
76 lines
3.6 KiB
TypeScript
import { expect, Page as PwPage } from "@playwright/test";
|
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
import { navigateToPage } from "./navigation";
|
|
import { Page } from "../fixtures/pages";
|
|
import { masterKey } from "./traffic";
|
|
|
|
export async function listUpstreamToolNames(url: string): Promise<string[]> {
|
|
const client = new Client({ name: "litellm-ui-e2e", version: "0.0.0" });
|
|
await client.connect(new StreamableHTTPClientTransport(new URL(url)));
|
|
try {
|
|
const { tools } = await client.listTools();
|
|
return tools.map((tool) => tool.name);
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
}
|
|
|
|
/** Creates an MCP server through the UI's discovery to custom-form flow and returns its name. */
|
|
export async function createMcpServer(page: PwPage, url: string): Promise<string> {
|
|
await navigateToPage(page, Page.McpServers);
|
|
|
|
await page.getByRole("button", { name: /Add New MCP Server/i }).click();
|
|
const discovery = page.getByRole("dialog").filter({ hasText: "Add MCP Server" });
|
|
await expect(discovery).toBeVisible({ timeout: 5_000 });
|
|
await discovery.getByRole("button", { name: /Custom Server/i }).click();
|
|
|
|
const formModal = page.getByRole("dialog").filter({ hasText: "MCP Server Name" });
|
|
await expect(formModal).toBeVisible({ timeout: 5_000 });
|
|
|
|
// validateMCPServerName rejects spaces and hyphens; the worker index avoids a same-millisecond collision.
|
|
const name = `e2e_mcp_${process.env.TEST_WORKER_INDEX ?? "0"}_${Date.now()}`;
|
|
await formModal.getByLabel("MCP Server Name").fill(name);
|
|
|
|
// Select popups are portaled to the body, so the option lookup is page-scoped, not modal-scoped.
|
|
await formModal.getByRole("combobox", { name: "Transport Type" }).click();
|
|
await page.getByRole("option", { name: "Streamable HTTP" }).click();
|
|
|
|
await formModal.getByLabel("MCP Server URL").fill(url);
|
|
|
|
await formModal.getByRole("combobox", { name: "Authentication", exact: true }).click();
|
|
await page.getByRole("option", { name: "None", exact: true }).click();
|
|
|
|
await formModal.getByRole("button", { name: /^Add MCP Server$/ }).click();
|
|
await expect(page.getByText("MCP Server created successfully").first()).toBeVisible({ timeout: 15_000 });
|
|
|
|
const card = page.getByTestId("mcp-servers-grid").getByText(name).first();
|
|
await expect(card).toBeVisible({ timeout: 10_000 });
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Deletes every server carrying `serverName`. Leaked servers break unrelated MCP specs: the page
|
|
* reaches out to each one it lists, so unreachable leftovers stall networkidle until it times out.
|
|
* Errors are swallowed because this runs from afterEach.
|
|
*/
|
|
export async function deleteMcpServerByName(page: PwPage, serverName: string): Promise<void> {
|
|
const headers = { Authorization: `Bearer ${masterKey()}` };
|
|
try {
|
|
const res = await page.request.get("/v1/mcp/server", { headers });
|
|
if (!res.ok()) return;
|
|
const servers = (await res.json()) as { server_id: string; server_name?: string }[];
|
|
for (const server of servers.filter((candidate) => candidate.server_name === serverName)) {
|
|
await page.request.delete(`/v1/mcp/server/${server.server_id}`, { headers });
|
|
}
|
|
} catch {
|
|
// best effort, see above
|
|
}
|
|
}
|
|
|
|
/** Opens a server from the grid and switches to its MCP Tools tab. */
|
|
export async function openMcpToolsTab(page: PwPage, serverName: string): Promise<void> {
|
|
await page.getByTestId("mcp-servers-grid").getByText(serverName).first().click();
|
|
await expect(page.getByRole("button", { name: /Back to All Servers/i })).toBeVisible({ timeout: 10_000 });
|
|
await page.getByRole("tab", { name: "MCP Tools" }).click();
|
|
}
|