Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions web/src/lib/i18n.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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': 'プラグインを追加',
Expand Down Expand Up @@ -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': '添加插件',
Expand Down Expand Up @@ -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': 'Добавить плагин',
Expand Down
33 changes: 31 additions & 2 deletions web/src/pages/McpPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
Tabs,
TabsList,
TabsTrigger,
Textarea,
} from '@/components/ui/primitives'
import {
Dialog,
Expand Down Expand Up @@ -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 ..."`}
</pre>
<p className="mt-3 text-xs text-muted-foreground">{t('mcp.docsHint')}</p>
</CardContent>
Expand All @@ -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<string>()

Expand All @@ -304,22 +308,35 @@ function AddMcpDialog({
setCommand('')
setArgs('')
setUrl('')
setEnvText('')
setError(undefined)
}

const submit = async () => {
setBusy(true)
setError(undefined)
try {
const env: Record<string, string> = {}
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'
? {
name,
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'))
Expand Down Expand Up @@ -418,6 +435,18 @@ function AddMcpDialog({
)}

{error ? <p className="text-xs text-destructive">{error}</p> : null}

<div className="grid gap-1.5">
<Label htmlFor="mcp-env">{t('mcp.fieldEnv')}</Label>
<Textarea
id="mcp-env"
value={envText}
onChange={(e) => setEnvText(e.target.value)}
placeholder={t('mcp.fieldEnvPlaceholder')}
className="min-h-[60px] resize-y font-mono text-xs"
rows={3}
/>
</div>
</DialogBody>
<DialogFooter>
<DialogClose asChild>
Expand Down
Loading