From 0d18f19871f3cb574113a5a2e0befe3beb708efe Mon Sep 17 00:00:00 2001 From: Reidho Satria Date: Thu, 13 Aug 2026 12:53:05 +0700 Subject: [PATCH] Add optional environment variables field to Add MCP server dialog - Add env textarea parsing KEY=VALUE lines in AddMcpDialog - Include env map in POST /mcp/servers body - Add i18n keys (fieldEnv, fieldEnvPlaceholder, envParseError) in all 5 locales - Show environment example in McpDocs YAML - Backend already supports Env field on MCPServer --- web/src/lib/i18n.tsx | 15 +++++++++++++++ web/src/pages/McpPage.tsx | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/web/src/lib/i18n.tsx b/web/src/lib/i18n.tsx index 6acaaa8..4b57ef9 100644 --- a/web/src/lib/i18n.tsx +++ b/web/src/lib/i18n.tsx @@ -203,6 +203,9 @@ const en = { 'mcp.fieldTransport': 'Transport', 'mcp.fieldCommand': 'Command', 'mcp.fieldArgs': 'Arguments', + 'mcp.fieldEnv': 'Environment (Optional)', + 'mcp.fieldEnvPlaceholder': 'KEY=VALUE per line', + 'mcp.envParseError': 'Environment entries must be KEY=VALUE, one per line.', 'mcp.fieldUrl': 'URL', 'plugins.add': 'Add plugin', 'plugins.addTitle': 'Add a plugin', @@ -1218,6 +1221,9 @@ const id: Dict = { 'mcp.fieldTransport': 'Transport', 'mcp.fieldCommand': 'Perintah', 'mcp.fieldArgs': 'Argumen', + 'mcp.fieldEnv': 'Lingkungan (Opsional)', + 'mcp.fieldEnvPlaceholder': 'KEY=VALUE per baris', + 'mcp.envParseError': 'Entri lingkungan harus berupa KEY=VALUE, satu per baris.', 'mcp.fieldUrl': 'URL', 'plugins.add': 'Tambah plugin', 'plugins.addTitle': 'Tambah plugin', @@ -2100,6 +2106,9 @@ const ja: Dict = { 'mcp.fieldTransport': 'トランスポート', 'mcp.fieldCommand': 'コマンド', 'mcp.fieldArgs': '引数', + 'mcp.fieldEnv': '環境変数 (オプション)', + 'mcp.fieldEnvPlaceholder': 'KEY=VALUE を行ごとに', + 'mcp.envParseError': '環境変数は KEY=VALUE の形式で各行に1つずつ指定してください。', 'mcp.fieldUrl': 'URL', 'plugins.add': 'プラグインを追加', 'plugins.addTitle': 'プラグインを追加', @@ -2802,6 +2811,9 @@ const zh: Dict = { 'mcp.fieldTransport': '传输方式', 'mcp.fieldCommand': '命令', 'mcp.fieldArgs': '参数', + 'mcp.fieldEnv': '环境变量 (可选)', + 'mcp.fieldEnvPlaceholder': '每行一个 KEY=VALUE', + 'mcp.envParseError': '环境变量必须为 KEY=VALUE 格式,每行一个。', 'mcp.fieldUrl': 'URL', 'plugins.add': '添加插件', 'plugins.addTitle': '添加插件', @@ -3502,6 +3514,9 @@ const ru: Dict = { 'mcp.fieldTransport': 'Транспорт', 'mcp.fieldCommand': 'Команда', 'mcp.fieldArgs': 'Аргументы', + 'mcp.fieldEnv': 'Переменные среды (опционально)', + 'mcp.fieldEnvPlaceholder': 'KEY=VALUE в каждой строке', + 'mcp.envParseError': 'Переменные среды должны быть в формате KEY=VALUE, по одной на строку.', 'mcp.fieldUrl': 'URL', 'plugins.add': 'Добавить плагин', 'plugins.addTitle': 'Добавить плагин', diff --git a/web/src/pages/McpPage.tsx b/web/src/pages/McpPage.tsx index abb4ca0..fa425ca 100644 --- a/web/src/pages/McpPage.tsx +++ b/web/src/pages/McpPage.tsx @@ -28,6 +28,7 @@ import { Tabs, TabsList, TabsTrigger, + Textarea, } from '@/components/ui/primitives' import { Dialog, @@ -267,12 +268,14 @@ function McpDocs() { transport: stdio command: npx args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/you/data"] + # env: # optional + # MY_VAR: "value" remote: enabled: true transport: http url: https://example.com/mcp headers: - Authorization: "Bearer …"`} + Authorization: "Bearer ..."`}

{t('mcp.docsHint')}

@@ -295,6 +298,7 @@ function AddMcpDialog({ const [command, setCommand] = useState('') const [args, setArgs] = useState('') const [url, setUrl] = useState('') + const [envText, setEnvText] = useState('') const [busy, setBusy] = useState(false) const [error, setError] = useState() @@ -304,6 +308,7 @@ function AddMcpDialog({ setCommand('') setArgs('') setUrl('') + setEnvText('') setError(undefined) } @@ -311,6 +316,17 @@ function AddMcpDialog({ setBusy(true) setError(undefined) try { + const env: Record = {} + for (const line of envText.split('\n')) { + const trimmed = line.trim() + if (!trimmed) continue + const eq = trimmed.indexOf('=') + if (eq <= 0) { + setError(t('mcp.envParseError')) + return + } + env[trimmed.slice(0, eq)] = trimmed.slice(eq + 1) + } const body = transport === 'stdio' ? { @@ -318,8 +334,9 @@ function AddMcpDialog({ transport, command, args: args.split(/\s+/).filter(Boolean), + env, } - : { name, transport, url } + : { name, transport, url, env } const r = await post<{ ok: boolean; error?: string }>('/mcp/servers', body) if (!r.ok) { setError(r.error ?? t('mcp.addFailed')) @@ -418,6 +435,18 @@ function AddMcpDialog({ )} {error ?

{error}

: null} + +
+ +