mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for MCP server view and list
This commit is contained in:
parent
359cc2920d
commit
b6e1cf642f
4 changed files with 238 additions and 73 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 服务器已成功删除"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue