mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(ui): make playground MCP work on all tool-capable endpoints
Route chat, responses, anthropic, and interactions through the shared buildMcpToolBlocks helper so MCP server_url/label resolution is correct for any model. Chat was labeling every server as "litellm" and preferring aliases; responses used absolute /mcp URLs that broke the all-servers path
This commit is contained in:
parent
609f7beddf
commit
e99ef9d5dd
7 changed files with 78 additions and 107 deletions
|
|
@ -249,6 +249,30 @@ describe("ChatUI", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should enable the MCP tools selector for interactions", async () => {
|
||||
render(
|
||||
<ChatUI
|
||||
accessToken="1234567890"
|
||||
token="1234567890"
|
||||
userRole="user"
|
||||
userID="1234567890"
|
||||
disabledPersonalKeyCreation={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Test Key")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const mcpInput = () => screen.getByLabelText("Select MCP servers");
|
||||
|
||||
await selectComboboxOption("Select an endpoint", "/v1beta/interactions");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mcpInput()).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show Simulate failure to test fallbacks in Model Settings when chat endpoint is selected", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ const MCP_SUPPORTED_ENDPOINTS = new Set<EndpointType>([
|
|||
EndpointType.RESPONSES,
|
||||
EndpointType.MCP,
|
||||
EndpointType.ANTHROPIC_MESSAGES,
|
||||
EndpointType.INTERACTIONS,
|
||||
]);
|
||||
|
||||
const CUSTOM_MODEL_DEBOUNCE_WAIT_MS = 500;
|
||||
|
|
@ -1070,6 +1071,11 @@ const ChatUI: React.FC<ChatUIProps> = ({
|
|||
selectedTags,
|
||||
signal,
|
||||
customProxyBaseUrl || undefined,
|
||||
undefined,
|
||||
selectedMCPServers,
|
||||
mcpServers,
|
||||
mcpServerToolRestrictions,
|
||||
mcpToolsets,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import NotificationManager from "@/components/molecules/notifications_manager";
|
||||
import { getGlobalLitellmHeaderName, getProxyBaseUrl } from "@/components/networking";
|
||||
import { buildMcpToolBlocks } from "@/components/llm_calls/mcp_tool_blocks";
|
||||
import type { MCPServer, MCPToolset } from "@/components/mcp_tools/types";
|
||||
|
||||
export async function makeInteractionsRequest(
|
||||
input: string,
|
||||
|
|
@ -10,6 +12,10 @@ export async function makeInteractionsRequest(
|
|||
signal?: AbortSignal,
|
||||
customBaseUrl?: string,
|
||||
previousInteractionId?: string,
|
||||
selectedMCPServers?: string[],
|
||||
mcpServers?: MCPServer[],
|
||||
mcpServerToolRestrictions?: Record<string, string[]>,
|
||||
mcpToolsets?: MCPToolset[],
|
||||
): Promise<void> {
|
||||
if (!accessToken) {
|
||||
throw new Error("Virtual Key is required");
|
||||
|
|
@ -32,10 +38,18 @@ export async function makeInteractionsRequest(
|
|||
headers["x-litellm-tags"] = tags.join(",");
|
||||
}
|
||||
|
||||
const tools = buildMcpToolBlocks({
|
||||
selectedMCPServers,
|
||||
mcpServers,
|
||||
mcpToolsets,
|
||||
mcpServerToolRestrictions,
|
||||
});
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
model: selectedModel,
|
||||
input,
|
||||
stream: true,
|
||||
...(tools.length > 0 ? { tools } : {}),
|
||||
};
|
||||
if (previousInteractionId) {
|
||||
body.previous_interaction_id = previousInteractionId;
|
||||
|
|
|
|||
|
|
@ -172,23 +172,22 @@ describe("chat_completion", () => {
|
|||
|
||||
const callArgs = mockCreate.mock.calls[0][0];
|
||||
expect(callArgs.tool_choice).toBe("auto");
|
||||
expect(callArgs.tools).toHaveLength(2);
|
||||
|
||||
// Check first tool
|
||||
const firstTool = callArgs.tools[0];
|
||||
expect(firstTool.type).toBe("mcp");
|
||||
expect(firstTool.server_label).toBe("litellm");
|
||||
expect(firstTool.server_url).toBe("litellm_proxy/mcp/alpha");
|
||||
expect(firstTool.require_approval).toBe("never");
|
||||
expect(firstTool.allowed_tools).toEqual(["toolA", "toolB"]);
|
||||
|
||||
// Check second tool
|
||||
const secondTool = callArgs.tools[1];
|
||||
expect(secondTool.type).toBe("mcp");
|
||||
expect(secondTool.server_label).toBe("litellm");
|
||||
expect(secondTool.server_url).toBe("litellm_proxy/mcp/Beta");
|
||||
expect(secondTool.require_approval).toBe("never");
|
||||
expect(secondTool.allowed_tools).toEqual(["toolC"]);
|
||||
expect(callArgs.tools).toEqual([
|
||||
{
|
||||
type: "mcp",
|
||||
server_label: "Alpha",
|
||||
server_url: "litellm_proxy/mcp/Alpha",
|
||||
require_approval: "never",
|
||||
allowed_tools: ["toolA", "toolB"],
|
||||
},
|
||||
{
|
||||
type: "mcp",
|
||||
server_label: "Beta",
|
||||
server_url: "litellm_proxy/mcp/Beta",
|
||||
require_approval: "never",
|
||||
allowed_tools: ["toolC"],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should include mock_testing_fallbacks in request body when mockTestFallbacks is true", async () => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { TokenUsage } from "../chat_ui/ResponseMetrics";
|
|||
import { VectorStoreSearchResponse } from "../chat_ui/types";
|
||||
import { getProxyBaseUrl } from "@/components/networking";
|
||||
import { MCPServer, MCPToolset, type MCPEvent } from "@/components/mcp_tools/types";
|
||||
import { buildMcpToolBlocks } from "./mcp_tool_blocks";
|
||||
|
||||
const completionAsSingleChunk = (completion: ChatCompletion): ChatCompletionChunk =>
|
||||
({
|
||||
|
|
@ -81,48 +82,12 @@ export async function makeOpenAIChatCompletionRequest(
|
|||
} = {};
|
||||
let mcpListToolsProcessed = false;
|
||||
|
||||
// Build tools array
|
||||
const tools: any[] = [];
|
||||
|
||||
// Add MCP servers if selected
|
||||
if (selectedMCPServers && selectedMCPServers.length > 0) {
|
||||
if (selectedMCPServers.includes("__all__")) {
|
||||
// All MCP Servers selected
|
||||
tools.push({
|
||||
type: "mcp",
|
||||
server_label: "litellm",
|
||||
server_url: "litellm_proxy/mcp",
|
||||
require_approval: "never",
|
||||
});
|
||||
} else {
|
||||
// Individual servers/toolsets selected - create one entry per item
|
||||
selectedMCPServers.forEach((serverId) => {
|
||||
if (serverId.startsWith("toolset:")) {
|
||||
const toolsetId = serverId.slice("toolset:".length);
|
||||
const toolset = mcpToolsets?.find((t) => t.toolset_id === toolsetId);
|
||||
const toolsetName = toolset?.toolset_name || toolsetId;
|
||||
tools.push({
|
||||
type: "mcp",
|
||||
server_label: toolsetName,
|
||||
server_url: `litellm_proxy/mcp/${encodeURIComponent(toolsetName)}`,
|
||||
require_approval: "never",
|
||||
});
|
||||
} else {
|
||||
const server = mcpServers?.find((s) => s.server_id === serverId);
|
||||
const serverName = server?.alias || server?.server_name || serverId;
|
||||
const allowedTools = mcpServerToolRestrictions?.[serverId] || [];
|
||||
|
||||
tools.push({
|
||||
type: "mcp",
|
||||
server_label: "litellm",
|
||||
server_url: `litellm_proxy/mcp/${serverName}`,
|
||||
require_approval: "never",
|
||||
...(allowedTools.length > 0 ? { allowed_tools: allowedTools } : {}),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const tools = buildMcpToolBlocks({
|
||||
selectedMCPServers,
|
||||
mcpServers,
|
||||
mcpToolsets,
|
||||
mcpServerToolRestrictions,
|
||||
});
|
||||
|
||||
const requestBody = {
|
||||
model: selectedModel,
|
||||
|
|
|
|||
|
|
@ -280,14 +280,14 @@ describe("responses_api", () => {
|
|||
{
|
||||
type: "mcp",
|
||||
server_label: "Alpha",
|
||||
server_url: "https://example.com/mcp/Alpha",
|
||||
server_url: "litellm_proxy/mcp/Alpha",
|
||||
require_approval: "never",
|
||||
allowed_tools: ["toolA"],
|
||||
},
|
||||
{
|
||||
type: "mcp",
|
||||
server_label: "Beta",
|
||||
server_url: "https://example.com/mcp/Beta",
|
||||
server_url: "litellm_proxy/mcp/Beta",
|
||||
require_approval: "never",
|
||||
allowed_tools: ["toolB", "toolC"],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
handleCodeInterpreterCall,
|
||||
handleCodeInterpreterOutput,
|
||||
} from "./code_interpreter_handler";
|
||||
import { buildMcpToolBlocks } from "./mcp_tool_blocks";
|
||||
|
||||
export type { CodeInterpreterResult } from "./code_interpreter_handler";
|
||||
|
||||
|
|
@ -134,53 +135,15 @@ export async function makeOpenAIResponsesRequest(
|
|||
};
|
||||
});
|
||||
|
||||
// Build tools array
|
||||
const tools: any[] = [];
|
||||
const tools: Array<Record<string, unknown>> = [
|
||||
...buildMcpToolBlocks({
|
||||
selectedMCPServers,
|
||||
mcpServers,
|
||||
mcpToolsets,
|
||||
mcpServerToolRestrictions,
|
||||
}),
|
||||
];
|
||||
|
||||
// Add MCP servers if selected
|
||||
if (selectedMCPServers && selectedMCPServers.length > 0) {
|
||||
if (selectedMCPServers.includes("__all__")) {
|
||||
// All MCP Servers selected
|
||||
tools.push({
|
||||
type: "mcp",
|
||||
server_label: "litellm",
|
||||
server_url: `${proxyBaseUrl}/mcp`,
|
||||
require_approval: "never",
|
||||
});
|
||||
} else {
|
||||
// Individual servers/toolsets selected - create one entry per item
|
||||
selectedMCPServers.forEach((serverId) => {
|
||||
if (serverId.startsWith("toolset:")) {
|
||||
// Toolset: same /{name}/mcp pattern as individual servers
|
||||
const toolsetId = serverId.slice("toolset:".length);
|
||||
const toolset = mcpToolsets?.find((t) => t.toolset_id === toolsetId);
|
||||
const toolsetName = toolset?.toolset_name || toolsetId;
|
||||
tools.push({
|
||||
type: "mcp",
|
||||
server_label: toolsetName,
|
||||
server_url: `${proxyBaseUrl}/mcp/${encodeURIComponent(toolsetName)}`,
|
||||
require_approval: "never",
|
||||
});
|
||||
} else {
|
||||
const server = mcpServers?.find((s) => s.server_id === serverId);
|
||||
// Use server_name for both routing and labelling. server_name is the
|
||||
// unique registered identifier; aliases can collide across servers.
|
||||
const routeName = server?.server_name || serverId;
|
||||
const allowedTools = mcpServerToolRestrictions?.[serverId] || [];
|
||||
|
||||
tools.push({
|
||||
type: "mcp",
|
||||
server_label: routeName, // unique per request — collisions cause silent tool-routing failures
|
||||
server_url: `${proxyBaseUrl}/mcp/${encodeURIComponent(routeName)}`,
|
||||
require_approval: "never",
|
||||
...(allowedTools.length > 0 ? { allowed_tools: allowedTools } : {}),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add code_interpreter tool if enabled (OpenAI auto-creates container)
|
||||
if (codeInterpreterEnabled) {
|
||||
tools.push({
|
||||
type: "code_interpreter",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue