feat(ui): i18n for MCP server view and list

This commit is contained in:
吹雪 2026-06-10 21:11:19 +09:00
parent 359cc2920d
commit b6e1cf642f
4 changed files with 238 additions and 73 deletions

View file

@ -1,4 +1,5 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { ArrowLeftIcon, EyeIcon, EyeOffIcon } from "@heroicons/react/outline";
import { Title, Card, Button, Text, Grid, TabGroup, TabList, TabPanel, TabPanels, Tab, Icon } from "@tremor/react";
@ -56,6 +57,7 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
}) => {
// Open the editing Settings tab on first render when returning from the edit OAuth
// redirect, so the "token fetched" feedback shows where the user left off (Settings=2).
const { t } = useTranslation();
const returningFromEditOAuth = isReturningFromEditOAuth(isProxyAdmin, mcpServer.server_id);
const [editing, setEditing] = useState(isEditing || returningFromEditOAuth);
const [showFullUrl, setShowFullUrl] = useState(false);
@ -107,10 +109,12 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
<div className="p-4 max-w-full">
<div className="mb-6">
<Button icon={ArrowLeftIcon} variant="light" className="mb-4" onClick={onBack}>
Back to All Servers
{t("mcpTools.mcpServerView.backToAllServers")}
</Button>
<div className="flex items-center gap-2">
<Title className="text-2xl">{mcpServer.server_name || mcpServer.alias || "Unnamed Server"}</Title>
<Title className="text-2xl">
{mcpServer.server_name || mcpServer.alias || t("mcpTools.mcpServerView.unnamedServer")}
</Title>
<AntdButton
type="text"
size="small"
@ -149,9 +153,9 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
<TabGroup index={selectedTabIndex} onIndexChange={setSelectedTabIndex}>
<TabList className="mb-4">
{[
<Tab key="overview">Overview</Tab>,
<Tab key="tools">MCP Tools</Tab>,
...(isProxyAdmin ? [<Tab key="settings">Settings</Tab>] : []),
<Tab key="overview">{t("mcpTools.mcpServerView.tabOverview")}</Tab>,
<Tab key="tools">{t("mcpTools.mcpServerView.tabMcpTools")}</Tab>,
...(isProxyAdmin ? [<Tab key="settings">{t("common.settings")}</Tab>] : []),
]}
</TabList>
@ -160,7 +164,9 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
<TabPanel>
<Grid numItems={1} numItemsSm={2} numItemsLg={3} className="gap-4">
<Card className="p-4">
<Text className="text-xs font-medium text-gray-500 uppercase tracking-wide">Transport</Text>
<Text className="text-xs font-medium text-gray-500 uppercase tracking-wide">
{t("mcpTools.mcpServerView.transport")}
</Text>
<div className="mt-3">
{getTransportBadge(
handleTransport(mcpServer.transport ?? undefined, mcpServer.spec_path ?? undefined),
@ -169,12 +175,16 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
</Card>
<Card className="p-4">
<Text className="text-xs font-medium text-gray-500 uppercase tracking-wide">Authentication</Text>
<Text className="text-xs font-medium text-gray-500 uppercase tracking-wide">
{t("mcpTools.mcpServerView.authentication")}
</Text>
<div className="mt-3">{getAuthBadge(handleAuth(mcpServer.auth_type ?? undefined))}</div>
</Card>
<Card className="p-4">
<Text className="text-xs font-medium text-gray-500 uppercase tracking-wide">Host URL</Text>
<Text className="text-xs font-medium text-gray-500 uppercase tracking-wide">
{t("mcpTools.mcpServerView.hostUrl")}
</Text>
<div className="mt-3 flex items-center gap-2">
<Text className="break-all overflow-wrap-anywhere font-mono text-sm">
{renderUrlWithToggle(mcpServer.url, showFullUrl)}
@ -196,7 +206,9 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
</Card>
</Grid>
<Card className="mt-4 p-4">
<Text className="text-xs font-medium text-gray-500 uppercase tracking-wide">Cost Configuration</Text>
<Text className="text-xs font-medium text-gray-500 uppercase tracking-wide">
{t("mcpTools.mcpServerView.costConfiguration")}
</Text>
<div className="mt-3">
<MCPServerCostDisplay costConfig={mcpServer.mcp_info?.mcp_server_cost_info} />
</div>
@ -223,10 +235,10 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
<TabPanel>
<Card>
<div className="flex justify-between items-center mb-4">
<Title>MCP Server Settings</Title>
<Title>{t("mcpTools.mcpServerView.mcpServerSettings")}</Title>
{editing ? null : (
<Button variant="light" onClick={() => setEditing(true)}>
Edit Settings
{t("mcpTools.mcpServerView.editSettings")}
</Button>
)}
</div>
@ -242,25 +254,25 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
) : (
<div className="divide-y divide-gray-100">
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Server Name</Text>
<Text className="text-sm font-medium text-gray-500">{t("mcpTools.mcpServerView.serverName")}</Text>
<div className="col-span-2 text-sm text-gray-900">
{mcpServer.server_name || <span className="text-gray-400">—</span>}
</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Alias</Text>
<Text className="text-sm font-medium text-gray-500">{t("mcpTools.mcpServerView.alias")}</Text>
<div className="col-span-2 text-sm font-mono text-gray-900">
{mcpServer.alias || <span className="text-gray-400">—</span>}
</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Description</Text>
<Text className="text-sm font-medium text-gray-500">{t("common.description")}</Text>
<div className="col-span-2 text-sm text-gray-900">
{mcpServer.description || <span className="text-gray-400">—</span>}
</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">URL</Text>
<Text className="text-sm font-medium text-gray-500">{t("mcpTools.mcpServerView.url")}</Text>
<div className="col-span-2 text-sm font-mono text-gray-900 break-all flex items-center gap-2">
{renderUrlWithToggle(mcpServer.url, showFullUrl)}
{hasToken && (
@ -274,17 +286,21 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Transport</Text>
<Text className="text-sm font-medium text-gray-500">{t("mcpTools.mcpServerView.transport")}</Text>
<div className="col-span-2">
{getTransportBadge(handleTransport(mcpServer.transport, mcpServer.spec_path))}
</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Authentication</Text>
<Text className="text-sm font-medium text-gray-500">
{t("mcpTools.mcpServerView.authentication")}
</Text>
<div className="col-span-2">{getAuthBadge(handleAuth(mcpServer.auth_type))}</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Extra Headers</Text>
<Text className="text-sm font-medium text-gray-500">
{t("mcpTools.mcpServerView.extraHeaders")}
</Text>
<div className="col-span-2 text-sm text-gray-900">
{mcpServer.extra_headers && mcpServer.extra_headers.length > 0 ? (
mcpServer.extra_headers.join(", ")
@ -294,48 +310,54 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Allow All Keys</Text>
<Text className="text-sm font-medium text-gray-500">
{t("mcpTools.mcpServerView.allowAllKeys")}
</Text>
<div className="col-span-2">
{mcpServer.allow_all_keys ? (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-green-50 text-green-700 rounded-full border border-green-200 text-xs font-medium">
<span className="h-1.5 w-1.5 rounded-full bg-green-500"></span>
Enabled
{t("common.enabled")}
</span>
) : (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-gray-50 text-gray-600 rounded-full border border-gray-200 text-xs font-medium">
Disabled
{t("common.disabled")}
</span>
)}
</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Network Access</Text>
<Text className="text-sm font-medium text-gray-500">
{t("mcpTools.mcpServerView.networkAccess")}
</Text>
<div className="col-span-2">
{mcpServer.available_on_public_internet ? (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-green-50 text-green-700 rounded-full border border-green-200 text-xs font-medium">
<span className="h-1.5 w-1.5 rounded-full bg-green-500"></span>
Public
{t("mcpTools.mcpServerView.networkPublic")}
</span>
) : (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-orange-50 text-orange-700 rounded-full border border-orange-200 text-xs font-medium">
<span className="h-1.5 w-1.5 rounded-full bg-orange-500"></span>
Internal only
{t("mcpTools.mcpServerView.networkInternalOnly")}
</span>
)}
</div>
</div>
{handleAuth(mcpServer.auth_type) === "oauth2" && (
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Delegate Auth to Upstream</Text>
<Text className="text-sm font-medium text-gray-500">
{t("mcpTools.mcpServerView.delegateAuthToUpstream")}
</Text>
<div className="col-span-2">
{mcpServer.delegate_auth_to_upstream ? (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-green-50 text-green-700 rounded-full border border-green-200 text-xs font-medium">
<span className="h-1.5 w-1.5 rounded-full bg-green-500"></span>
Enabled (PKCE passthrough)
{t("mcpTools.mcpServerView.enabledPkcePassthrough")}
</span>
) : (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-gray-50 text-gray-600 rounded-full border border-gray-200 text-xs font-medium">
Disabled
{t("common.disabled")}
</span>
)}
</div>
@ -347,23 +369,27 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
(h) => typeof h === "string" && h.toLowerCase() === "authorization",
) && (
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">OAuth Pass-through</Text>
<Text className="text-sm font-medium text-gray-500">
{t("mcpTools.mcpServerView.oauthPassthrough")}
</Text>
<div className="col-span-2">
{mcpServer.oauth_passthrough ? (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-green-50 text-green-700 rounded-full border border-green-200 text-xs font-medium">
<span className="h-1.5 w-1.5 rounded-full bg-green-500"></span>
Enabled
{t("common.enabled")}
</span>
) : (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-gray-50 text-gray-600 rounded-full border border-gray-200 text-xs font-medium">
Disabled
{t("common.disabled")}
</span>
)}
</div>
</div>
)}
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Access Groups</Text>
<Text className="text-sm font-medium text-gray-500">
{t("mcpTools.mcpServerView.accessGroups")}
</Text>
<div className="col-span-2">
{mcpServer.mcp_access_groups && mcpServer.mcp_access_groups.length > 0 ? (
<div className="flex flex-wrap gap-1.5">
@ -382,7 +408,9 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Allowed Tools</Text>
<Text className="text-sm font-medium text-gray-500">
{t("mcpTools.mcpServerView.allowedTools")}
</Text>
<div className="col-span-2">
{mcpServer.allowed_tools && mcpServer.allowed_tools.length > 0 ? (
<div className="flex flex-wrap gap-1.5">
@ -397,13 +425,13 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
</div>
) : (
<span className="inline-flex items-center text-xs font-medium px-2 py-0.5 rounded bg-green-50 text-green-700 border border-green-200">
All tools enabled
{t("mcpTools.mcpServerView.allToolsEnabled")}
</span>
)}
</div>
</div>
<div className="py-3 grid grid-cols-3 gap-4">
<Text className="text-sm font-medium text-gray-500">Cost</Text>
<Text className="text-sm font-medium text-gray-500">{t("mcpTools.mcpServerView.cost")}</Text>
<div className="col-span-2">
<MCPServerCostDisplay costConfig={mcpServer.mcp_info?.mcp_server_cost_info} />
</div>

View file

@ -2,6 +2,8 @@ import { isAdminRole } from "@/utils/roles";
import { QuestionCircleOutlined, SearchOutlined } from "@ant-design/icons";
import { Button, Tab, TabGroup, TabList, TabPanel, TabPanels, Text, Title } from "@tremor/react";
import NewBadge from "../common_components/NewBadge";
import { useTranslation } from "react-i18next";
import type { TFunction } from "i18next";
import { Descriptions, Empty, Input, Modal, Select, Spin, Tooltip, Typography } from "antd";
import React, { useEffect, useState, useMemo, useCallback } from "react";
import { useQuery } from "@tanstack/react-query";
@ -27,11 +29,11 @@ import { listMCPUserEnvVarStatus } from "../networking";
type SortKey = "created_desc" | "updated_desc" | "name_asc" | "health";
const SORT_OPTIONS: { value: SortKey; label: string }[] = [
{ value: "created_desc", label: "Recently created" },
{ value: "updated_desc", label: "Recently updated" },
{ value: "name_asc", label: "Name (A→Z)" },
{ value: "health", label: "Health (unhealthy first)" },
const getSortOptions = (t: TFunction): { value: SortKey; label: string }[] => [
{ value: "created_desc", label: t("mcpTools.mcpServers.sortRecentlyCreated") },
{ value: "updated_desc", label: t("mcpTools.mcpServers.sortRecentlyUpdated") },
{ value: "name_asc", label: t("mcpTools.mcpServers.sortNameAsc") },
{ value: "health", label: t("mcpTools.mcpServers.sortHealthFirst") },
];
const HEALTH_RANK: Record<string, number> = {
@ -92,6 +94,8 @@ const readToolsOAuthServerId = (): string | null => {
const { Option } = Select;
const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID }) => {
const { t } = useTranslation();
const sortOptions = useMemo(() => getSortOptions(t), [t]);
const { data: mcpServers, isLoading: isLoadingServers, refetch } = useMCPServers();
// Fetch health status for all servers
@ -317,7 +321,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
try {
setIsDeletingServer(true);
await deleteMCPServer(accessToken, serverIdToDelete);
NotificationsManager.success("Deleted MCP Server successfully");
NotificationsManager.success(t("mcpTools.mcpServers.deleteSuccess"));
// If the user is currently viewing the detail page of the server they
// just deleted, return them to the All Servers list. Otherwise the
// detail view would stay mounted, fall back to an empty stub server,
@ -381,44 +385,46 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
if (!accessToken || !userRole || !userID) {
console.log("Missing required authentication parameters", { accessToken, userRole, userID });
return <div className="p-6 text-center text-gray-500">Missing required authentication parameters.</div>;
return <div className="p-6 text-center text-gray-500">{t("mcpTools.mcpServers.missingAuthParams")}</div>;
}
return (
<div className="w-full h-full p-6">
<Modal
open={isDeleteModalOpen}
title="Delete MCP Server?"
title={t("mcpTools.mcpServers.deleteModalTitle")}
onOk={confirmDelete}
okText={isDeletingServer ? "Deleting..." : "Delete"}
okText={isDeletingServer ? t("common.deleting") : t("common.delete")}
onCancel={cancelDelete}
cancelText="Cancel"
cancelText={t("common.cancel")}
cancelButtonProps={{ disabled: isDeletingServer }}
okButtonProps={{ danger: true }}
confirmLoading={isDeletingServer}
>
<div className="space-y-4">
<AntdText className="text-gray-600">
This action is permanent and cannot be undone. All associated configurations will be removed.
</AntdText>
<AntdText className="text-gray-600">{t("mcpTools.mcpServers.deleteModalBody")}</AntdText>
{serverToDelete && (
<div className="mt-3 p-4 bg-gray-50 rounded-lg border border-gray-200">
<Descriptions column={1} size="small" colon={false}>
{serverToDelete.server_name && (
<Descriptions.Item label={<span className="text-gray-500 text-sm">Name</span>}>
<Descriptions.Item label={<span className="text-gray-500 text-sm">{t("common.name")}</span>}>
<AntdText strong className="text-sm">
{serverToDelete.server_name}
</AntdText>
</Descriptions.Item>
)}
<Descriptions.Item label={<span className="text-gray-500 text-sm">ID</span>}>
<Descriptions.Item
label={<span className="text-gray-500 text-sm">{t("mcpTools.mcpServers.idLabel")}</span>}
>
<AntdText code className="text-xs">
{serverToDelete.server_id}
</AntdText>
</Descriptions.Item>
{serverToDelete.url && (
<Descriptions.Item label={<span className="text-gray-500 text-sm">URL</span>}>
<Descriptions.Item
label={<span className="text-gray-500 text-sm">{t("mcpTools.mcpServerView.url")}</span>}
>
<AntdText code className="text-xs break-all">
{serverToDelete.url}
</AntdText>
@ -447,19 +453,19 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
<div className="flex items-center justify-between">
<div>
<div className="flex items-center gap-3">
<Title>MCP Servers</Title>
<Title>{t("mcpTools.mcpServers.pageTitle")}</Title>
{filteredServers.length > 0 && (
<span className="inline-flex items-center text-xs font-medium px-2 py-0.5 rounded-full bg-gray-100 text-gray-600 border border-gray-200">
{filteredServers.length}
</span>
)}
</div>
<Text className="text-tremor-content mt-1">Configure and manage your MCP servers</Text>
<Text className="text-tremor-content mt-1">{t("mcpTools.mcpServers.pageSubtitle")}</Text>
</div>
<div className="flex items-center gap-2">
{isAdminRole(userRole) && (
<Button className="flex-shrink-0" onClick={() => setDiscoveryVisible(true)}>
+ Add New MCP Server
{t("mcpTools.mcpServers.addNewServer")}
</Button>
)}
{!isAdminRole(userRole) && (
@ -471,7 +477,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
}}
variant="secondary"
>
+ Submit MCP Server
{t("mcpTools.mcpServers.submitServer")}
</Button>
)}
</div>
@ -494,15 +500,15 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
<TabGroup className="w-full h-full">
<TabList className="flex justify-between mt-2 w-full items-center">
<div className="flex">
<Tab>All Servers</Tab>
<Tab>Toolsets</Tab>
<Tab>Connect</Tab>
<Tab>Semantic Filter</Tab>
<Tab>Network Settings</Tab>
<Tab>{t("mcpTools.mcpServers.tabAllServers")}</Tab>
<Tab>{t("mcpTools.mcpServers.tabToolsets")}</Tab>
<Tab>{t("mcpTools.mcpServers.tabConnect")}</Tab>
<Tab>{t("mcpTools.mcpServers.tabSemanticFilter")}</Tab>
<Tab>{t("mcpTools.mcpServers.tabNetworkSettings")}</Tab>
{isAdminRole(userRole) && (
<Tab>
<span className="flex items-center gap-2">
Submitted MCPs <NewBadge />
{t("mcpTools.mcpServers.tabSubmittedMcps")} <NewBadge />
</span>
</Tab>
)}
@ -529,15 +535,19 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
<div className="flex flex-col space-y-4">
<div className="flex items-center gap-6 bg-white rounded-lg px-4 py-3 border border-gray-200">
<div className="flex items-center gap-2">
<Text className="text-sm font-medium text-gray-600 whitespace-nowrap">Team</Text>
<Text className="text-sm font-medium text-gray-600 whitespace-nowrap">
{t("mcpTools.mcpServers.filterTeam")}
</Text>
<Select value={selectedTeam} onChange={handleTeamChange} style={{ width: 220 }} size="middle">
<Option value="all">
<span className="font-medium">
{isInternalUser ? "All Available Servers" : "All Servers"}
{isInternalUser
? t("mcpTools.mcpServers.allAvailableServers")
: t("mcpTools.mcpServers.allServers")}
</span>
</Option>
<Option value="personal">
<span className="font-medium">Personal</span>
<span className="font-medium">{t("mcpTools.mcpServers.personal")}</span>
</Option>
{uniqueTeams.map((team) => (
<Option key={team.team_id} value={team.team_id}>
@ -549,8 +559,8 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
<div className="h-6 w-px bg-gray-200"></div>
<div className="flex items-center gap-2">
<Text className="text-sm font-medium text-gray-600 whitespace-nowrap">
Access Group
<Tooltip title="An MCP Access Group is a set of users or teams that have permission to access specific MCP servers. Use access groups to control and organize who can connect to which servers.">
{t("mcpTools.mcpServers.filterAccessGroup")}
<Tooltip title={t("mcpTools.mcpServers.accessGroupTooltip")}>
<QuestionCircleOutlined style={{ marginLeft: 4, color: "#9ca3af" }} />
</Tooltip>
</Text>
@ -561,7 +571,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
size="middle"
>
<Option value="all">
<span className="font-medium">All Access Groups</span>
<span className="font-medium">{t("mcpTools.mcpServers.allAccessGroups")}</span>
</Option>
{uniqueMcpAccessGroups.map((group) => (
<Option key={group} value={group}>
@ -577,20 +587,22 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
<Input
allowClear
prefix={<SearchOutlined className="text-gray-400" />}
placeholder="Search by name, alias, URL, or ID"
placeholder={t("mcpTools.mcpServers.searchPlaceholder")}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
style={{ maxWidth: 320 }}
/>
<div className="flex items-center gap-2">
<Text className="whitespace-nowrap text-sm font-medium text-gray-600">Sort</Text>
<Text className="whitespace-nowrap text-sm font-medium text-gray-600">
{t("mcpTools.mcpServers.sortLabel")}
</Text>
<Select
value={sortKey}
onChange={(v: SortKey) => setSortKey(v)}
style={{ width: 220 }}
size="middle"
>
{SORT_OPTIONS.map((opt) => (
{sortOptions.map((opt) => (
<Option key={opt.value} value={opt.value}>
{opt.label}
</Option>
@ -598,21 +610,24 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
</Select>
</div>
<div className="ml-auto text-xs text-gray-500">
{displayedServers.length} of {filteredServers.length} servers
{t("mcpTools.mcpServers.serverCount", {
displayed: displayedServers.length,
total: filteredServers.length,
})}
</div>
</div>
<div className="mt-4 w-full">
{isLoadingServers ? (
<div className="flex items-center justify-center rounded-lg border border-dashed border-gray-200 bg-white p-12">
<Spin tip="Loading MCP servers..." />
<Spin tip={t("mcpTools.mcpServers.loadingServers")} />
</div>
) : displayedServers.length === 0 ? (
<div className="rounded-lg border border-dashed border-gray-200 bg-white p-12">
<Empty
description={
filteredServers.length === 0
? "No MCP servers configured. Click '+ Add New MCP Server' to get started."
: "No servers match the current filters or search."
? t("mcpTools.mcpServers.emptyNoServers")
: t("mcpTools.mcpServers.emptyNoMatch")
}
/>
</div>

View file

@ -3647,6 +3647,67 @@
"awsRegionLabel": "AWS Region",
"awsRegionTooltip": "AWS region for SigV4 signing (e.g., us-east-1)",
"awsRegionPlaceholder": "us-east-1 (leave blank to keep existing)"
},
"mcpServerView": {
"backToAllServers": "Back to All Servers",
"unnamedServer": "Unnamed Server",
"tabOverview": "Overview",
"tabMcpTools": "MCP Tools",
"transport": "Transport",
"authentication": "Authentication",
"hostUrl": "Host URL",
"costConfiguration": "Cost Configuration",
"mcpServerSettings": "MCP Server Settings",
"editSettings": "Edit Settings",
"serverName": "Server Name",
"alias": "Alias",
"url": "URL",
"extraHeaders": "Extra Headers",
"allowAllKeys": "Allow All Keys",
"networkAccess": "Network Access",
"networkPublic": "Public",
"networkInternalOnly": "Internal only",
"delegateAuthToUpstream": "Delegate Auth to Upstream",
"enabledPkcePassthrough": "Enabled (PKCE passthrough)",
"oauthPassthrough": "OAuth Pass-through",
"accessGroups": "Access Groups",
"allowedTools": "Allowed Tools",
"allToolsEnabled": "All tools enabled",
"cost": "Cost"
},
"mcpServers": {
"sortRecentlyCreated": "Recently created",
"sortRecentlyUpdated": "Recently updated",
"sortNameAsc": "Name (A→Z)",
"sortHealthFirst": "Health (unhealthy first)",
"missingAuthParams": "Missing required authentication parameters.",
"deleteModalTitle": "Delete MCP Server?",
"deleteModalBody": "This action is permanent and cannot be undone. All associated configurations will be removed.",
"idLabel": "ID",
"pageTitle": "MCP Servers",
"pageSubtitle": "Configure and manage your MCP servers",
"addNewServer": "+ Add New MCP Server",
"submitServer": "+ Submit MCP Server",
"tabAllServers": "All Servers",
"tabToolsets": "Toolsets",
"tabConnect": "Connect",
"tabSemanticFilter": "Semantic Filter",
"tabNetworkSettings": "Network Settings",
"tabSubmittedMcps": "Submitted MCPs",
"filterTeam": "Team",
"allAvailableServers": "All Available Servers",
"allServers": "All Servers",
"personal": "Personal",
"filterAccessGroup": "Access Group",
"accessGroupTooltip": "An MCP Access Group is a set of users or teams that have permission to access specific MCP servers. Use access groups to control and organize who can connect to which servers.",
"allAccessGroups": "All Access Groups",
"searchPlaceholder": "Search by name, alias, URL, or ID",
"sortLabel": "Sort",
"serverCount": "{{displayed}} of {{total}} servers",
"loadingServers": "Loading MCP servers...",
"emptyNoServers": "No MCP servers configured. Click '+ Add New MCP Server' to get started.",
"emptyNoMatch": "No servers match the current filters or search.",
"deleteSuccess": "Deleted MCP Server successfully"
}
}
}

View file

@ -3647,6 +3647,67 @@
"awsRegionLabel": "AWS 区域",
"awsRegionTooltip": "用于 SigV4 签名的 AWS 区域(例如 us-east-1)",
"awsRegionPlaceholder": "us-east-1(留空则保留现有值)"
},
"mcpServerView": {
"backToAllServers": "返回所有服务器",
"unnamedServer": "未命名服务器",
"tabOverview": "概览",
"tabMcpTools": "MCP 工具",
"transport": "传输协议",
"authentication": "身份验证",
"hostUrl": "主机 URL",
"costConfiguration": "费用配置",
"mcpServerSettings": "MCP 服务器设置",
"editSettings": "编辑设置",
"serverName": "服务器名称",
"alias": "别名",
"url": "URL",
"extraHeaders": "额外请求头",
"allowAllKeys": "允许所有密钥",
"networkAccess": "网络访问",
"networkPublic": "公开",
"networkInternalOnly": "仅内部",
"delegateAuthToUpstream": "委托认证至上游",
"enabledPkcePassthrough": "已启用(PKCE 透传)",
"oauthPassthrough": "OAuth 透传",
"accessGroups": "访问组",
"allowedTools": "允许的工具",
"allToolsEnabled": "所有工具已启用",
"cost": "费用"
},
"mcpServers": {
"sortRecentlyCreated": "最新创建",
"sortRecentlyUpdated": "最近更新",
"sortNameAsc": "名称(A→Z)",
"sortHealthFirst": "健康状态(不健康优先)",
"missingAuthParams": "缺少必要的身份验证参数。",
"deleteModalTitle": "删除 MCP 服务器?",
"deleteModalBody": "此操作不可撤销,所有关联配置将被移除。",
"idLabel": "ID",
"pageTitle": "MCP 服务器",
"pageSubtitle": "配置和管理您的 MCP 服务器",
"addNewServer": "+ 添加新 MCP 服务器",
"submitServer": "+ 提交 MCP 服务器",
"tabAllServers": "所有服务器",
"tabToolsets": "工具集",
"tabConnect": "连接",
"tabSemanticFilter": "语义过滤",
"tabNetworkSettings": "网络设置",
"tabSubmittedMcps": "已提交的 MCP",
"filterTeam": "团队",
"allAvailableServers": "所有可用服务器",
"allServers": "所有服务器",
"personal": "个人",
"filterAccessGroup": "访问组",
"accessGroupTooltip": "MCP 访问组是一组具有访问特定 MCP 服务器权限的用户或团队。使用访问组来控制和组织谁可以连接到哪些服务器。",
"allAccessGroups": "所有访问组",
"searchPlaceholder": "按名称、别名、URL 或 ID 搜索",
"sortLabel": "排序",
"serverCount": "显示 {{displayed}} / {{total}} 个服务器",
"loadingServers": "正在加载 MCP 服务器...",
"emptyNoServers": "未配置 MCP 服务器。点击「+ 添加新 MCP 服务器」开始使用。",
"emptyNoMatch": "没有服务器匹配当前过滤条件或搜索。",
"deleteSuccess": "MCP 服务器已成功删除"
}
}
}