mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for MCP tool configuration and tools list
This commit is contained in:
parent
b6e1cf642f
commit
cd8fbb5709
4 changed files with 271 additions and 128 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { Card, Title, Text } from "@tremor/react";
|
||||
import { ToolOutlined, CheckCircleOutlined, SearchOutlined, EditOutlined } from "@ant-design/icons";
|
||||
import { Badge, Spin, Checkbox, Input, Radio } from "antd";
|
||||
|
|
@ -59,86 +60,93 @@ const ToolRow: React.FC<ToolRowProps> = ({
|
|||
onToggleExpand,
|
||||
onDisplayNameChange,
|
||||
onDescriptionChange,
|
||||
}) => (
|
||||
<div
|
||||
className={`rounded-lg border transition-colors ${
|
||||
isEnabled
|
||||
? "bg-blue-50 border-blue-300 hover:border-blue-400"
|
||||
: "bg-gray-50 border-gray-200 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<div className="p-4 cursor-pointer" onClick={() => onToggle(tool.name)}>
|
||||
<div className="flex items-start gap-3">
|
||||
<Checkbox checked={isEnabled} onChange={() => onToggle(tool.name)} />
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Text className="font-medium text-gray-900">{toolNameToDisplayName[tool.name] || tool.name}</Text>
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs rounded-full font-medium ${
|
||||
isEnabled ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"
|
||||
}`}
|
||||
>
|
||||
{isEnabled ? "Enabled" : "Disabled"}
|
||||
</span>
|
||||
{toolNameToDisplayName[tool.name] && (
|
||||
<span className="px-2 py-0.5 text-xs rounded-full font-medium bg-purple-100 text-purple-800">
|
||||
Custom name
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
className={`rounded-lg border transition-colors ${
|
||||
isEnabled
|
||||
? "bg-blue-50 border-blue-300 hover:border-blue-400"
|
||||
: "bg-gray-50 border-gray-200 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<div className="p-4 cursor-pointer" onClick={() => onToggle(tool.name)}>
|
||||
<div className="flex items-start gap-3">
|
||||
<Checkbox checked={isEnabled} onChange={() => onToggle(tool.name)} />
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Text className="font-medium text-gray-900">{toolNameToDisplayName[tool.name] || tool.name}</Text>
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs rounded-full font-medium ${
|
||||
isEnabled ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"
|
||||
}`}
|
||||
>
|
||||
{isEnabled ? t("common.enabled") : t("common.disabled")}
|
||||
</span>
|
||||
{toolNameToDisplayName[tool.name] && (
|
||||
<span className="px-2 py-0.5 text-xs rounded-full font-medium bg-purple-100 text-purple-800">
|
||||
{t("mcpTools.mcpToolConfiguration.customName")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{(toolNameToDescription[tool.name] || tool.description) && (
|
||||
<Text className="text-gray-500 text-sm block mt-1">
|
||||
{toolNameToDescription[tool.name] || tool.description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
{(toolNameToDescription[tool.name] || tool.description) && (
|
||||
<Text className="text-gray-500 text-sm block mt-1">
|
||||
{toolNameToDescription[tool.name] || tool.description}
|
||||
<Text className="text-gray-400 text-xs block mt-1">
|
||||
{isEnabled
|
||||
? t("mcpTools.mcpToolConfiguration.toolCallableByUsers")
|
||||
: t("mcpTools.mcpToolConfiguration.toolBlockedFromUsers")}
|
||||
</Text>
|
||||
)}
|
||||
<Text className="text-gray-400 text-xs block mt-1">
|
||||
{isEnabled ? "✓ Users can call this tool" : "✗ Users cannot call this tool"}
|
||||
</Text>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => onToggleExpand(tool.name, e)}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
isEditExpanded ? "bg-blue-100 text-blue-600" : "text-gray-400 hover:text-gray-600 hover:bg-gray-100"
|
||||
}`}
|
||||
title={t("mcpTools.mcpToolConfiguration.editDisplayNameTitle")}
|
||||
>
|
||||
<EditOutlined />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => onToggleExpand(tool.name, e)}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
isEditExpanded ? "bg-blue-100 text-blue-600" : "text-gray-400 hover:text-gray-600 hover:bg-gray-100"
|
||||
}`}
|
||||
title="Edit display name and description"
|
||||
</div>
|
||||
{isEditExpanded && (
|
||||
<div
|
||||
className="px-4 pb-4 pt-3 border-t border-gray-200 space-y-3 bg-gray-50 rounded-b-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<EditOutlined />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-xs font-medium text-gray-600 mb-1 block">
|
||||
{t("mcpTools.mcpToolConfiguration.displayName")}
|
||||
</Text>
|
||||
<Input
|
||||
placeholder={tool.name}
|
||||
value={toolNameToDisplayName[tool.name] || ""}
|
||||
onChange={(e) => onDisplayNameChange(tool.name, e.target.value)}
|
||||
/>
|
||||
<Text className="text-xs text-gray-400 mt-1 block">
|
||||
{t("mcpTools.mcpToolConfiguration.displayNameHelp")}
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-xs font-medium text-gray-600 mb-1 block">{t("common.description")}</Text>
|
||||
<Input.TextArea
|
||||
placeholder={tool.description || t("mcpTools.mcpToolConfiguration.noDescription")}
|
||||
value={toolNameToDescription[tool.name] || ""}
|
||||
onChange={(e) => onDescriptionChange(tool.name, e.target.value)}
|
||||
rows={2}
|
||||
/>
|
||||
<Text className="text-xs text-gray-400 mt-1 block">
|
||||
{t("mcpTools.mcpToolConfiguration.descriptionHelp")}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isEditExpanded && (
|
||||
<div
|
||||
className="px-4 pb-4 pt-3 border-t border-gray-200 space-y-3 bg-gray-50 rounded-b-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div>
|
||||
<Text className="text-xs font-medium text-gray-600 mb-1 block">Display Name</Text>
|
||||
<Input
|
||||
placeholder={tool.name}
|
||||
value={toolNameToDisplayName[tool.name] || ""}
|
||||
onChange={(e) => onDisplayNameChange(tool.name, e.target.value)}
|
||||
/>
|
||||
<Text className="text-xs text-gray-400 mt-1 block">
|
||||
Override how this tool's name appears to users. Leave blank to use original.
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-xs font-medium text-gray-600 mb-1 block">Description</Text>
|
||||
<Input.TextArea
|
||||
placeholder={tool.description || "No description"}
|
||||
value={toolNameToDescription[tool.name] || ""}
|
||||
onChange={(e) => onDescriptionChange(tool.name, e.target.value)}
|
||||
rows={2}
|
||||
/>
|
||||
<Text className="text-xs text-gray-400 mt-1 block">
|
||||
Override the tool description shown to users. Leave blank to use original.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
||||
accessToken,
|
||||
|
|
@ -159,6 +167,7 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
externalCanFetch,
|
||||
isEditMode = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const previousToolsRef = useRef<ToolEntry[]>([]);
|
||||
const [toolSearchTerm, setToolSearchTerm] = useState("");
|
||||
const [viewMode, setViewMode] = useState<"crud" | "flat">("crud");
|
||||
|
|
@ -387,7 +396,7 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<ToolOutlined className="text-blue-600" />
|
||||
<Title>Tool Configuration</Title>
|
||||
<Title>{t("mcpTools.mcpToolConfiguration.title")}</Title>
|
||||
{tools.length > 0 && (
|
||||
<Badge
|
||||
count={tools.length}
|
||||
|
|
@ -405,8 +414,8 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
options={[
|
||||
{ label: "Risk Groups", value: "crud" },
|
||||
{ label: "Flat List", value: "flat" },
|
||||
{ label: t("mcpTools.mcpToolConfiguration.viewRiskGroups"), value: "crud" },
|
||||
{ label: t("mcpTools.mcpToolConfiguration.viewFlatList"), value: "flat" },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -415,8 +424,7 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
{/* Description */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||
<Text className="text-blue-800 text-sm">
|
||||
<strong>Select which tools users can call:</strong> Only checked tools will be available for users to
|
||||
invoke. Unchecked tools will be blocked from execution.
|
||||
<Trans i18nKey="mcpTools.mcpToolConfiguration.selectToolsDescription" components={{ strong: <strong /> }} />
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
|
|
@ -424,7 +432,7 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
{isLoadingTools && (
|
||||
<div className="flex items-center justify-center py-6">
|
||||
<Spin size="large" />
|
||||
<Text className="ml-3">Loading tools from spec...</Text>
|
||||
<Text className="ml-3">{t("mcpTools.mcpToolConfiguration.loadingFromSpec")}</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -432,7 +440,7 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
{toolsError && !isLoadingTools && (
|
||||
<div className="text-center py-6 text-red-500 border rounded-lg border-dashed border-red-300 bg-red-50">
|
||||
<ToolOutlined className="text-2xl mb-2" />
|
||||
<Text className="text-red-600 font-medium">Unable to load tools</Text>
|
||||
<Text className="text-red-600 font-medium">{t("mcpTools.mcpToolConfiguration.unableToLoadTools")}</Text>
|
||||
<br />
|
||||
<Text className="text-sm text-red-500">{toolsError}</Text>
|
||||
</div>
|
||||
|
|
@ -446,15 +454,19 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
(keyTools && keyTools.length > 0 ? (
|
||||
<div className="text-center py-4 text-gray-400 border rounded-lg border-dashed">
|
||||
<ToolOutlined className="text-2xl mb-2" />
|
||||
<Text>No tools loaded from spec</Text>
|
||||
<Text className="text-sm block mt-1">Expected tools: {keyTools.map((t) => t.name).join(", ")}</Text>
|
||||
<Text>{t("mcpTools.mcpToolConfiguration.noToolsLoadedFromSpec")}</Text>
|
||||
<Text className="text-sm block mt-1">
|
||||
{t("mcpTools.mcpToolConfiguration.expectedTools", {
|
||||
tools: keyTools.map((tool) => tool.name).join(", "),
|
||||
})}
|
||||
</Text>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-6 text-gray-400 border rounded-lg border-dashed">
|
||||
<ToolOutlined className="text-2xl mb-2" />
|
||||
<Text>No tools available for configuration</Text>
|
||||
<Text>{t("mcpTools.mcpToolConfiguration.noToolsAvailable")}</Text>
|
||||
<br />
|
||||
<Text className="text-sm">Connect to an MCP server with tools to configure them</Text>
|
||||
<Text className="text-sm">{t("mcpTools.mcpToolConfiguration.connectMcpServerHint")}</Text>
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
|
@ -462,9 +474,9 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
{!canFetchTools && (formValues.url || formValues.spec_path) && (
|
||||
<div className="text-center py-6 text-gray-400 border rounded-lg border-dashed">
|
||||
<ToolOutlined className="text-2xl mb-2" />
|
||||
<Text>Complete required fields to configure tools</Text>
|
||||
<Text>{t("mcpTools.mcpToolConfiguration.completeRequiredFields")}</Text>
|
||||
<br />
|
||||
<Text className="text-sm">Fill in URL, Transport, and Authentication to load available tools</Text>
|
||||
<Text className="text-sm">{t("mcpTools.mcpToolConfiguration.fillInFieldsHint")}</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -474,14 +486,16 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
<div className="flex items-center gap-2 p-3 bg-green-50 rounded-lg border border-green-200">
|
||||
<CheckCircleOutlined className="text-green-600" />
|
||||
<Text className="text-green-700 font-medium">
|
||||
{effectiveAllowedTools.length} of {tools.length} {tools.length === 1 ? "tool" : "tools"} enabled for
|
||||
user access
|
||||
{t("mcpTools.mcpToolConfiguration.toolsEnabled", {
|
||||
enabled: effectiveAllowedTools.length,
|
||||
count: tools.length,
|
||||
})}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* Search box shared by both views */}
|
||||
<Input
|
||||
placeholder="Search tools by name or description..."
|
||||
placeholder={t("mcpTools.mcpToolConfiguration.searchPlaceholder")}
|
||||
prefix={<SearchOutlined className="text-gray-400" />}
|
||||
value={toolSearchTerm}
|
||||
onChange={(e) => setToolSearchTerm(e.target.value)}
|
||||
|
|
@ -506,28 +520,30 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
{filteredTools.length === 0 ? (
|
||||
<div className="text-center py-6 text-gray-400 border rounded-lg border-dashed">
|
||||
<SearchOutlined className="text-2xl mb-2" />
|
||||
<Text>No tools found matching "{toolSearchTerm}"</Text>
|
||||
<Text>{t("mcpTools.mcpToolConfiguration.noToolsMatchSearch", { term: toolSearchTerm })}</Text>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{pinnedFiltered.length > 0 && (
|
||||
<>
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wide">Suggested tools</p>
|
||||
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wide">
|
||||
{t("mcpTools.mcpToolConfiguration.suggestedTools")}
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleEnableSuggested}
|
||||
className="text-xs text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
Enable all
|
||||
{t("mcpTools.mcpToolConfiguration.enableAll")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDisableSuggested}
|
||||
className="text-xs text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
Disable all
|
||||
{t("mcpTools.mcpToolConfiguration.disableAll")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -551,7 +567,9 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
<>
|
||||
<div className="flex items-center justify-between px-1 pt-2">
|
||||
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wide">
|
||||
{pinnedFiltered.length > 0 ? "All tools" : "Tools"}
|
||||
{pinnedFiltered.length > 0
|
||||
? t("mcpTools.mcpToolConfiguration.allTools")
|
||||
: t("mcpTools.mcpToolConfiguration.tools")}
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
|
|
@ -559,14 +577,14 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
|
|||
onClick={handleEnableRest}
|
||||
className="text-xs text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
Enable all
|
||||
{t("mcpTools.mcpToolConfiguration.enableAll")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDisableRest}
|
||||
className="text-xs text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
Disable all
|
||||
{t("mcpTools.mcpToolConfiguration.disableAll")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { ToolTestPanel } from "./ToolTestPanel";
|
||||
import { MCPTool, MCPToolsViewerProps, MCPContent, CallMCPToolResponse, getMcpOAuthMode } from "./types";
|
||||
|
|
@ -25,6 +26,7 @@ const MCPToolsViewer = ({
|
|||
serverAlias,
|
||||
extraHeaders,
|
||||
}: MCPToolsViewerProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedTool, setSelectedTool] = useState<MCPTool | null>(null);
|
||||
const [toolResult, setToolResult] = useState<MCPContent[] | null>(null);
|
||||
const [toolError, setToolError] = useState<Error | null>(null);
|
||||
|
|
@ -260,7 +262,7 @@ const MCPToolsViewer = ({
|
|||
<div className="flex h-auto w-full gap-4">
|
||||
{/* Left Sidebar with Controls */}
|
||||
<div className="w-1/4 p-4 bg-gray-50 flex flex-col">
|
||||
<Title className="text-xl font-semibold mb-6 mt-2">MCP Tools</Title>
|
||||
<Title className="text-xl font-semibold mb-6 mt-2">{t("mcpTools.mcpTools.title")}</Title>
|
||||
|
||||
<div className="flex flex-col flex-1">
|
||||
{/* Extra Headers Input Section */}
|
||||
|
|
@ -269,7 +271,9 @@ const MCPToolsViewer = ({
|
|||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center">
|
||||
<KeyOutlined className="text-blue-600 mr-2" />
|
||||
<Text className="text-sm font-medium text-blue-800">Additional Headers</Text>
|
||||
<Text className="text-sm font-medium text-blue-800">
|
||||
{t("mcpTools.mcpTools.additionalHeaders")}
|
||||
</Text>
|
||||
</div>
|
||||
<AntdButton
|
||||
size="small"
|
||||
|
|
@ -277,14 +281,12 @@ const MCPToolsViewer = ({
|
|||
onClick={() => setShowHeaderInput(!showHeaderInput)}
|
||||
className="text-blue-700 p-0 h-auto"
|
||||
>
|
||||
{showHeaderInput ? "Hide" : "Configure"}
|
||||
{showHeaderInput ? t("mcpTools.mcpTools.hide") : t("mcpTools.mcpTools.configure")}
|
||||
</AntdButton>
|
||||
</div>
|
||||
|
||||
{!showHeaderInput && Object.keys(passthroughHeaders).length === 0 && (
|
||||
<Text className="text-xs text-blue-700">
|
||||
This server requires additional headers. Click "Configure" to provide values.
|
||||
</Text>
|
||||
<Text className="text-xs text-blue-700">{t("mcpTools.mcpTools.headersRequiredHint")}</Text>
|
||||
)}
|
||||
|
||||
{showHeaderInput && (
|
||||
|
|
@ -294,7 +296,7 @@ const MCPToolsViewer = ({
|
|||
<label className="block text-xs font-medium text-gray-700 mb-1">{headerName}</label>
|
||||
<Input
|
||||
size="small"
|
||||
placeholder={`Enter ${headerName}`}
|
||||
placeholder={t("mcpTools.mcpTools.enterHeaderPlaceholder", { header: headerName })}
|
||||
value={passthroughHeaders[headerName] || ""}
|
||||
onChange={(e) => {
|
||||
setPassthroughHeaders({
|
||||
|
|
@ -317,7 +319,7 @@ const MCPToolsViewer = ({
|
|||
disabled={Object.values(passthroughHeaders).every((v) => !v || !v.trim())}
|
||||
className="w-full mt-2"
|
||||
>
|
||||
Load Tools
|
||||
{t("mcpTools.mcpTools.loadTools")}
|
||||
</AntdButton>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -326,7 +328,7 @@ const MCPToolsViewer = ({
|
|||
<div className="mt-2">
|
||||
<Text className="text-xs text-green-700 flex items-center">
|
||||
<span className="inline-block w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
||||
{Object.keys(passthroughHeaders).length} header(s) configured
|
||||
{t("mcpTools.mcpTools.headersConfigured", { count: Object.keys(passthroughHeaders).length })}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -336,7 +338,7 @@ const MCPToolsViewer = ({
|
|||
{/* Tool Selection - Show tools first */}
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<Text className="font-medium block mb-3 text-gray-700 flex items-center">
|
||||
<ToolOutlined className="mr-2" /> Available Tools
|
||||
<ToolOutlined className="mr-2" /> {t("mcpTools.mcpTools.availableTools")}
|
||||
{toolsData.length > 0 && (
|
||||
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-medium px-2 py-0.5 rounded-full">
|
||||
{toolsData.length}
|
||||
|
|
@ -348,8 +350,8 @@ const MCPToolsViewer = ({
|
|||
{isPassthrough && !oauthToken && (
|
||||
<div className="p-4 text-center bg-white border border-gray-200 rounded-lg">
|
||||
<LockOutlined className="text-2xl text-gray-400 mb-2" />
|
||||
<p className="text-xs font-medium text-gray-700 mb-1">Authentication required</p>
|
||||
<p className="text-xs text-gray-500 mb-3">Authenticate to view available tools</p>
|
||||
<p className="text-xs font-medium text-gray-700 mb-1">{t("mcpTools.mcpTools.authRequired")}</p>
|
||||
<p className="text-xs text-gray-500 mb-3">{t("mcpTools.mcpTools.authenticateToViewTools")}</p>
|
||||
<AntdButton
|
||||
size="small"
|
||||
type="primary"
|
||||
|
|
@ -357,7 +359,7 @@ const MCPToolsViewer = ({
|
|||
onClick={startOAuthFlow}
|
||||
disabled={!accessToken}
|
||||
>
|
||||
Authorize
|
||||
{t("mcpTools.mcpTools.authorize")}
|
||||
</AntdButton>
|
||||
{oauthError && <p className="text-xs text-red-500 mt-2">{oauthError}</p>}
|
||||
</div>
|
||||
|
|
@ -369,10 +371,8 @@ const MCPToolsViewer = ({
|
|||
{oboNeedsAuth && (
|
||||
<div className="p-4 text-center bg-white border border-gray-200 rounded-lg">
|
||||
<LockOutlined className="text-2xl text-gray-400 mb-2" />
|
||||
<p className="text-xs font-medium text-gray-700 mb-1">Authentication required</p>
|
||||
<p className="text-xs text-gray-500 mb-3">
|
||||
Authenticate with the upstream provider to view available tools
|
||||
</p>
|
||||
<p className="text-xs font-medium text-gray-700 mb-1">{t("mcpTools.mcpTools.authRequired")}</p>
|
||||
<p className="text-xs text-gray-500 mb-3">{t("mcpTools.mcpTools.authenticateWithUpstream")}</p>
|
||||
<AntdButton
|
||||
size="small"
|
||||
type="primary"
|
||||
|
|
@ -380,7 +380,7 @@ const MCPToolsViewer = ({
|
|||
onClick={startOboAuthorize}
|
||||
disabled={!accessToken}
|
||||
>
|
||||
Authorize
|
||||
{t("mcpTools.mcpTools.authorize")}
|
||||
</AntdButton>
|
||||
{dbOAuthError && <p className="text-xs text-red-500 mt-2">{dbOAuthError}</p>}
|
||||
</div>
|
||||
|
|
@ -392,7 +392,7 @@ const MCPToolsViewer = ({
|
|||
{toolsData.length > 0 && (
|
||||
<div className="mb-3">
|
||||
<Input
|
||||
placeholder="Search tools..."
|
||||
placeholder={t("mcpTools.mcpTools.searchToolsPlaceholder")}
|
||||
prefix={<SearchOutlined className="text-gray-400" />}
|
||||
value={toolSearchTerm}
|
||||
onChange={(e) => setToolSearchTerm(e.target.value)}
|
||||
|
|
@ -410,7 +410,7 @@ const MCPToolsViewer = ({
|
|||
<div className="animate-spin rounded-full h-6 w-6 border-2 border-gray-200"></div>
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-2 border-blue-600 border-t-transparent absolute top-0"></div>
|
||||
</div>
|
||||
<p className="text-xs font-medium text-gray-700">Loading tools...</p>
|
||||
<p className="text-xs font-medium text-gray-700">{t("mcpTools.mcpTools.loadingTools")}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -418,7 +418,8 @@ const MCPToolsViewer = ({
|
|||
{(mcpToolsResponse?.error || mcpToolsError) && !toolsAreaLoading && !toolsData.length && (
|
||||
<div className="p-3 text-xs text-red-800 rounded-lg bg-red-50 border border-red-200">
|
||||
<p className="font-medium">
|
||||
Error: {mcpToolsResponse?.message || (mcpToolsError as Error)?.message}
|
||||
{t("mcpTools.mcpTools.errorPrefix")}
|
||||
{mcpToolsResponse?.message || (mcpToolsError as Error)?.message}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -444,8 +445,10 @@ const MCPToolsViewer = ({
|
|||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-xs font-medium text-gray-700 mb-1">No tools available</p>
|
||||
<p className="text-xs text-gray-500">No tools found for this server</p>
|
||||
<p className="text-xs font-medium text-gray-700 mb-1">
|
||||
{t("mcpTools.mcpTools.noToolsAvailable")}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">{t("mcpTools.mcpTools.noToolsFoundForServer")}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -455,8 +458,12 @@ const MCPToolsViewer = ({
|
|||
{filteredTools.length === 0 ? (
|
||||
<div className="p-4 text-center bg-white border border-gray-200 rounded-lg">
|
||||
<SearchOutlined className="text-2xl text-gray-400 mb-2" />
|
||||
<p className="text-xs font-medium text-gray-700 mb-1">No tools found</p>
|
||||
<p className="text-xs text-gray-500">No tools match "{toolSearchTerm}"</p>
|
||||
<p className="text-xs font-medium text-gray-700 mb-1">
|
||||
{t("mcpTools.mcpTools.noToolsFound")}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{t("mcpTools.mcpTools.noToolsMatchSearch", { term: toolSearchTerm })}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
|
|
@ -485,7 +492,7 @@ const MCPToolsViewer = ({
|
|||
{tool.mcp_info.logo_url && (
|
||||
<img
|
||||
src={tool.mcp_info.logo_url}
|
||||
alt={`${tool.mcp_info.server_name} logo`}
|
||||
alt={t("mcpTools.mcpTools.serverLogoAlt", { name: tool.mcp_info.server_name })}
|
||||
className="w-4 h-4 object-contain flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
)}
|
||||
|
|
@ -509,7 +516,7 @@ const MCPToolsViewer = ({
|
|||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Selected
|
||||
{t("mcpTools.mcpTools.selected")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -528,7 +535,7 @@ const MCPToolsViewer = ({
|
|||
{/* Main Testing Area */}
|
||||
<div className="w-3/4 flex flex-col bg-white">
|
||||
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
||||
<Title className="text-xl font-semibold mb-0">Tool Testing Playground</Title>
|
||||
<Title className="text-xl font-semibold mb-0">{t("mcpTools.mcpTools.testingPlaygroundTitle")}</Title>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto p-4">
|
||||
|
|
@ -536,10 +543,10 @@ const MCPToolsViewer = ({
|
|||
/* Empty State */
|
||||
<div className="h-full flex flex-col items-center justify-center text-gray-400">
|
||||
<RobotOutlined style={{ fontSize: "48px", marginBottom: "16px" }} />
|
||||
<Text className="text-lg font-medium text-gray-600 mb-2">Select a Tool to Test</Text>
|
||||
<Text className="text-center text-gray-500 max-w-md">
|
||||
Choose a tool from the left sidebar to start testing its functionality with custom inputs.
|
||||
<Text className="text-lg font-medium text-gray-600 mb-2">
|
||||
{t("mcpTools.mcpTools.selectToolToTest")}
|
||||
</Text>
|
||||
<Text className="text-center text-gray-500 max-w-md">{t("mcpTools.mcpTools.selectToolHint")}</Text>
|
||||
</div>
|
||||
) : (
|
||||
/* Tool Test Panel */
|
||||
|
|
|
|||
|
|
@ -3045,6 +3045,65 @@
|
|||
}
|
||||
},
|
||||
"mcpTools": {
|
||||
"mcpToolConfiguration": {
|
||||
"title": "Tool Configuration",
|
||||
"viewRiskGroups": "Risk Groups",
|
||||
"viewFlatList": "Flat List",
|
||||
"selectToolsDescription": "<strong>Select which tools users can call:</strong> Only checked tools will be available for users to invoke. Unchecked tools will be blocked from execution.",
|
||||
"loadingFromSpec": "Loading tools from spec...",
|
||||
"unableToLoadTools": "Unable to load tools",
|
||||
"noToolsLoadedFromSpec": "No tools loaded from spec",
|
||||
"expectedTools": "Expected tools: {{tools}}",
|
||||
"noToolsAvailable": "No tools available for configuration",
|
||||
"connectMcpServerHint": "Connect to an MCP server with tools to configure them",
|
||||
"completeRequiredFields": "Complete required fields to configure tools",
|
||||
"fillInFieldsHint": "Fill in URL, Transport, and Authentication to load available tools",
|
||||
"toolsEnabled_one": "{{enabled}} of {{count}} tool enabled for user access",
|
||||
"toolsEnabled_other": "{{enabled}} of {{count}} tools enabled for user access",
|
||||
"searchPlaceholder": "Search tools by name or description...",
|
||||
"noToolsMatchSearch": "No tools found matching \"{{term}}\"",
|
||||
"suggestedTools": "Suggested tools",
|
||||
"enableAll": "Enable all",
|
||||
"disableAll": "Disable all",
|
||||
"allTools": "All tools",
|
||||
"tools": "Tools",
|
||||
"customName": "Custom name",
|
||||
"toolCallableByUsers": "✓ Users can call this tool",
|
||||
"toolBlockedFromUsers": "✗ Users cannot call this tool",
|
||||
"editDisplayNameTitle": "Edit display name and description",
|
||||
"displayName": "Display Name",
|
||||
"displayNameHelp": "Override how this tool's name appears to users. Leave blank to use original.",
|
||||
"noDescription": "No description",
|
||||
"descriptionHelp": "Override the tool description shown to users. Leave blank to use original."
|
||||
},
|
||||
"mcpTools": {
|
||||
"title": "MCP Tools",
|
||||
"additionalHeaders": "Additional Headers",
|
||||
"hide": "Hide",
|
||||
"configure": "Configure",
|
||||
"headersRequiredHint": "This server requires additional headers. Click \"Configure\" to provide values.",
|
||||
"enterHeaderPlaceholder": "Enter {{header}}",
|
||||
"loadTools": "Load Tools",
|
||||
"headersConfigured_one": "{{count}} header configured",
|
||||
"headersConfigured_other": "{{count}} header(s) configured",
|
||||
"availableTools": "Available Tools",
|
||||
"authRequired": "Authentication required",
|
||||
"authenticateToViewTools": "Authenticate to view available tools",
|
||||
"authenticateWithUpstream": "Authenticate with the upstream provider to view available tools",
|
||||
"authorize": "Authorize",
|
||||
"searchToolsPlaceholder": "Search tools...",
|
||||
"loadingTools": "Loading tools...",
|
||||
"errorPrefix": "Error: ",
|
||||
"noToolsAvailable": "No tools available",
|
||||
"noToolsFoundForServer": "No tools found for this server",
|
||||
"noToolsFound": "No tools found",
|
||||
"noToolsMatchSearch": "No tools match \"{{term}}\"",
|
||||
"serverLogoAlt": "{{name}} logo",
|
||||
"selected": "Selected",
|
||||
"testingPlaygroundTitle": "Tool Testing Playground",
|
||||
"selectToolToTest": "Select a Tool to Test",
|
||||
"selectToolHint": "Choose a tool from the left sidebar to start testing its functionality with custom inputs."
|
||||
},
|
||||
"byokCredentialModal": {
|
||||
"pleaseEnterApiKey": "Please enter your API key",
|
||||
"connectedSuccess": "Connected to {{name}}",
|
||||
|
|
|
|||
|
|
@ -3045,6 +3045,65 @@
|
|||
}
|
||||
},
|
||||
"mcpTools": {
|
||||
"mcpToolConfiguration": {
|
||||
"title": "工具配置",
|
||||
"viewRiskGroups": "风险分组",
|
||||
"viewFlatList": "平铺列表",
|
||||
"selectToolsDescription": "<strong>选择用户可调用的工具:</strong>只有勾选的工具才对用户开放调用,未勾选的工具将被阻止执行。",
|
||||
"loadingFromSpec": "正在从 spec 加载工具...",
|
||||
"unableToLoadTools": "无法加载工具",
|
||||
"noToolsLoadedFromSpec": "未从 spec 加载到工具",
|
||||
"expectedTools": "预期工具:{{tools}}",
|
||||
"noToolsAvailable": "暂无可配置的工具",
|
||||
"connectMcpServerHint": "连接到带有工具的 MCP 服务器以进行配置",
|
||||
"completeRequiredFields": "请填写必填字段以配置工具",
|
||||
"fillInFieldsHint": "填写 URL、传输方式和认证信息以加载可用工具",
|
||||
"toolsEnabled_one": "已为用户开放 {{enabled}} / {{count}} 个工具",
|
||||
"toolsEnabled_other": "已为用户开放 {{enabled}} / {{count}} 个工具",
|
||||
"searchPlaceholder": "按名称或描述搜索工具...",
|
||||
"noToolsMatchSearch": "未找到匹配「{{term}}」的工具",
|
||||
"suggestedTools": "推荐工具",
|
||||
"enableAll": "全部启用",
|
||||
"disableAll": "全部禁用",
|
||||
"allTools": "所有工具",
|
||||
"tools": "工具",
|
||||
"customName": "自定义名称",
|
||||
"toolCallableByUsers": "✓ 用户可以调用此工具",
|
||||
"toolBlockedFromUsers": "✗ 用户无法调用此工具",
|
||||
"editDisplayNameTitle": "编辑显示名称和描述",
|
||||
"displayName": "显示名称",
|
||||
"displayNameHelp": "覆盖此工具对用户显示的名称,留空则使用原名称。",
|
||||
"noDescription": "无描述",
|
||||
"descriptionHelp": "覆盖向用户显示的工具描述,留空则使用原描述。"
|
||||
},
|
||||
"mcpTools": {
|
||||
"title": "MCP 工具",
|
||||
"additionalHeaders": "附加请求头",
|
||||
"hide": "收起",
|
||||
"configure": "配置",
|
||||
"headersRequiredHint": "此服务器需要附加请求头,请点击「配置」提供相应值。",
|
||||
"enterHeaderPlaceholder": "输入 {{header}}",
|
||||
"loadTools": "加载工具",
|
||||
"headersConfigured_one": "已配置 {{count}} 个请求头",
|
||||
"headersConfigured_other": "已配置 {{count}} 个请求头",
|
||||
"availableTools": "可用工具",
|
||||
"authRequired": "需要认证",
|
||||
"authenticateToViewTools": "请先认证以查看可用工具",
|
||||
"authenticateWithUpstream": "请向上游提供商认证以查看可用工具",
|
||||
"authorize": "授权",
|
||||
"searchToolsPlaceholder": "搜索工具...",
|
||||
"loadingTools": "加载工具中...",
|
||||
"errorPrefix": "错误:",
|
||||
"noToolsAvailable": "暂无可用工具",
|
||||
"noToolsFoundForServer": "此服务器暂无工具",
|
||||
"noToolsFound": "未找到工具",
|
||||
"noToolsMatchSearch": "没有工具匹配「{{term}}」",
|
||||
"serverLogoAlt": "{{name}} 图标",
|
||||
"selected": "已选择",
|
||||
"testingPlaygroundTitle": "工具测试 Playground",
|
||||
"selectToolToTest": "选择工具进行测试",
|
||||
"selectToolHint": "从左侧边栏选择工具,开始使用自定义输入测试其功能。"
|
||||
},
|
||||
"byokCredentialModal": {
|
||||
"pleaseEnterApiKey": "请输入您的 API 密钥",
|
||||
"connectedSuccess": "已连接到 {{name}}",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue