feat(ui): i18n for MCP server creation and user env vars modal

This commit is contained in:
吹雪 2026-06-10 19:14:19 +09:00
parent 8708a1e614
commit 6b7e0f6bee
4 changed files with 338 additions and 110 deletions

View file

@ -4,6 +4,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { MCPServer, MCPUserEnvVarsStatus } from "./types";
import { getMCPUserEnvVars, storeMCPUserEnvVars } from "../networking";
import NotificationsManager from "../molecules/notifications_manager";
import { useTranslation } from "react-i18next";
const { Text, Title } = Typography;
@ -24,6 +25,7 @@ interface UserEnvVarsModalProps {
*/
const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({ server, open, accessToken, onClose, onSaved }) => {
const [form] = Form.useForm();
const { t } = useTranslation();
const {
data: status,
@ -38,12 +40,16 @@ const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({ server, open, acces
const saveMutation = useMutation({
mutationFn: (values: Record<string, string>) => storeMCPUserEnvVars(accessToken!, server!.server_id, values),
onSuccess: (saved) => {
NotificationsManager.success("Credentials saved");
NotificationsManager.success(t("mcpTools.userEnvVarsModal.savedSuccess"));
onSaved?.(saved);
onClose();
},
onError: (err) => {
NotificationsManager.fromBackend(`Failed to save env vars: ${err instanceof Error ? err.message : String(err)}`);
NotificationsManager.fromBackend(
t("mcpTools.userEnvVarsModal.saveFailed", {
error: err instanceof Error ? err.message : String(err),
}),
);
},
});
@ -74,9 +80,9 @@ const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({ server, open, acces
<div>
<div className="flex items-center gap-2">
<Title level={5} style={{ margin: 0 }}>
Set your credentials
{t("mcpTools.userEnvVarsModal.title")}
</Title>
<Tag color="blue">Per-user</Tag>
<Tag color="blue">{t("mcpTools.userEnvVarsModal.perUserTag")}</Tag>
</div>
<Text type="secondary" className="text-xs">
{displayName}
@ -90,16 +96,12 @@ const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({ server, open, acces
<Spin />
</div>
) : isError ? (
<Alert type="error" showIcon message="Failed to load env vars" />
<Alert type="error" showIcon message={t("mcpTools.userEnvVarsModal.loadError")} />
) : required.length === 0 ? (
<Alert type="info" showIcon message="No per-user fields configured for this server." />
<Alert type="info" showIcon message={t("mcpTools.userEnvVarsModal.noFieldsConfigured")} />
) : (
<>
<Text className="text-sm text-gray-600 block">
These values are private to you. Your admin configured this MCP server to require these per-user
credentials. Saved values are never shown back; leave an already-set field blank to keep it, or enter a
value to set or change it.
</Text>
<Text className="text-sm text-gray-600 block">{t("mcpTools.userEnvVarsModal.privacyNote")}</Text>
<Form form={form} layout="vertical" onFinish={handleSave} disabled={isSaving}>
{required.map((spec) => (
<Form.Item
@ -108,15 +110,21 @@ const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({ server, open, acces
label={
<span className="flex items-center gap-2">
<span className="font-mono text-sm font-semibold">{spec.name}</span>
{spec.is_set && <Tag color="green">Set</Tag>}
{spec.is_set && <Tag color="green">{t("mcpTools.userEnvVarsModal.setTag")}</Tag>}
</span>
}
extra={spec.description || undefined}
rules={spec.is_set ? undefined : [{ required: true, message: `${spec.name} is required` }]}
rules={
spec.is_set
? undefined
: [{ required: true, message: t("mcpTools.userEnvVarsModal.fieldRequired", { name: spec.name }) }]
}
>
<Input.Password
placeholder={
spec.is_set ? "Enter a new value to overwrite" : spec.description || `Enter your ${spec.name}`
spec.is_set
? t("mcpTools.userEnvVarsModal.overwritePlaceholder")
: spec.description || t("mcpTools.userEnvVarsModal.enterFieldPlaceholder", { name: spec.name })
}
visibilityToggle
/>
@ -124,10 +132,10 @@ const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({ server, open, acces
))}
<div className="flex items-center justify-end gap-2 pt-2 border-t border-gray-100">
<Button onClick={onClose} disabled={isSaving}>
Cancel
{t("common.cancel")}
</Button>
<Button type="primary" htmlType="submit" loading={isSaving}>
Save Credentials
{t("mcpTools.userEnvVarsModal.saveButton")}
</Button>
</div>
</Form>

View file

@ -3,6 +3,8 @@ import { Modal, Tooltip, Form, Select, Input, Switch, Collapse } from "antd";
import { InfoCircleOutlined } from "@ant-design/icons";
import { Button, TextInput } from "@tremor/react";
import { createMCPServer, registerMCPServer, storeMCPOAuthUserCredential } from "../networking";
import { useTranslation } from "react-i18next";
import { Trans } from "react-i18next";
import { setToken } from "@/utils/mcpTokenStore";
import {
AUTH_TYPE,
@ -70,6 +72,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
onBackToDiscovery,
}) => {
const [form] = Form.useForm();
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const [costConfig, setCostConfig] = useState<MCPServerCostInfo>({});
const [formValues, setFormValues] = useState<Record<string, any>>({});
@ -183,9 +186,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
form.setFieldsValue({ credentials });
NotificationsManager.success(
"OAuth authorization successful! Please click 'Create MCP Server' to save the configuration.",
);
NotificationsManager.success(t("mcpTools.createMcpServer.oauthAuthSuccess"));
}
},
onBeforeRedirect: persistCreateUiState,
@ -372,7 +373,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
console.log("Parsed stdio config:", stdioFields);
} catch (error) {
NotificationsManager.fromBackend("Invalid JSON in stdio configuration");
NotificationsManager.fromBackend(t("mcpTools.createMcpServer.invalidJsonStdio"));
return;
}
}
@ -388,7 +389,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
try {
tokenValidation = JSON.parse(rawTokenValidationJson);
} catch {
NotificationsManager.fromBackend("Invalid JSON in Token Validation Rules");
NotificationsManager.fromBackend(t("mcpTools.createMcpServer.invalidJsonTokenValidation"));
setIsLoading(false);
return;
}
@ -468,7 +469,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
}
NotificationsManager.success(
isAdmin ? "MCP Server created successfully" : "MCP Server submitted for admin review",
isAdmin ? t("mcpTools.createMcpServer.createSuccess") : t("mcpTools.createMcpServer.submitSuccess"),
);
form.resetFields();
setCostConfig({});
@ -483,7 +484,9 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
NotificationsManager.fromBackend(
isAdmin ? `Error creating MCP Server: ${reason}` : `Error submitting MCP Server: ${reason}`,
isAdmin
? t("mcpTools.createMcpServer.createFailed", { reason })
: t("mcpTools.createMcpServer.submitFailed", { reason }),
);
} finally {
setIsLoading(false);
@ -537,7 +540,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<span className="font-medium">{searchValue}</span>
<span className="text-gray-400 text-xs ml-1">create new group</span>
<span className="text-gray-400 text-xs ml-1">{t("mcpTools.createMcpServer.createNewGroup")}</span>
</div>
),
});
@ -587,7 +590,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
)}
<img
src={mcpLogoImg}
alt="MCP Logo"
alt={t("mcpTools.createMcpServer.mcpLogoAlt")}
className="w-8 h-8 object-contain"
style={{
height: "20px",
@ -596,7 +599,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
}}
/>
<h2 className="text-xl font-semibold text-gray-900">
{isAdmin ? "Add New MCP Server" : "Submit MCP Server for Review"}
{isAdmin ? t("mcpTools.createMcpServer.modalTitleAdmin") : t("mcpTools.createMcpServer.modalTitleUser")}
</h2>
</div>
}
@ -621,28 +624,27 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
>
{!isAdmin && (
<div className="rounded-md bg-blue-50 border border-blue-200 px-4 py-3 text-sm text-blue-800">
Your submission will be sent for admin review before it becomes active. Note: the request must be made
with a team-scoped API key.
{t("mcpTools.createMcpServer.nonAdminBanner")}
</div>
)}
<div className="grid grid-cols-1 gap-6">
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
MCP Server Name
<Tooltip title="Best practice: Use a descriptive name that indicates the server's purpose (e.g., 'GitHub_MCP', 'Email_Service'). Cannot contain spaces or hyphens; use underscores instead. Names must comply with SEP-986 and will be rejected if invalid (https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names).">
{t("mcpTools.createMcpServer.serverNameLabel")}
<Tooltip title={t("mcpTools.createMcpServer.serverNameTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
}
name="server_name"
rules={[
{ required: false, message: "Please enter a server name" },
{ required: false, message: t("mcpTools.createMcpServer.serverNameRequired") },
{ validator: (_, value) => validateMCPServerName(value) },
]}
>
<TextInput
placeholder="e.g., GitHub_MCP, Zapier_MCP, etc."
placeholder={t("mcpTools.createMcpServer.serverNamePlaceholder")}
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
@ -650,8 +652,8 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Alias
<Tooltip title="A short, unique identifier for this server. Defaults to the server name if not provided. Cannot contain spaces or hyphens; use underscores instead.">
{t("mcpTools.createMcpServer.aliasLabel")}
<Tooltip title={t("mcpTools.createMcpServer.aliasTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -660,24 +662,24 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
rules={[{ required: false }, { validator: (_, value) => validateMCPServerName(value) }]}
>
<TextInput
placeholder="e.g., GitHub_MCP, Zapier_MCP, etc."
placeholder={t("mcpTools.createMcpServer.serverNamePlaceholder")}
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
onChange={() => setAliasManuallyEdited(true)}
/>
</Form.Item>
<Form.Item
label={<span className="text-sm font-medium text-gray-700">Description</span>}
label={<span className="text-sm font-medium text-gray-700">{t("common.description")}</span>}
name="description"
rules={[
{
required: false,
message: "Please enter a server description",
message: t("mcpTools.createMcpServer.descriptionRequired"),
},
]}
>
<TextInput
placeholder="Brief description of what this server does"
placeholder={t("mcpTools.createMcpServer.descriptionPlaceholder")}
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
@ -685,7 +687,11 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<MCPLogoSelector value={logoUrl} onChange={setLogoUrl} />
<Form.Item
label={<span className="text-sm font-medium text-gray-700">GitHub / Source URL</span>}
label={
<span className="text-sm font-medium text-gray-700">
{t("mcpTools.createMcpServer.sourceUrlLabel")}
</span>
}
name="source_url"
>
<TextInput
@ -695,31 +701,41 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
</Form.Item>
<Form.Item
label={<span className="text-sm font-medium text-gray-700">Transport Type</span>}
label={
<span className="text-sm font-medium text-gray-700">
{t("mcpTools.createMcpServer.transportLabel")}
</span>
}
name="transport"
rules={[{ required: true, message: "Please select a transport type" }]}
rules={[{ required: true, message: t("mcpTools.createMcpServer.transportRequired") }]}
>
<Select
placeholder="Select transport"
placeholder={t("mcpTools.createMcpServer.transportPlaceholder")}
className="rounded-lg"
size="large"
onChange={handleTransportChange}
value={transportType}
>
<Select.Option value="http">Streamable HTTP (Recommended)</Select.Option>
<Select.Option value="sse">Server-Sent Events (SSE)</Select.Option>
<Select.Option value="stdio">Standard Input/Output (stdio)</Select.Option>
<Select.Option value={TRANSPORT.OPENAPI}>OpenAPI Spec</Select.Option>
<Select.Option value="http">{t("mcpTools.createMcpServer.transportHttp")}</Select.Option>
<Select.Option value="sse">{t("mcpTools.createMcpServer.transportSse")}</Select.Option>
<Select.Option value="stdio">{t("mcpTools.createMcpServer.transportStdio")}</Select.Option>
<Select.Option value={TRANSPORT.OPENAPI}>
{t("mcpTools.createMcpServer.transportOpenapi")}
</Select.Option>
</Select>
</Form.Item>
{/* URL field - only show for HTTP and SSE */}
{(transportType === "http" || transportType === "sse") && (
<Form.Item
label={<span className="text-sm font-medium text-gray-700">MCP Server URL</span>}
label={
<span className="text-sm font-medium text-gray-700">
{t("mcpTools.createMcpServer.serverUrlLabel")}
</span>
}
name="url"
rules={[
{ required: true, message: "Please enter a server URL" },
{ required: true, message: t("mcpTools.createMcpServer.serverUrlRequired") },
{ validator: (_, value) => validateMCPServerUrl(value) },
]}
>
@ -748,8 +764,8 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center gap-2">
BYOK (Bring Your Own Key)
<Tooltip title="When enabled, each user provides their own API key for this service. Keys are stored per-user and never shared.">
{t("mcpTools.createMcpServer.byokLabel")}
<Tooltip title={t("mcpTools.createMcpServer.byokTooltip")}>
<InfoCircleOutlined className="text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -772,7 +788,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<div className="mb-4 p-3 bg-blue-50 rounded-lg text-sm text-blue-700 flex items-start gap-2">
<InfoCircleOutlined className="mt-0.5 flex-shrink-0" />
<span>
User keys will be sent as:{" "}
{t("mcpTools.createMcpServer.byokKeysSentAs")}{" "}
<code className="font-mono bg-blue-100 px-1 rounded">
{getFieldValue("auth_type") === "bearer_token" && "Authorization: Bearer {key}"}
{getFieldValue("auth_type") === "token" && "Authorization: token {key}"}
@ -780,7 +796,6 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
{getFieldValue("auth_type") === "basic" && "Authorization: Basic {key}"}
{getFieldValue("auth_type") === "authorization" && "Authorization: {key}"}
</code>
{!getFieldValue("auth_type") && "Set Authentication Type below to specify the format."}
</span>
</div>
)}
@ -788,16 +803,18 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<div className="mb-4 p-3 bg-yellow-50 rounded-lg text-sm text-yellow-700 flex items-start gap-2">
<InfoCircleOutlined className="mt-0.5 flex-shrink-0" />
<span>
Set the <strong>Authentication Type</strong> below to specify how user keys are sent
(e.g., Bearer Token, API Key header).
<Trans
i18nKey="mcpTools.createMcpServer.byokSetAuthType"
components={{ strong: <strong /> }}
/>
</span>
</div>
)}
<Form.Item
label={
<span className="text-sm font-medium text-gray-700">
Access Description
<Tooltip title="List of permissions shown to users in the connection modal (e.g. 'Create and manage Jira issues')">
{t("mcpTools.createMcpServer.byokAccessDescLabel")}
<Tooltip title={t("mcpTools.createMcpServer.byokAccessDescTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -806,7 +823,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
>
<Select
mode="tags"
placeholder="Add access description items (press Enter after each)"
placeholder={t("mcpTools.createMcpServer.byokAccessDescPlaceholder")}
className="w-full"
tokenSeparators={[","]}
/>
@ -815,8 +832,8 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<Form.Item
label={
<span className="text-sm font-medium text-gray-700">
API Key Help URL
<Tooltip title="Optional link shown to users to help them find their API key">
{t("mcpTools.createMcpServer.byokApiKeyHelpUrlLabel")}
<Tooltip title={t("mcpTools.createMcpServer.byokApiKeyHelpUrlTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -840,18 +857,33 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
items={[
{
key: "auth",
label: <span className="text-sm font-semibold text-gray-700">Authentication</span>,
label: (
<span className="text-sm font-semibold text-gray-700">
{t("mcpTools.createMcpServer.authLabel")}
</span>
),
children: (
<>
<Form.Item name="auth_type" rules={[{ required: true, message: "Please select an auth type" }]}>
<Select placeholder="Select auth type" className="rounded-lg" size="large">
<Select.Option value="none">None</Select.Option>
<Select.Option value="api_key">API Key</Select.Option>
<Select.Option value="bearer_token">Bearer Token</Select.Option>
<Select.Option value="token">Token</Select.Option>
<Select.Option value="basic">Basic Auth</Select.Option>
<Select.Option value="oauth2">OAuth</Select.Option>
<Select.Option value="aws_sigv4">AWS SigV4 (Bedrock AgentCore MCPs)</Select.Option>
<Form.Item
name="auth_type"
rules={[{ required: true, message: t("mcpTools.createMcpServer.authTypeRequired") }]}
>
<Select
placeholder={t("mcpTools.createMcpServer.authTypePlaceholder")}
className="rounded-lg"
size="large"
>
<Select.Option value="none">{t("mcpTools.createMcpServer.authNone")}</Select.Option>
<Select.Option value="api_key">{t("mcpTools.createMcpServer.authApiKey")}</Select.Option>
<Select.Option value="bearer_token">
{t("mcpTools.createMcpServer.authBearerToken")}
</Select.Option>
<Select.Option value="token">{t("mcpTools.createMcpServer.authToken")}</Select.Option>
<Select.Option value="basic">{t("mcpTools.createMcpServer.authBasic")}</Select.Option>
<Select.Option value="oauth2">{t("mcpTools.createMcpServer.authOauth")}</Select.Option>
<Select.Option value="aws_sigv4">
{t("mcpTools.createMcpServer.authAwsSigv4")}
</Select.Option>
</Select>
</Form.Item>
@ -859,8 +891,8 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Authentication Value
<Tooltip title="Token, password, or header value to send with each request for the selected auth type.">
{t("mcpTools.createMcpServer.authValueLabel")}
<Tooltip title={t("mcpTools.createMcpServer.authValueTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -870,14 +902,14 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
{
validator: (_, value) =>
value && typeof value === "string" && value.trim() === ""
? Promise.reject(new Error("Authentication value cannot be empty whitespace"))
? Promise.reject(new Error(t("mcpTools.createMcpServer.authValueEmpty")))
: Promise.resolve(),
},
]}
>
<TextInput
type="password"
placeholder="Enter token or secret"
placeholder={t("mcpTools.createMcpServer.authValuePlaceholder")}
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
@ -906,27 +938,31 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
{transportType !== "stdio" && transportType !== "" && isAwsSigV4AuthType && (
<>
<p className="text-sm text-gray-500 mb-2">
For MCP servers hosted on AWS Bedrock AgentCore.{" "}
<a
href="https://docs.litellm.ai/docs/mcp_aws_sigv4"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700"
>
View docs &rarr;
</a>
<Trans
i18nKey="mcpTools.createMcpServer.awsSigv4Desc"
components={{
a: (
<a
href="https://docs.litellm.ai/docs/mcp_aws_sigv4"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700"
/>
),
}}
/>
</p>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
AWS Region
<Tooltip title="AWS region for SigV4 signing (e.g., us-east-1)">
{t("mcpTools.createMcpServer.awsRegionLabel")}
<Tooltip title={t("mcpTools.createMcpServer.awsRegionTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
}
name={["credentials", "aws_region_name"]}
rules={[{ required: true, message: "AWS region is required for SigV4 auth" }]}
rules={[{ required: true, message: t("mcpTools.createMcpServer.awsRegionRequired") }]}
>
<Input
placeholder="us-east-1"
@ -936,8 +972,8 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
AWS Service Name
<Tooltip title="AWS service name for SigV4 signing. Defaults to 'bedrock-agentcore'.">
{t("mcpTools.createMcpServer.awsServiceNameLabel")}
<Tooltip title={t("mcpTools.createMcpServer.awsServiceNameTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -952,8 +988,8 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
AWS Access Key ID
<Tooltip title="Optional. If not provided, falls back to the boto3 credential chain (IAM role, env vars, etc.).">
{t("mcpTools.createMcpServer.awsAccessKeyIdLabel")}
<Tooltip title={t("mcpTools.createMcpServer.awsAccessKeyIdTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -965,9 +1001,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
validator(_, value) {
const secretKey = getFieldValue(["credentials", "aws_secret_access_key"]);
if (secretKey && !value) {
return Promise.reject(
new Error("Access Key ID is required when Secret Access Key is provided"),
);
return Promise.reject(new Error(t("mcpTools.createMcpServer.awsAccessKeyIdRequired")));
}
return Promise.resolve();
},
@ -975,15 +1009,15 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
]}
>
<Input.Password
placeholder="AKIA... (optional — uses IAM role if blank)"
placeholder={t("mcpTools.createMcpServer.awsAccessKeyIdPlaceholder")}
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
AWS Secret Access Key
<Tooltip title="Optional. Required if AWS Access Key ID is provided.">
{t("mcpTools.createMcpServer.awsSecretKeyLabel")}
<Tooltip title={t("mcpTools.createMcpServer.awsSecretKeyTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -995,9 +1029,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
validator(_, value) {
const accessKeyId = getFieldValue(["credentials", "aws_access_key_id"]);
if (accessKeyId && !value) {
return Promise.reject(
new Error("Secret Access Key is required when Access Key ID is provided"),
);
return Promise.reject(new Error(t("mcpTools.createMcpServer.awsSecretKeyRequired")));
}
return Promise.resolve();
},
@ -1005,15 +1037,15 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
]}
>
<Input.Password
placeholder="Enter secret key (optional — uses IAM role if blank)"
placeholder={t("mcpTools.createMcpServer.awsSecretKeyPlaceholder")}
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
AWS Session Token
<Tooltip title="Optional. Only needed for temporary STS credentials.">
{t("mcpTools.createMcpServer.awsSessionTokenLabel")}
<Tooltip title={t("mcpTools.createMcpServer.awsSessionTokenTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -1021,15 +1053,15 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
name={["credentials", "aws_session_token"]}
>
<Input.Password
placeholder="Enter session token (optional)"
placeholder={t("mcpTools.createMcpServer.awsSessionTokenPlaceholder")}
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
AWS Role ARN
<Tooltip title="Optional. IAM role ARN to assume via STS before signing. If set, LiteLLM calls sts:AssumeRole to get temporary credentials. Uses ambient credentials (IAM role, env vars) as the source identity unless explicit keys are also provided.">
{t("mcpTools.createMcpServer.awsRoleArnLabel")}
<Tooltip title={t("mcpTools.createMcpServer.awsRoleArnTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -1037,15 +1069,15 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
name={["credentials", "aws_role_name"]}
>
<Input
placeholder="arn:aws:iam::123456789012:role/MyRole (optional)"
placeholder={t("mcpTools.createMcpServer.awsRoleArnPlaceholder")}
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
AWS Session Name
<Tooltip title="Optional. Session name for the AssumeRole call — appears in CloudTrail logs. Auto-generated if omitted.">
{t("mcpTools.createMcpServer.awsSessionNameLabel")}
<Tooltip title={t("mcpTools.createMcpServer.awsSessionNameTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -1053,7 +1085,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
name={["credentials", "aws_session_name"]}
>
<Input
placeholder="litellm-prod (optional, auto-generated if blank)"
placeholder={t("mcpTools.createMcpServer.awsSessionNamePlaceholder")}
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
@ -1127,10 +1159,10 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<div className="flex items-center justify-end space-x-3 pt-6 border-t border-gray-100">
<Button variant="secondary" onClick={handleCancel}>
Cancel
{t("common.cancel")}
</Button>
<Button variant="primary" loading={isLoading}>
{isLoading ? "Creating..." : "Add MCP Server"}
{isLoading ? t("mcpTools.createMcpServer.creating") : t("mcpTools.createMcpServer.addButton")}
</Button>
</div>
</Form>

View file

@ -3326,6 +3326,100 @@
"required": "Please enter stdio configuration",
"invalidJson": "Please enter valid JSON"
},
"userEnvVarsModal": {
"title": "Set your credentials",
"perUserTag": "Per-user",
"loadError": "Failed to load env vars",
"noFieldsConfigured": "No per-user fields configured for this server.",
"privacyNote": "These values are private to you. Your admin configured this MCP server to require these per-user credentials. Saved values are never shown back; leave an already-set field blank to keep it, or enter a value to set or change it.",
"setTag": "Set",
"fieldRequired": "{{name}} is required",
"overwritePlaceholder": "Enter a new value to overwrite",
"enterFieldPlaceholder": "Enter your {{name}}",
"savedSuccess": "Credentials saved",
"saveFailed": "Failed to save env vars: {{error}}",
"saveButton": "Save Credentials"
},
"createMcpServer": {
"oauthAuthSuccess": "OAuth authorization successful! Please click 'Create MCP Server' to save the configuration.",
"invalidJsonStdio": "Invalid JSON in stdio configuration",
"invalidJsonTokenValidation": "Invalid JSON in Token Validation Rules",
"createSuccess": "MCP Server created successfully",
"submitSuccess": "MCP Server submitted for admin review",
"createFailed": "Error creating MCP Server: {{reason}}",
"submitFailed": "Error submitting MCP Server: {{reason}}",
"createNewGroup": "create new group",
"mcpLogoAlt": "MCP Logo",
"modalTitleAdmin": "Add New MCP Server",
"modalTitleUser": "Submit MCP Server for Review",
"nonAdminBanner": "Your submission will be sent for admin review before it becomes active. Note: the request must be made with a team-scoped API key.",
"serverNameLabel": "MCP Server Name",
"serverNameTooltip": "Best practice: Use a descriptive name that indicates the server's purpose (e.g., 'GitHub_MCP', 'Email_Service'). Cannot contain spaces or hyphens; use underscores instead. Names must comply with SEP-986 and will be rejected if invalid (https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names).",
"serverNameRequired": "Please enter a server name",
"serverNamePlaceholder": "e.g., GitHub_MCP, Zapier_MCP, etc.",
"aliasLabel": "Alias",
"aliasTooltip": "A short, unique identifier for this server. Defaults to the server name if not provided. Cannot contain spaces or hyphens; use underscores instead.",
"descriptionRequired": "Please enter a server description",
"descriptionPlaceholder": "Brief description of what this server does",
"sourceUrlLabel": "GitHub / Source URL",
"transportLabel": "Transport Type",
"transportRequired": "Please select a transport type",
"transportPlaceholder": "Select transport",
"transportHttp": "Streamable HTTP (Recommended)",
"transportSse": "Server-Sent Events (SSE)",
"transportStdio": "Standard Input/Output (stdio)",
"transportOpenapi": "OpenAPI Spec",
"serverUrlLabel": "MCP Server URL",
"serverUrlRequired": "Please enter a server URL",
"byokLabel": "BYOK (Bring Your Own Key)",
"byokTooltip": "When enabled, each user provides their own API key for this service. Keys are stored per-user and never shared.",
"byokKeysSentAs": "User keys will be sent as:",
"byokSetAuthType": "Set the <strong>Authentication Type</strong> below to specify how user keys are sent (e.g., Bearer Token, API Key header).",
"byokAccessDescLabel": "Access Description",
"byokAccessDescTooltip": "List of permissions shown to users in the connection modal (e.g. 'Create and manage Jira issues')",
"byokAccessDescPlaceholder": "Add access description items (press Enter after each)",
"byokApiKeyHelpUrlLabel": "API Key Help URL",
"byokApiKeyHelpUrlTooltip": "Optional link shown to users to help them find their API key",
"authLabel": "Authentication",
"authTypeRequired": "Please select an auth type",
"authTypePlaceholder": "Select auth type",
"authNone": "None",
"authApiKey": "API Key",
"authBearerToken": "Bearer Token",
"authToken": "Token",
"authBasic": "Basic Auth",
"authOauth": "OAuth",
"authAwsSigv4": "AWS SigV4 (Bedrock AgentCore MCPs)",
"authValueLabel": "Authentication Value",
"authValueTooltip": "Token, password, or header value to send with each request for the selected auth type.",
"authValueEmpty": "Authentication value cannot be empty whitespace",
"authValuePlaceholder": "Enter token or secret",
"awsSigv4Desc": "For MCP servers hosted on AWS Bedrock AgentCore. <a>View docs</a>",
"awsRegionLabel": "AWS Region",
"awsRegionTooltip": "AWS region for SigV4 signing (e.g., us-east-1)",
"awsRegionRequired": "AWS region is required for SigV4 auth",
"awsServiceNameLabel": "AWS Service Name",
"awsServiceNameTooltip": "AWS service name for SigV4 signing. Defaults to 'bedrock-agentcore'.",
"awsAccessKeyIdLabel": "AWS Access Key ID",
"awsAccessKeyIdTooltip": "Optional. If not provided, falls back to the boto3 credential chain (IAM role, env vars, etc.).",
"awsAccessKeyIdRequired": "Access Key ID is required when Secret Access Key is provided",
"awsAccessKeyIdPlaceholder": "AKIA... (optional — uses IAM role if blank)",
"awsSecretKeyLabel": "AWS Secret Access Key",
"awsSecretKeyTooltip": "Optional. Required if AWS Access Key ID is provided.",
"awsSecretKeyRequired": "Secret Access Key is required when Access Key ID is provided",
"awsSecretKeyPlaceholder": "Enter secret key (optional — uses IAM role if blank)",
"awsSessionTokenLabel": "AWS Session Token",
"awsSessionTokenTooltip": "Optional. Only needed for temporary STS credentials.",
"awsSessionTokenPlaceholder": "Enter session token (optional)",
"awsRoleArnLabel": "AWS Role ARN",
"awsRoleArnTooltip": "Optional. IAM role ARN to assume via STS before signing. If set, LiteLLM calls sts:AssumeRole to get temporary credentials. Uses ambient credentials (IAM role, env vars) as the source identity unless explicit keys are also provided.",
"awsRoleArnPlaceholder": "arn:aws:iam::123456789012:role/MyRole (optional)",
"awsSessionNameLabel": "AWS Session Name",
"awsSessionNameTooltip": "Optional. Session name for the AssumeRole call — appears in CloudTrail logs. Auto-generated if omitted.",
"awsSessionNamePlaceholder": "litellm-prod (optional, auto-generated if blank)",
"creating": "Creating...",
"addButton": "Add MCP Server"
},
"toolTestPanel": {
"testToolTitle": "Test Tool:",
"clickToCopyToolName": "Click to copy tool name",

View file

@ -3326,6 +3326,100 @@
"required": "请输入 stdio 配置",
"invalidJson": "请输入有效的 JSON"
},
"userEnvVarsModal": {
"title": "设置您的凭据",
"perUserTag": "按用户",
"loadError": "加载环境变量失败",
"noFieldsConfigured": "此服务器未配置按用户字段。",
"privacyNote": "这些值仅对您私有。您的管理员配置了此 MCP 服务器需要这些按用户凭据。已保存的值不会再次显示;已设置的字段留空表示保留原值,输入新值则覆盖。",
"setTag": "已设置",
"fieldRequired": "{{name}} 为必填项",
"overwritePlaceholder": "输入新值以覆盖",
"enterFieldPlaceholder": "输入您的 {{name}}",
"savedSuccess": "凭据已保存",
"saveFailed": "保存环境变量失败:{{error}}",
"saveButton": "保存凭据"
},
"createMcpServer": {
"oauthAuthSuccess": "OAuth 授权成功!请点击「创建 MCP 服务器」保存配置。",
"invalidJsonStdio": "stdio 配置中的 JSON 无效",
"invalidJsonTokenValidation": "Token 验证规则中的 JSON 无效",
"createSuccess": "MCP 服务器创建成功",
"submitSuccess": "MCP 服务器已提交,待管理员审核",
"createFailed": "创建 MCP 服务器时出错:{{reason}}",
"submitFailed": "提交 MCP 服务器时出错:{{reason}}",
"createNewGroup": "创建新组",
"mcpLogoAlt": "MCP Logo",
"modalTitleAdmin": "添加新 MCP 服务器",
"modalTitleUser": "提交 MCP 服务器审核",
"nonAdminBanner": "您的提交将发送给管理员审核后才能生效。注意:请求必须使用团队范围的 API 密钥。",
"serverNameLabel": "MCP 服务器名称",
"serverNameTooltip": "最佳实践:使用描述性名称说明服务器用途(如 'GitHub_MCP'、'Email_Service')。不能包含空格或连字符,请使用下划线。名称须符合 SEP-986,无效名称将被拒绝。",
"serverNameRequired": "请输入服务器名称",
"serverNamePlaceholder": "例如:GitHub_MCP、Zapier_MCP 等",
"aliasLabel": "别名",
"aliasTooltip": "此服务器的简短唯一标识符。未提供时默认使用服务器名称。不能包含空格或连字符,请使用下划线。",
"descriptionRequired": "请输入服务器描述",
"descriptionPlaceholder": "简要描述此服务器的功能",
"sourceUrlLabel": "GitHub / 源码 URL",
"transportLabel": "传输类型",
"transportRequired": "请选择传输类型",
"transportPlaceholder": "选择传输类型",
"transportHttp": "Streamable HTTP(推荐)",
"transportSse": "Server-Sent Events (SSE)",
"transportStdio": "Standard Input/Output (stdio)",
"transportOpenapi": "OpenAPI Spec",
"serverUrlLabel": "MCP 服务器 URL",
"serverUrlRequired": "请输入服务器 URL",
"byokLabel": "BYOK(自带密钥)",
"byokTooltip": "启用后,每位用户为此服务提供自己的 API 密钥。密钥按用户存储,不共享。",
"byokKeysSentAs": "用户密钥发送格式:",
"byokSetAuthType": "请在下方设置<strong>认证类型</strong>,以指定用户密钥的发送方式(如 Bearer Token、API Key 请求头)。",
"byokAccessDescLabel": "访问描述",
"byokAccessDescTooltip": "在连接弹窗中向用户显示的权限列表(例如:'创建和管理 Jira 问题')",
"byokAccessDescPlaceholder": "添加访问描述项(每项后按回车)",
"byokApiKeyHelpUrlLabel": "API 密钥帮助 URL",
"byokApiKeyHelpUrlTooltip": "向用户显示的可选链接,帮助其找到 API 密钥",
"authLabel": "认证",
"authTypeRequired": "请选择认证类型",
"authTypePlaceholder": "选择认证类型",
"authNone": "无",
"authApiKey": "API Key",
"authBearerToken": "Bearer Token",
"authToken": "Token",
"authBasic": "Basic Auth",
"authOauth": "OAuth",
"authAwsSigv4": "AWS SigV4(Bedrock AgentCore MCPs)",
"authValueLabel": "认证值",
"authValueTooltip": "针对所选认证类型,随每次请求发送的 Token、密码或请求头值。",
"authValueEmpty": "认证值不能为纯空格",
"authValuePlaceholder": "输入 Token 或密钥",
"awsSigv4Desc": "适用于托管在 AWS Bedrock AgentCore 上的 MCP 服务器。<a>查看文档</a>",
"awsRegionLabel": "AWS 区域",
"awsRegionTooltip": "SigV4 签名使用的 AWS 区域(如 us-east-1)",
"awsRegionRequired": "SigV4 认证需要提供 AWS 区域",
"awsServiceNameLabel": "AWS 服务名称",
"awsServiceNameTooltip": "SigV4 签名使用的 AWS 服务名称,默认为 'bedrock-agentcore'。",
"awsAccessKeyIdLabel": "AWS Access Key ID",
"awsAccessKeyIdTooltip": "可选。若不提供,则回退到 boto3 凭据链(IAM 角色、环境变量等)。",
"awsAccessKeyIdRequired": "提供 Secret Access Key 时必须同时提供 Access Key ID",
"awsAccessKeyIdPlaceholder": "AKIA...(可选,留空使用 IAM 角色)",
"awsSecretKeyLabel": "AWS Secret Access Key",
"awsSecretKeyTooltip": "可选。提供 AWS Access Key ID 时必须填写。",
"awsSecretKeyRequired": "提供 Access Key ID 时必须同时提供 Secret Access Key",
"awsSecretKeyPlaceholder": "输入密钥(可选,留空使用 IAM 角色)",
"awsSessionTokenLabel": "AWS Session Token",
"awsSessionTokenTooltip": "可选。仅在使用临时 STS 凭据时需要。",
"awsSessionTokenPlaceholder": "输入 Session Token(可选)",
"awsRoleArnLabel": "AWS Role ARN",
"awsRoleArnTooltip": "可选。签名前通过 STS 代入的 IAM 角色 ARN。若设置,LiteLLM 将调用 sts:AssumeRole 获取临时凭据。除非同时提供了显式密钥,否则使用环境凭据(IAM 角色、环境变量)作为源身份。",
"awsRoleArnPlaceholder": "arn:aws:iam::123456789012:role/MyRole(可选)",
"awsSessionNameLabel": "AWS Session Name",
"awsSessionNameTooltip": "可选。AssumeRole 调用的会话名称,出现在 CloudTrail 日志中。若不填写则自动生成。",
"awsSessionNamePlaceholder": "litellm-prod(可选,留空自动生成)",
"creating": "创建中...",
"addButton": "添加 MCP 服务器"
},
"toolTestPanel": {
"testToolTitle": "测试工具:",
"clickToCopyToolName": "点击复制工具名称",