feat(ui): i18n for MCP tool configuration and tools list

This commit is contained in:
吹雪 2026-06-10 21:20:59 +09:00
parent b6e1cf642f
commit cd8fbb5709
4 changed files with 271 additions and 128 deletions

View file

@ -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&apos;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 &quot;{toolSearchTerm}&quot;</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>

View file

@ -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 &quot;Configure&quot; 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 &quot;{toolSearchTerm}&quot;</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 */

View file

@ -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}}",

View file

@ -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}}",