diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.test.tsx index 036eb9f2872..3311546aa8e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.test.tsx @@ -249,6 +249,30 @@ describe("ChatUI", () => { }); }); + it("should enable the MCP tools selector for interactions", async () => { + render( + , + ); + + 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( diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx index 56905c5dfe4..2b8f76aeaf3 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx @@ -103,6 +103,7 @@ const MCP_SUPPORTED_ENDPOINTS = new Set([ EndpointType.RESPONSES, EndpointType.MCP, EndpointType.ANTHROPIC_MESSAGES, + EndpointType.INTERACTIONS, ]); const CUSTOM_MODEL_DEBOUNCE_WAIT_MS = 500; @@ -1070,6 +1071,11 @@ const ChatUI: React.FC = ({ selectedTags, signal, customProxyBaseUrl || undefined, + undefined, + selectedMCPServers, + mcpServers, + mcpServerToolRestrictions, + mcpToolsets, ); } } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/llm_calls/interactions_api.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/llm_calls/interactions_api.tsx index 39d998fe7eb..d03880bbe70 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/llm_calls/interactions_api.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/llm_calls/interactions_api.tsx @@ -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, + mcpToolsets?: MCPToolset[], ): Promise { 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 = { model: selectedModel, input, stream: true, + ...(tools.length > 0 ? { tools } : {}), }; if (previousInteractionId) { body.previous_interaction_id = previousInteractionId; diff --git a/ui/litellm-dashboard/src/components/llm_calls/chat_completion.test.tsx b/ui/litellm-dashboard/src/components/llm_calls/chat_completion.test.tsx index 10252adecd0..9a9e7a42b08 100644 --- a/ui/litellm-dashboard/src/components/llm_calls/chat_completion.test.tsx +++ b/ui/litellm-dashboard/src/components/llm_calls/chat_completion.test.tsx @@ -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 () => { diff --git a/ui/litellm-dashboard/src/components/llm_calls/chat_completion.tsx b/ui/litellm-dashboard/src/components/llm_calls/chat_completion.tsx index 549f309b4ea..c4e7e1f4533 100644 --- a/ui/litellm-dashboard/src/components/llm_calls/chat_completion.tsx +++ b/ui/litellm-dashboard/src/components/llm_calls/chat_completion.tsx @@ -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, diff --git a/ui/litellm-dashboard/src/components/llm_calls/responses_api.test.tsx b/ui/litellm-dashboard/src/components/llm_calls/responses_api.test.tsx index a897e6fc4cc..7f54e67c300 100644 --- a/ui/litellm-dashboard/src/components/llm_calls/responses_api.test.tsx +++ b/ui/litellm-dashboard/src/components/llm_calls/responses_api.test.tsx @@ -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"], }, diff --git a/ui/litellm-dashboard/src/components/llm_calls/responses_api.tsx b/ui/litellm-dashboard/src/components/llm_calls/responses_api.tsx index f356b2cb2c3..6eb72fad46b 100644 --- a/ui/litellm-dashboard/src/components/llm_calls/responses_api.tsx +++ b/ui/litellm-dashboard/src/components/llm_calls/responses_api.tsx @@ -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> = [ + ...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",