Skip to content

Commit 0d18f19

Browse files
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
1 parent 51d860f commit 0d18f19

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

web/src/lib/i18n.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ const en = {
203203
'mcp.fieldTransport': 'Transport',
204204
'mcp.fieldCommand': 'Command',
205205
'mcp.fieldArgs': 'Arguments',
206+
'mcp.fieldEnv': 'Environment (Optional)',
207+
'mcp.fieldEnvPlaceholder': 'KEY=VALUE per line',
208+
'mcp.envParseError': 'Environment entries must be KEY=VALUE, one per line.',
206209
'mcp.fieldUrl': 'URL',
207210
'plugins.add': 'Add plugin',
208211
'plugins.addTitle': 'Add a plugin',
@@ -1218,6 +1221,9 @@ const id: Dict = {
12181221
'mcp.fieldTransport': 'Transport',
12191222
'mcp.fieldCommand': 'Perintah',
12201223
'mcp.fieldArgs': 'Argumen',
1224+
'mcp.fieldEnv': 'Lingkungan (Opsional)',
1225+
'mcp.fieldEnvPlaceholder': 'KEY=VALUE per baris',
1226+
'mcp.envParseError': 'Entri lingkungan harus berupa KEY=VALUE, satu per baris.',
12211227
'mcp.fieldUrl': 'URL',
12221228
'plugins.add': 'Tambah plugin',
12231229
'plugins.addTitle': 'Tambah plugin',
@@ -2100,6 +2106,9 @@ const ja: Dict = {
21002106
'mcp.fieldTransport': 'トランスポート',
21012107
'mcp.fieldCommand': 'コマンド',
21022108
'mcp.fieldArgs': '引数',
2109+
'mcp.fieldEnv': '環境変数 (オプション)',
2110+
'mcp.fieldEnvPlaceholder': 'KEY=VALUE を行ごとに',
2111+
'mcp.envParseError': '環境変数は KEY=VALUE の形式で各行に1つずつ指定してください。',
21032112
'mcp.fieldUrl': 'URL',
21042113
'plugins.add': 'プラグインを追加',
21052114
'plugins.addTitle': 'プラグインを追加',
@@ -2802,6 +2811,9 @@ const zh: Dict = {
28022811
'mcp.fieldTransport': '传输方式',
28032812
'mcp.fieldCommand': '命令',
28042813
'mcp.fieldArgs': '参数',
2814+
'mcp.fieldEnv': '环境变量 (可选)',
2815+
'mcp.fieldEnvPlaceholder': '每行一个 KEY=VALUE',
2816+
'mcp.envParseError': '环境变量必须为 KEY=VALUE 格式,每行一个。',
28052817
'mcp.fieldUrl': 'URL',
28062818
'plugins.add': '添加插件',
28072819
'plugins.addTitle': '添加插件',
@@ -3502,6 +3514,9 @@ const ru: Dict = {
35023514
'mcp.fieldTransport': 'Транспорт',
35033515
'mcp.fieldCommand': 'Команда',
35043516
'mcp.fieldArgs': 'Аргументы',
3517+
'mcp.fieldEnv': 'Переменные среды (опционально)',
3518+
'mcp.fieldEnvPlaceholder': 'KEY=VALUE в каждой строке',
3519+
'mcp.envParseError': 'Переменные среды должны быть в формате KEY=VALUE, по одной на строку.',
35053520
'mcp.fieldUrl': 'URL',
35063521
'plugins.add': 'Добавить плагин',
35073522
'plugins.addTitle': 'Добавить плагин',

web/src/pages/McpPage.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
Tabs,
2929
TabsList,
3030
TabsTrigger,
31+
Textarea,
3132
} from '@/components/ui/primitives'
3233
import {
3334
Dialog,
@@ -267,12 +268,14 @@ function McpDocs() {
267268
transport: stdio
268269
command: npx
269270
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/you/data"]
271+
# env: # optional
272+
# MY_VAR: "value"
270273
remote:
271274
enabled: true
272275
transport: http
273276
url: https://example.com/mcp
274277
headers:
275-
Authorization: "Bearer "`}
278+
Authorization: "Bearer ..."`}
276279
</pre>
277280
<p className="mt-3 text-xs text-muted-foreground">{t('mcp.docsHint')}</p>
278281
</CardContent>
@@ -295,6 +298,7 @@ function AddMcpDialog({
295298
const [command, setCommand] = useState('')
296299
const [args, setArgs] = useState('')
297300
const [url, setUrl] = useState('')
301+
const [envText, setEnvText] = useState('')
298302
const [busy, setBusy] = useState(false)
299303
const [error, setError] = useState<string>()
300304

@@ -304,22 +308,35 @@ function AddMcpDialog({
304308
setCommand('')
305309
setArgs('')
306310
setUrl('')
311+
setEnvText('')
307312
setError(undefined)
308313
}
309314

310315
const submit = async () => {
311316
setBusy(true)
312317
setError(undefined)
313318
try {
319+
const env: Record<string, string> = {}
320+
for (const line of envText.split('\n')) {
321+
const trimmed = line.trim()
322+
if (!trimmed) continue
323+
const eq = trimmed.indexOf('=')
324+
if (eq <= 0) {
325+
setError(t('mcp.envParseError'))
326+
return
327+
}
328+
env[trimmed.slice(0, eq)] = trimmed.slice(eq + 1)
329+
}
314330
const body =
315331
transport === 'stdio'
316332
? {
317333
name,
318334
transport,
319335
command,
320336
args: args.split(/\s+/).filter(Boolean),
337+
env,
321338
}
322-
: { name, transport, url }
339+
: { name, transport, url, env }
323340
const r = await post<{ ok: boolean; error?: string }>('/mcp/servers', body)
324341
if (!r.ok) {
325342
setError(r.error ?? t('mcp.addFailed'))
@@ -418,6 +435,18 @@ function AddMcpDialog({
418435
)}
419436

420437
{error ? <p className="text-xs text-destructive">{error}</p> : null}
438+
439+
<div className="grid gap-1.5">
440+
<Label htmlFor="mcp-env">{t('mcp.fieldEnv')}</Label>
441+
<Textarea
442+
id="mcp-env"
443+
value={envText}
444+
onChange={(e) => setEnvText(e.target.value)}
445+
placeholder={t('mcp.fieldEnvPlaceholder')}
446+
className="min-h-[60px] resize-y font-mono text-xs"
447+
rows={3}
448+
/>
449+
</div>
421450
</DialogBody>
422451
<DialogFooter>
423452
<DialogClose asChild>

0 commit comments

Comments
 (0)