mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
test(e2e-ui): check the MCP Tools tab against the upstream's own tools/list (#42397)
* 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>
This commit is contained in:
parent
5cf17f9ce8
commit
b96842f62c
4 changed files with 1309 additions and 7 deletions
|
|
@ -1,8 +1,21 @@
|
|||
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);
|
||||
|
|
|
|||
1282
tests/e2e/ui/package-lock.json
generated
1282
tests/e2e/ui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,7 @@
|
|||
"e2e:migration:root": "playwright test --config migration.serverRootPath.config.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.30.0",
|
||||
"@playwright/test": "1.58.1",
|
||||
"@types/node": "20.19.37",
|
||||
"typescript": "5.9.3"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
import { test, expect, Locator } from "@playwright/test";
|
||||
import { ADMIN_STORAGE_PATH } from "../../constants";
|
||||
import { createMcpServer, deleteMcpServerByName, openMcpToolsTab } from "../../helpers/mcp";
|
||||
import { createMcpServer, deleteMcpServerByName, listUpstreamToolNames, openMcpToolsTab } from "../../helpers/mcp";
|
||||
|
||||
// Listing and calling MCP tools, which needs a server that really answers; the create-only spec
|
||||
// points at an unreachable URL on purpose.
|
||||
//
|
||||
// This spec makes a read-only network call to DeepWiki's public MCP server, from the proxy rather
|
||||
// than the browser. It needs no credentials, so there is no secret to leak from a public repo.
|
||||
// This spec makes read-only network calls to DeepWiki's public MCP server: from the proxy, and from
|
||||
// the test runner to learn which tools the upstream advertises today, so the tool list is never
|
||||
// pinned here. It needs no credentials, so there is no secret to leak from a public repo.
|
||||
//
|
||||
// A DeepWiki outage turns this red for something that is not a litellm regression. That is left
|
||||
// visible rather than auto-skipped: skipping on connection trouble also skips when the proxy's own
|
||||
// MCP client breaks, which is the regression this exists to catch. E2E_SKIP_EXTERNAL_MCP=1 opts out.
|
||||
const MCP_SERVER_URL = "https://mcp.deepwiki.com/mcp";
|
||||
// Read from DeepWiki's tools/list on 2026-09-22. One name has to be pinned so the call-tool test can
|
||||
// fill a known input (repoName); the listing test checks it is still advertised before the UI checks.
|
||||
const TOOL_NAME = "read_wiki_structure";
|
||||
const TOOL_ARG_REPO = "BerriAI/litellm";
|
||||
|
||||
|
|
@ -36,14 +39,17 @@ test.describe("MCP Tools", () => {
|
|||
});
|
||||
|
||||
test("MCP Tools tab lists the tools the upstream server advertises", async ({ page }) => {
|
||||
const upstreamTools = await listUpstreamToolNames(MCP_SERVER_URL);
|
||||
expect(upstreamTools).toContain(TOOL_NAME);
|
||||
|
||||
// Fetched through the proxy on mount, so allow for a cold upstream connection.
|
||||
const toolList = page.locator(".mcp-tools-scrollable");
|
||||
await expect(toolList).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
// Non-empty would still pass if the proxy returned some other server's tools.
|
||||
await expect(toolCard(toolList, TOOL_NAME)).toBeVisible();
|
||||
await expect(toolCard(toolList, "ask_question")).toBeVisible();
|
||||
await expect(toolCard(toolList, "read_wiki_contents")).toBeVisible();
|
||||
for (const name of upstreamTools) {
|
||||
await expect(toolCard(toolList, name)).toBeVisible();
|
||||
}
|
||||
await expect(toolList.locator("h4.font-mono")).toHaveCount(upstreamTools.length);
|
||||
|
||||
// No other tool's name or description contains this string, so exactly one card survives.
|
||||
await page.getByPlaceholder("Search tools...").fill(TOOL_NAME);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue