cli-jaw 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +8 -6
- package/README.ko.md +8 -6
- package/README.md +12 -6
- package/README.zh-CN.md +8 -6
- package/dist/bin/cli-jaw.js +5 -1
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/browser.js +1 -1
- package/dist/bin/commands/browser.js.map +1 -1
- package/dist/bin/commands/chat.js +98 -58
- package/dist/bin/commands/chat.js.map +1 -1
- package/dist/bin/commands/dispatch.js +130 -5
- package/dist/bin/commands/dispatch.js.map +1 -1
- package/dist/bin/commands/goal.js +18 -6
- package/dist/bin/commands/goal.js.map +1 -1
- package/dist/bin/commands/orchestrate.js.map +1 -1
- package/dist/bin/commands/tui/fullscreen-mode.js +237 -0
- package/dist/bin/commands/tui/fullscreen-mode.js.map +1 -0
- package/dist/bin/commands/tui/input-handler.js +139 -52
- package/dist/bin/commands/tui/input-handler.js.map +1 -1
- package/dist/bin/commands/tui/overlays.js +177 -70
- package/dist/bin/commands/tui/overlays.js.map +1 -1
- package/dist/bin/commands/tui/renderer.js +70 -20
- package/dist/bin/commands/tui/renderer.js.map +1 -1
- package/dist/bin/commands/tui/simple-mode.js +1 -1
- package/dist/bin/commands/tui/simple-mode.js.map +1 -1
- package/dist/bin/commands/tui/tui-io.js +8 -0
- package/dist/bin/commands/tui/tui-io.js.map +1 -0
- package/dist/bin/commands/tui/types.js +9 -0
- package/dist/bin/commands/tui/types.js.map +1 -1
- package/dist/bin/commands/tui/ws-handler.js +115 -23
- package/dist/bin/commands/tui/ws-handler.js.map +1 -1
- package/dist/bin/commands/worker.js +159 -0
- package/dist/bin/commands/worker.js.map +1 -0
- package/dist/bin/postinstall.js +0 -76
- package/dist/bin/postinstall.js.map +1 -1
- package/dist/lib/mcp/format-converters.js +21 -2
- package/dist/lib/mcp/format-converters.js.map +1 -1
- package/dist/lib/mcp/mcp-registry.js +75 -0
- package/dist/lib/mcp/mcp-registry.js.map +1 -0
- package/dist/lib/stt.js +1 -1
- package/dist/lib/stt.js.map +1 -1
- package/dist/scripts/fresh-install-smoke.js +1 -1
- package/dist/scripts/fresh-install-smoke.js.map +1 -1
- package/dist/server.js +27 -6
- package/dist/server.js.map +1 -1
- package/dist/src/agent/args.js +33 -1
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/claude-e-runtime.js +1 -1
- package/dist/src/agent/claude-e-runtime.js.map +1 -1
- package/dist/src/agent/codex-app-client.js +1 -1
- package/dist/src/agent/error-classifier.js +6 -1
- package/dist/src/agent/error-classifier.js.map +1 -1
- package/dist/src/agent/events/claude.js +1 -0
- package/dist/src/agent/events/claude.js.map +1 -1
- package/dist/src/agent/events/codex.js +0 -44
- package/dist/src/agent/events/codex.js.map +1 -1
- package/dist/src/agent/events/helpers.js +52 -5
- package/dist/src/agent/events/helpers.js.map +1 -1
- package/dist/src/agent/events/opencode.js +6 -2
- package/dist/src/agent/events/opencode.js.map +1 -1
- package/dist/src/agent/events/summary.js +3 -2
- package/dist/src/agent/events/summary.js.map +1 -1
- package/dist/src/agent/events/tool-labels.js +8 -5
- package/dist/src/agent/events/tool-labels.js.map +1 -1
- package/dist/src/agent/kiro-auth.js +213 -0
- package/dist/src/agent/kiro-auth.js.map +1 -0
- package/dist/src/agent/kiro-models.js +79 -0
- package/dist/src/agent/kiro-models.js.map +1 -0
- package/dist/src/agent/kiro-runtime.js +306 -0
- package/dist/src/agent/kiro-runtime.js.map +1 -0
- package/dist/src/agent/lifecycle-handler.js +190 -12
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/memory-flush-controller.js +1 -3
- package/dist/src/agent/memory-flush-controller.js.map +1 -1
- package/dist/src/agent/resume-classifier.js +18 -1
- package/dist/src/agent/resume-classifier.js.map +1 -1
- package/dist/src/agent/session-persistence.js +1 -1
- package/dist/src/agent/session-persistence.js.map +1 -1
- package/dist/src/agent/smoke-detector.js +3 -0
- package/dist/src/agent/smoke-detector.js.map +1 -1
- package/dist/src/agent/spawn/queue.js +10 -4
- package/dist/src/agent/spawn/queue.js.map +1 -1
- package/dist/src/agent/spawn/resume.js +6 -1
- package/dist/src/agent/spawn/resume.js.map +1 -1
- package/dist/src/agent/spawn-env.js +1 -1
- package/dist/src/agent/spawn-env.js.map +1 -1
- package/dist/src/agent/spawn.js +188 -28
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/browser/web-ai/grok-live.js +2 -0
- package/dist/src/browser/web-ai/grok-live.js.map +1 -1
- package/dist/src/cli/claude-models.js +2 -2
- package/dist/src/cli/claude-models.js.map +1 -1
- package/dist/src/cli/commands.js +3 -2
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers-runtime.js +2 -0
- package/dist/src/cli/handlers-runtime.js.map +1 -1
- package/dist/src/cli/handlers-workflows.js +9 -4
- package/dist/src/cli/handlers-workflows.js.map +1 -1
- package/dist/src/cli/handlers.js +47 -7
- package/dist/src/cli/handlers.js.map +1 -1
- package/dist/src/cli/readiness.js +17 -1
- package/dist/src/cli/readiness.js.map +1 -1
- package/dist/src/cli/registry-live.js +17 -0
- package/dist/src/cli/registry-live.js.map +1 -0
- package/dist/src/cli/registry.js +44 -3
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/cli/tui/composer.js +135 -5
- package/dist/src/cli/tui/composer.js.map +1 -1
- package/dist/src/cli/tui/diffview.js +41 -0
- package/dist/src/cli/tui/diffview.js.map +1 -0
- package/dist/src/cli/tui/editor.js +45 -0
- package/dist/src/cli/tui/editor.js.map +1 -0
- package/dist/src/cli/tui/file-mention.js +78 -0
- package/dist/src/cli/tui/file-mention.js.map +1 -0
- package/dist/src/cli/tui/highlight.js +82 -0
- package/dist/src/cli/tui/highlight.js.map +1 -0
- package/dist/src/cli/tui/keymap.js +21 -0
- package/dist/src/cli/tui/keymap.js.map +1 -1
- package/dist/src/cli/tui/markdown.js +124 -0
- package/dist/src/cli/tui/markdown.js.map +1 -0
- package/dist/src/cli/tui/mode.js +26 -0
- package/dist/src/cli/tui/mode.js.map +1 -0
- package/dist/src/cli/tui/overlay.js +121 -98
- package/dist/src/cli/tui/overlay.js.map +1 -1
- package/dist/src/cli/tui/render/frame.js +72 -0
- package/dist/src/cli/tui/render/frame.js.map +1 -0
- package/dist/src/cli/tui/render/layout.js +22 -0
- package/dist/src/cli/tui/render/layout.js.map +1 -0
- package/dist/src/cli/tui/render/mouse.js +25 -0
- package/dist/src/cli/tui/render/mouse.js.map +1 -0
- package/dist/src/cli/tui/render/scheduler.js +37 -0
- package/dist/src/cli/tui/render/scheduler.js.map +1 -0
- package/dist/src/cli/tui/render/viewport.js +82 -0
- package/dist/src/cli/tui/render/viewport.js.map +1 -0
- package/dist/src/cli/tui/renderers.js +37 -0
- package/dist/src/cli/tui/renderers.js.map +1 -1
- package/dist/src/cli/tui/stream.js +48 -0
- package/dist/src/cli/tui/stream.js.map +1 -0
- package/dist/src/cli/tui/text-buffer.js +143 -0
- package/dist/src/cli/tui/text-buffer.js.map +1 -0
- package/dist/src/cli/tui/theme.js +128 -0
- package/dist/src/cli/tui/theme.js.map +1 -0
- package/dist/src/cli/tui/transcript.js +4 -0
- package/dist/src/cli/tui/transcript.js.map +1 -1
- package/dist/src/command-contract/help-renderer.js +1 -1
- package/dist/src/command-contract/help-renderer.js.map +1 -1
- package/dist/src/core/cli-detection.js +14 -0
- package/dist/src/core/cli-detection.js.map +1 -1
- package/dist/src/core/config.js +5 -0
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +7 -0
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/employees.js +2 -2
- package/dist/src/core/employees.js.map +1 -1
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/discord-file.js +1 -1
- package/dist/src/discord/discord-file.js.map +1 -1
- package/dist/src/discord/send-only-client.js +1 -3
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/goal/heartbeat.js +30 -7
- package/dist/src/goal/heartbeat.js.map +1 -1
- package/dist/src/goal/store.js +6 -2
- package/dist/src/goal/store.js.map +1 -1
- package/dist/src/ide/diff.js +37 -0
- package/dist/src/ide/diff.js.map +1 -1
- package/dist/src/manager/launchd-service.js +1 -1
- package/dist/src/manager/launchd-service.js.map +1 -1
- package/dist/src/manager/lifecycle-helpers.js +14 -0
- package/dist/src/manager/lifecycle-helpers.js.map +1 -1
- package/dist/src/manager/lifecycle.js +49 -4
- package/dist/src/manager/lifecycle.js.map +1 -1
- package/dist/src/manager/notes/routes.js +0 -31
- package/dist/src/manager/notes/routes.js.map +1 -1
- package/dist/src/manager/notes/ws.js.map +1 -1
- package/dist/src/manager/registry.js.map +1 -1
- package/dist/src/manager/routes/dashboard-memory.js.map +1 -1
- package/dist/src/manager/scan.js +36 -1
- package/dist/src/manager/scan.js.map +1 -1
- package/dist/src/manager/server.js +21 -2
- package/dist/src/manager/server.js.map +1 -1
- package/dist/src/manager/systemd-service.js +1 -1
- package/dist/src/manager/systemd-service.js.map +1 -1
- package/dist/src/memory/keyword-expand.js +0 -187
- package/dist/src/memory/keyword-expand.js.map +1 -1
- package/dist/src/memory/runtime.js +2 -2
- package/dist/src/memory/runtime.js.map +1 -1
- package/dist/src/memory/shared.js.map +1 -1
- package/dist/src/orchestrator/collect.js +3 -2
- package/dist/src/orchestrator/collect.js.map +1 -1
- package/dist/src/orchestrator/distribute.js +7 -1
- package/dist/src/orchestrator/distribute.js.map +1 -1
- package/dist/src/orchestrator/friction.js +59 -0
- package/dist/src/orchestrator/friction.js.map +1 -0
- package/dist/src/orchestrator/pipeline.js +88 -45
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/sanitize.js +19 -0
- package/dist/src/orchestrator/sanitize.js.map +1 -0
- package/dist/src/orchestrator/seed.js +50 -0
- package/dist/src/orchestrator/seed.js.map +1 -0
- package/dist/src/orchestrator/state-machine.js +206 -74
- package/dist/src/orchestrator/state-machine.js.map +1 -1
- package/dist/src/orchestrator/worker-monitor.js +1 -1
- package/dist/src/orchestrator/worker-monitor.js.map +1 -1
- package/dist/src/orchestrator/worker-progress.js +29 -0
- package/dist/src/orchestrator/worker-progress.js.map +1 -0
- package/dist/src/orchestrator/worker-registry.js +63 -2
- package/dist/src/orchestrator/worker-registry.js.map +1 -1
- package/dist/src/prompt/builder.js +19 -29
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +40 -5
- package/dist/src/prompt/templates/control-system.md +2 -2
- package/dist/src/prompt/templates/employee.md +4 -1
- package/dist/src/prompt/templates/orchestration.md +23 -8
- package/dist/src/routes/employees.js +4 -4
- package/dist/src/routes/employees.js.map +1 -1
- package/dist/src/routes/goal.js +11 -3
- package/dist/src/routes/goal.js.map +1 -1
- package/dist/src/routes/heartbeat.js +1 -1
- package/dist/src/routes/heartbeat.js.map +1 -1
- package/dist/src/routes/i18n.js +1 -1
- package/dist/src/routes/i18n.js.map +1 -1
- package/dist/src/routes/jaw-memory.js +1 -1
- package/dist/src/routes/jaw-memory.js.map +1 -1
- package/dist/src/routes/memory.js.map +1 -1
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/orchestrate.js +265 -116
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/quota-agy-reverse.js +0 -5
- package/dist/src/routes/quota-agy-reverse.js.map +1 -1
- package/dist/src/routes/quota-kiro-reverse.js +187 -0
- package/dist/src/routes/quota-kiro-reverse.js.map +1 -0
- package/dist/src/routes/settings.js +31 -2
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/routes/skills.js.map +1 -1
- package/dist/src/security/security-audit-log.js.map +1 -1
- package/dist/src/shared/shell-command-display.js +51 -0
- package/dist/src/shared/shell-command-display.js.map +1 -0
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/trace/store.js +32 -0
- package/dist/src/trace/store.js.map +1 -1
- package/dist/src/types/cli-engine.js +1 -0
- package/dist/src/types/cli-engine.js.map +1 -1
- package/dist/src/workflows/employee-boundary.js +7 -7
- package/dist/src/workflows/employee-boundary.js.map +1 -1
- package/dist/src/workflows/handoff.js +16 -16
- package/dist/src/workflows/handoff.js.map +1 -1
- package/dist/src/workflows/scope-sandbox.js +46 -0
- package/dist/src/workflows/scope-sandbox.js.map +1 -0
- package/package.json +1 -1
- package/public/assets/providers/kiro-color.svg +15 -0
- package/public/assets/providers/kiro.svg +14 -0
- package/public/css/modals.css +79 -0
- package/public/css/orc-state.css +21 -2
- package/public/css/sidebar.css +85 -0
- package/public/css/variables.css +2 -2
- package/public/dist/assets/{Agent-CIUGaUVn.js → Agent-DkH7eoHd.js} +1 -1
- package/public/dist/assets/{DocPanel-C1pGgE-S.js → DocPanel-BU16GUlB.js} +1 -1
- package/public/dist/assets/DocPanel-gU-WkgyA.css +1 -0
- package/public/dist/assets/Employees-F0ssNuO-.js +1 -0
- package/public/dist/assets/Heartbeat-C3JS6gkF.js +1 -0
- package/public/dist/assets/Mcp-_Yq4N3Sk.js +4 -0
- package/public/dist/assets/{Memory-BgN8djV4.js → Memory-C-EQubN2.js} +1 -1
- package/public/dist/assets/{ModelProvider-DHQ1Zvw0.js → ModelProvider-BD2KrypI.js} +1 -1
- package/public/dist/assets/agent-meta-B1098B_a.js +1 -0
- package/public/dist/assets/app-ByHYOMZE.js +48 -0
- package/public/dist/assets/app-CYdhP6Vh.css +1 -0
- package/public/dist/assets/constants-s2UJodER.js +1 -0
- package/public/dist/assets/{employees-DZSPGlaH.js → employees-_A-p_bZg.js} +7 -7
- package/public/dist/assets/idb-cache-0LNMskFB.js +1 -0
- package/public/dist/assets/idb-cache-5H89a4l8.js +1 -0
- package/public/dist/assets/{manager-DyrwJLZr.css → manager-B2qEQRxN.css} +1 -1
- package/public/dist/assets/manager-CR9BA-wO.js +12 -0
- package/public/dist/assets/memory-D1RKYvyu.js +1 -0
- package/public/dist/assets/{memory-B4RpXXJ7.js → memory-D9AUn8fG.js} +9 -9
- package/public/dist/assets/{provider-icons-XEfJPGTe.js → provider-icons-CVVK5xUP.js} +35 -6
- package/public/dist/assets/{render-DnylWKO1.js → render-DFBujF8n.js} +4 -4
- package/public/dist/assets/settings-B4ZkeaU-.js +1 -0
- package/public/dist/assets/settings-D9jTceN0.js +151 -0
- package/public/dist/assets/sidebar-DPPRNiTc.js +48 -0
- package/public/dist/assets/skills-8nHJkv-r.js +1 -0
- package/public/dist/assets/{skills-CW7NEhew.js → skills-BDVLIrVT.js} +6 -6
- package/public/dist/assets/{slash-commands-CAydTUgf.js → slash-commands-C5da3q1p.js} +1 -1
- package/public/dist/assets/slash-commands-RJWO8wJZ.js +1 -0
- package/public/dist/assets/{trace-drawer-G-KP3IbQ.js → trace-drawer-5kqBzFMk.js} +1 -1
- package/public/dist/assets/ui-Crnp79bG.js +142 -0
- package/public/dist/assets/ui-HtSKByR3.js +1 -0
- package/public/dist/index.html +113 -86
- package/public/dist/manager/index.html +2 -2
- package/public/index.html +111 -84
- package/public/js/constants.ts +30 -7
- package/public/js/features/chat-search.ts +5 -1
- package/public/js/features/employees.ts +7 -2
- package/public/js/features/idb-cache.ts +4 -0
- package/public/js/features/message-history.ts +55 -7
- package/public/js/features/pending-queue.ts +8 -2
- package/public/js/features/process-block.ts +32 -17
- package/public/js/features/process-log-adapter.ts +11 -2
- package/public/js/features/settings-cli-status-render.ts +163 -0
- package/public/js/features/settings-cli-status.ts +130 -185
- package/public/js/features/settings-core.ts +5 -1
- package/public/js/features/settings-mcp.ts +513 -7
- package/public/js/features/settings.ts +1 -1
- package/public/js/features/tool-ui.ts +18 -4
- package/public/js/features/transport-status-row.ts +3 -0
- package/public/js/main.ts +3 -0
- package/public/js/preview-parent-origin.ts +14 -0
- package/public/js/provider-icons.ts +6 -1
- package/public/js/render/file-links.ts +10 -2
- package/public/js/ui.ts +5 -2
- package/public/js/virtual-scroll.ts +19 -0
- package/public/js/ws.ts +163 -10
- package/public/locales/en.json +8 -2
- package/public/locales/ja.json +8 -2
- package/public/locales/ko.json +8 -2
- package/public/locales/zh.json +8 -2
- package/public/manager/src/App.tsx +13 -70
- package/public/manager/src/InstancePreview.tsx +36 -11
- package/public/manager/src/SidebarRailRouter.tsx +1 -1
- package/public/manager/src/doc-panel/DocPanel.tsx +14 -1
- package/public/manager/src/doc-panel/doc-panel.css +16 -0
- package/public/manager/src/hooks/useInstanceMessageEvents.ts +4 -15
- package/public/manager/src/manager-components.css +1 -1
- package/public/manager/src/manager-polish.css +1 -1
- package/public/manager/src/manager-shortcut-runner.ts +107 -0
- package/public/manager/src/manager-shortcuts.ts +15 -0
- package/public/manager/src/panels/desktop-bridge.ts +2 -0
- package/public/manager/src/settings/SettingsSidebar.tsx +1 -1
- package/public/manager/src/settings/components/sidebar-filter.ts +2 -0
- package/public/manager/src/settings/pages/Mcp.tsx +311 -43
- package/public/manager/src/settings/pages/components/McpServerCard.tsx +89 -55
- package/public/manager/src/settings/pages/components/agent/agent-meta.ts +29 -6
- package/public/manager/src/settings/pages/components/employees-helpers.ts +1 -0
- package/public/manager/src/settings/pages/components/heartbeat-helpers.ts +1 -0
- package/public/manager/src/settings/pages/mcp-helpers.ts +54 -12
- package/public/manager/src/settings/types.ts +1 -0
- package/public/manager/src/types.ts +2 -1
- package/scripts/fresh-install-smoke.ts +1 -1
- package/public/dist/assets/DocPanel-CL1scIfq.css +0 -1
- package/public/dist/assets/Employees-BL9MAzzx.js +0 -1
- package/public/dist/assets/Heartbeat-D64JCg4t.js +0 -1
- package/public/dist/assets/Mcp-wYwJA_61.js +0 -3
- package/public/dist/assets/agent-meta-BhEbjy4P.js +0 -1
- package/public/dist/assets/app-DLDdZ41k.js +0 -48
- package/public/dist/assets/app-RpnKs-sT.css +0 -1
- package/public/dist/assets/constants-ahKI_aEG.js +0 -1
- package/public/dist/assets/idb-cache-BnZfG5FD.js +0 -1
- package/public/dist/assets/idb-cache-CZ3JdK8r.js +0 -1
- package/public/dist/assets/manager-CfyZloIP.js +0 -12
- package/public/dist/assets/memory-C9EVOb_D.js +0 -1
- package/public/dist/assets/settings-DIG_i79X.js +0 -1
- package/public/dist/assets/settings-VCXKhhhz.js +0 -66
- package/public/dist/assets/sidebar-Bi_ktFVw.js +0 -18
- package/public/dist/assets/skills-RjRqvykb.js +0 -1
- package/public/dist/assets/slash-commands-DdXu96Zd.js +0 -1
- package/public/dist/assets/ui-CIQjTLzc.js +0 -1
- package/public/dist/assets/ui-CkHB9Jmo.js +0 -140
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
// Phase 8 — MCP page: structured editor for server cards plus an Advanced
|
|
2
|
-
// JSON view, plus action buttons for sync/install/reset against the
|
|
3
|
-
// unified MCP config (`/api/mcp`).
|
|
4
|
-
//
|
|
5
|
-
// The page owns one synthetic dirty-store key (`mcp.config`) carrying the
|
|
6
|
-
// full normalized config. Save PUTs back with `toPersistShape` to keep the
|
|
7
|
-
// on-disk JSON minimal. Render-time validation (duplicate names, missing
|
|
8
|
-
// commands, malformed env) gates the dirty entry's `valid` flag — invalid
|
|
9
|
-
// configs cannot save.
|
|
10
|
-
|
|
11
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
2
|
import type { SettingsPageProps } from '../types';
|
|
13
3
|
import { JsonEditorField } from '../fields';
|
|
@@ -21,7 +11,9 @@ import {
|
|
|
21
11
|
import { InlineWarn } from './components/InlineWarn';
|
|
22
12
|
import { McpServerCard } from './components/McpServerCard';
|
|
23
13
|
import {
|
|
14
|
+
countInstallBundleCandidates,
|
|
24
15
|
findDuplicateNames,
|
|
16
|
+
getServerTag,
|
|
25
17
|
makeEmptyServer,
|
|
26
18
|
newServerName,
|
|
27
19
|
normalizeMcpConfig,
|
|
@@ -46,6 +38,9 @@ type SyncResultsPayload = {
|
|
|
46
38
|
servers?: unknown;
|
|
47
39
|
};
|
|
48
40
|
|
|
41
|
+
type ModalTab = 'active' | 'add';
|
|
42
|
+
type AddSubTab = 'local' | 'remote';
|
|
43
|
+
|
|
49
44
|
export default function Mcp({ port, client, dirty, registerSave }: SettingsPageProps) {
|
|
50
45
|
const { state, refresh, setData } = usePageSnapshot<unknown>(client, '/api/mcp');
|
|
51
46
|
const [draft, setDraft] = useState<McpConfig>({ servers: {} });
|
|
@@ -55,6 +50,18 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
55
50
|
const [actionState, setActionState] = useState<ActionResult>({ kind: 'idle' });
|
|
56
51
|
const actionRunIdRef = useRef(0);
|
|
57
52
|
|
|
53
|
+
const [modalOpen, setModalOpen] = useState(false);
|
|
54
|
+
const [modalTab, setModalTab] = useState<ModalTab>('active');
|
|
55
|
+
const [addSubTab, setAddSubTab] = useState<AddSubTab>('local');
|
|
56
|
+
const [addName, setAddName] = useState('');
|
|
57
|
+
const [addCommand, setAddCommand] = useState('');
|
|
58
|
+
const [addArgs, setAddArgs] = useState('');
|
|
59
|
+
const [addEnv, setAddEnv] = useState('');
|
|
60
|
+
const [addUrl, setAddUrl] = useState('');
|
|
61
|
+
const [addHeaders, setAddHeaders] = useState('');
|
|
62
|
+
const [addError, setAddError] = useState<string | null>(null);
|
|
63
|
+
const [addPending, setAddPending] = useState(false);
|
|
64
|
+
|
|
58
65
|
const original = useMemo<McpConfig>(
|
|
59
66
|
() => (state.kind === 'ready' ? normalizeMcpConfig(state.data) : { servers: {} }),
|
|
60
67
|
[state],
|
|
@@ -69,9 +76,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
69
76
|
}, [state]);
|
|
70
77
|
|
|
71
78
|
useEffect(() => {
|
|
72
|
-
return () => {
|
|
73
|
-
dirty.remove(DIRTY_KEY);
|
|
74
|
-
};
|
|
79
|
+
return () => { dirty.remove(DIRTY_KEY); };
|
|
75
80
|
}, [dirty]);
|
|
76
81
|
|
|
77
82
|
const names = useMemo(() => Object.keys(draft.servers), [draft]);
|
|
@@ -85,6 +90,10 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
85
90
|
const hasFieldErrors = validations.some((v) => v.result.kind === 'invalid');
|
|
86
91
|
const hasDupes = duplicates.size > 0;
|
|
87
92
|
const isValid = !hasFieldErrors && !hasDupes && advancedValid;
|
|
93
|
+
const bundleCandidates = useMemo(
|
|
94
|
+
() => countInstallBundleCandidates(draft.servers),
|
|
95
|
+
[draft],
|
|
96
|
+
);
|
|
88
97
|
|
|
89
98
|
const writeDirty = useCallback(
|
|
90
99
|
(next: McpConfig, valid: boolean) => {
|
|
@@ -101,7 +110,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
101
110
|
(next: McpConfig, nextOrder?: string[]) => {
|
|
102
111
|
setDraft(next);
|
|
103
112
|
if (nextOrder) setOrder(nextOrder);
|
|
104
|
-
// Recompute validity on the next draft, not the stale `isValid`.
|
|
105
113
|
const ns = nextOrder ?? Object.keys(next.servers);
|
|
106
114
|
const dupes = findDuplicateNames(ns);
|
|
107
115
|
const fieldOk = ns.every(
|
|
@@ -115,8 +123,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
115
123
|
const onRenameServer = useCallback(
|
|
116
124
|
(oldName: string, nextName: string) => {
|
|
117
125
|
if (oldName === nextName) return;
|
|
118
|
-
// Preserve insertion order; keep the same card in place even if the
|
|
119
|
-
// new name collides with a later card (validation surfaces the dupe).
|
|
120
126
|
const nextOrder = order.map((n) => (n === oldName ? nextName : n));
|
|
121
127
|
const nextServers: Record<string, McpServer> = {};
|
|
122
128
|
for (const n of nextOrder) {
|
|
@@ -185,9 +191,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
185
191
|
if (!(DIRTY_KEY in bundle)) return;
|
|
186
192
|
const body = bundle[DIRTY_KEY];
|
|
187
193
|
const updated = await client.put<unknown>('/api/mcp', body);
|
|
188
|
-
// /api/mcp PUT returns `{ ok, servers: string[] }` — re-fetch to get the
|
|
189
|
-
// canonical persisted shape rather than reusing the (possibly trimmed)
|
|
190
|
-
// request body.
|
|
191
194
|
dirty.clear();
|
|
192
195
|
const fresh = await client.get<unknown>('/api/mcp').catch(() => updated);
|
|
193
196
|
const normalized = normalizeMcpConfig(fresh);
|
|
@@ -211,9 +214,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
211
214
|
!window.confirm(
|
|
212
215
|
`You have unsaved MCP edits. ${label} will use the on-disk config, not your unsaved changes. Continue?`,
|
|
213
216
|
)
|
|
214
|
-
) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
+
) { return; }
|
|
217
218
|
}
|
|
218
219
|
const runId = actionRunIdRef.current + 1;
|
|
219
220
|
actionRunIdRef.current = runId;
|
|
@@ -236,6 +237,94 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
236
237
|
[client, dirty],
|
|
237
238
|
);
|
|
238
239
|
|
|
240
|
+
const resetAddForm = useCallback(() => {
|
|
241
|
+
setAddName('');
|
|
242
|
+
setAddCommand('');
|
|
243
|
+
setAddArgs('');
|
|
244
|
+
setAddEnv('');
|
|
245
|
+
setAddUrl('');
|
|
246
|
+
setAddHeaders('');
|
|
247
|
+
setAddError(null);
|
|
248
|
+
setAddPending(false);
|
|
249
|
+
}, []);
|
|
250
|
+
|
|
251
|
+
const openModal = useCallback(() => {
|
|
252
|
+
setModalTab('active');
|
|
253
|
+
resetAddForm();
|
|
254
|
+
setModalOpen(true);
|
|
255
|
+
}, [resetAddForm]);
|
|
256
|
+
|
|
257
|
+
const closeModal = useCallback(() => {
|
|
258
|
+
setModalOpen(false);
|
|
259
|
+
resetAddForm();
|
|
260
|
+
}, [resetAddForm]);
|
|
261
|
+
|
|
262
|
+
const handleAddAndSync = useCallback(async () => {
|
|
263
|
+
const name = addName.trim() || newServerName(Object.keys(original.servers));
|
|
264
|
+
const server: McpServer = addSubTab === 'remote'
|
|
265
|
+
? { url: addUrl.trim(), ...(addHeaders.trim() ? { headers: parseSimpleKV(addHeaders) } : {}) }
|
|
266
|
+
: {
|
|
267
|
+
command: addCommand.trim(),
|
|
268
|
+
...(addArgs.trim() ? { args: addArgs.split(/\r?\n|,/).map(s => s.trim()).filter(Boolean) } : {}),
|
|
269
|
+
...(addEnv.trim() ? { env: parseSimpleKV(addEnv) } : {}),
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const validation = validateServer(name, server);
|
|
273
|
+
if (validation.kind === 'invalid') {
|
|
274
|
+
setAddError(validation.reason);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (original.servers[name]) {
|
|
278
|
+
setAddError(`Server "${name}" already exists.`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
setAddPending(true);
|
|
283
|
+
setAddError(null);
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const merged: McpConfig = {
|
|
287
|
+
...original,
|
|
288
|
+
servers: { ...original.servers, [name]: server },
|
|
289
|
+
};
|
|
290
|
+
await client.put<unknown>('/api/mcp', toPersistShape(merged));
|
|
291
|
+
await client.post<unknown>('/api/mcp/sync', {});
|
|
292
|
+
dirty.clear();
|
|
293
|
+
const fresh = await client.get<unknown>('/api/mcp').catch(() => merged);
|
|
294
|
+
const normalized = normalizeMcpConfig(fresh);
|
|
295
|
+
setDraft(normalized);
|
|
296
|
+
setOrder(Object.keys(normalized.servers));
|
|
297
|
+
setData(fresh);
|
|
298
|
+
await refresh();
|
|
299
|
+
closeModal();
|
|
300
|
+
} catch (err: unknown) {
|
|
301
|
+
setAddError(err instanceof Error ? err.message : String(err));
|
|
302
|
+
setAddPending(false);
|
|
303
|
+
}
|
|
304
|
+
}, [addName, addSubTab, addUrl, addHeaders, addCommand, addArgs, addEnv, original, client, dirty, refresh, setData, closeModal]);
|
|
305
|
+
|
|
306
|
+
const handleRemoveFromModal = useCallback(async (name: string) => {
|
|
307
|
+
const nextServers = { ...original.servers };
|
|
308
|
+
delete nextServers[name];
|
|
309
|
+
const merged: McpConfig = { ...original, servers: nextServers };
|
|
310
|
+
try {
|
|
311
|
+
await client.put<unknown>('/api/mcp', toPersistShape(merged));
|
|
312
|
+
await client.post<unknown>('/api/mcp/sync', {});
|
|
313
|
+
dirty.clear();
|
|
314
|
+
const fresh = await client.get<unknown>('/api/mcp').catch(() => merged);
|
|
315
|
+
const normalized = normalizeMcpConfig(fresh);
|
|
316
|
+
setDraft(normalized);
|
|
317
|
+
setOrder(Object.keys(normalized.servers));
|
|
318
|
+
setData(fresh);
|
|
319
|
+
await refresh();
|
|
320
|
+
} catch (err: unknown) {
|
|
321
|
+
setActionState({
|
|
322
|
+
kind: 'error',
|
|
323
|
+
message: err instanceof Error ? err.message : String(err),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}, [original, client, dirty, refresh, setData]);
|
|
327
|
+
|
|
239
328
|
if (state.kind === 'loading') return <PageLoading />;
|
|
240
329
|
if (state.kind === 'offline') return <PageOffline port={port} />;
|
|
241
330
|
if (state.kind === 'error') return <PageError message={state.message} />;
|
|
@@ -277,15 +366,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
277
366
|
);
|
|
278
367
|
})}
|
|
279
368
|
</div>
|
|
280
|
-
<div className="mcp-servers-actions">
|
|
281
|
-
<button
|
|
282
|
-
type="button"
|
|
283
|
-
className="settings-action"
|
|
284
|
-
onClick={onAddServer}
|
|
285
|
-
>
|
|
286
|
-
Add server
|
|
287
|
-
</button>
|
|
288
|
-
</div>
|
|
289
369
|
{hasDupes && (
|
|
290
370
|
<InlineWarn role="alert">
|
|
291
371
|
Duplicate server name{dupeNames.length === 1 ? '' : 's'}: {dupeNames.join(', ')}.
|
|
@@ -294,7 +374,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
294
374
|
)}
|
|
295
375
|
{hasFieldErrors && !hasDupes && (
|
|
296
376
|
<InlineWarn role="alert">
|
|
297
|
-
Some servers have errors (missing command or invalid name).
|
|
377
|
+
Some servers have errors (missing command/URL or invalid name).
|
|
298
378
|
Saving is blocked until they are fixed.
|
|
299
379
|
</InlineWarn>
|
|
300
380
|
)}
|
|
@@ -305,6 +385,13 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
305
385
|
hint="These act on the saved config. Save edits first if you want them included."
|
|
306
386
|
>
|
|
307
387
|
<div className="mcp-action-buttons">
|
|
388
|
+
<button
|
|
389
|
+
type="button"
|
|
390
|
+
className="settings-action"
|
|
391
|
+
onClick={openModal}
|
|
392
|
+
>
|
|
393
|
+
+ Add, Set MCP
|
|
394
|
+
</button>
|
|
308
395
|
<button
|
|
309
396
|
type="button"
|
|
310
397
|
className="settings-action"
|
|
@@ -316,10 +403,10 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
316
403
|
<button
|
|
317
404
|
type="button"
|
|
318
405
|
className="settings-action"
|
|
319
|
-
disabled={actionState.kind === 'pending'}
|
|
406
|
+
disabled={actionState.kind === 'pending' || bundleCandidates === 0}
|
|
320
407
|
onClick={() => void runAction('Install bundle', '/api/mcp/install')}
|
|
321
408
|
>
|
|
322
|
-
Install bundle
|
|
409
|
+
Install bundle ({bundleCandidates})
|
|
323
410
|
</button>
|
|
324
411
|
<button
|
|
325
412
|
type="button"
|
|
@@ -331,9 +418,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
331
418
|
!window.confirm(
|
|
332
419
|
'Reset MCP config to defaults? Your custom servers will be removed.',
|
|
333
420
|
)
|
|
334
|
-
) {
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
421
|
+
) { return; }
|
|
337
422
|
void runAction('Reset to defaults', '/api/mcp/reset');
|
|
338
423
|
}}
|
|
339
424
|
>
|
|
@@ -341,14 +426,10 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
341
426
|
</button>
|
|
342
427
|
</div>
|
|
343
428
|
{actionState.kind === 'pending' && (
|
|
344
|
-
<p className="settings-section-hint" role="status">
|
|
345
|
-
{actionState.label}…
|
|
346
|
-
</p>
|
|
429
|
+
<p className="settings-section-hint" role="status">{actionState.label}…</p>
|
|
347
430
|
)}
|
|
348
431
|
{actionState.kind === 'success' && (
|
|
349
|
-
<p className="settings-section-hint" role="status">
|
|
350
|
-
✅ {actionState.message}
|
|
351
|
-
</p>
|
|
432
|
+
<p className="settings-section-hint" role="status">{actionState.message}</p>
|
|
352
433
|
)}
|
|
353
434
|
{actionState.kind === 'error' && (
|
|
354
435
|
<InlineWarn role="alert">{actionState.message}</InlineWarn>
|
|
@@ -382,6 +463,193 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
|
|
|
382
463
|
</p>
|
|
383
464
|
)}
|
|
384
465
|
</SettingsSection>
|
|
466
|
+
|
|
467
|
+
{modalOpen && (
|
|
468
|
+
<div className="mcp-modal-backdrop" onClick={closeModal}>
|
|
469
|
+
<div
|
|
470
|
+
className="mcp-modal"
|
|
471
|
+
role="dialog"
|
|
472
|
+
aria-modal="true"
|
|
473
|
+
aria-label="MCP Server Management"
|
|
474
|
+
onClick={(e) => e.stopPropagation()}
|
|
475
|
+
>
|
|
476
|
+
<div className="mcp-modal-tabs">
|
|
477
|
+
<button
|
|
478
|
+
type="button"
|
|
479
|
+
className={`mcp-modal-tab${modalTab === 'active' ? ' is-active' : ''}`}
|
|
480
|
+
onClick={() => setModalTab('active')}
|
|
481
|
+
>
|
|
482
|
+
Active Servers
|
|
483
|
+
</button>
|
|
484
|
+
<button
|
|
485
|
+
type="button"
|
|
486
|
+
className={`mcp-modal-tab${modalTab === 'add' ? ' is-active' : ''}`}
|
|
487
|
+
onClick={() => { setModalTab('add'); resetAddForm(); }}
|
|
488
|
+
>
|
|
489
|
+
Add New
|
|
490
|
+
</button>
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<div className="mcp-modal-body">
|
|
494
|
+
{modalTab === 'active' && (
|
|
495
|
+
<div className="mcp-active-list">
|
|
496
|
+
{Object.keys(original.servers).length === 0 && (
|
|
497
|
+
<p className="settings-section-hint">No active MCP servers.</p>
|
|
498
|
+
)}
|
|
499
|
+
{Object.entries(original.servers).map(([name, srv]) => {
|
|
500
|
+
const tag = getServerTag(srv);
|
|
501
|
+
return (
|
|
502
|
+
<div key={name} className="mcp-active-row">
|
|
503
|
+
<span className="mcp-active-name">{name}</span>
|
|
504
|
+
<span className="mcp-active-detail">
|
|
505
|
+
{srv.url || [srv.command, ...(srv.args || [])].filter(Boolean).join(' ')}
|
|
506
|
+
</span>
|
|
507
|
+
{tag && <span className="mcp-server-tag" data-tag={tag}>[{tag}]</span>}
|
|
508
|
+
<button
|
|
509
|
+
type="button"
|
|
510
|
+
className="settings-action settings-action-discard mcp-active-remove"
|
|
511
|
+
onClick={() => void handleRemoveFromModal(name)}
|
|
512
|
+
>
|
|
513
|
+
Remove
|
|
514
|
+
</button>
|
|
515
|
+
</div>
|
|
516
|
+
);
|
|
517
|
+
})}
|
|
518
|
+
</div>
|
|
519
|
+
)}
|
|
520
|
+
|
|
521
|
+
{modalTab === 'add' && (
|
|
522
|
+
<div className="mcp-add-form">
|
|
523
|
+
<div className="mcp-add-subtabs">
|
|
524
|
+
<button
|
|
525
|
+
type="button"
|
|
526
|
+
className={`mcp-modal-tab${addSubTab === 'local' ? ' is-active' : ''}`}
|
|
527
|
+
onClick={() => setAddSubTab('local')}
|
|
528
|
+
>
|
|
529
|
+
Local
|
|
530
|
+
</button>
|
|
531
|
+
<button
|
|
532
|
+
type="button"
|
|
533
|
+
className={`mcp-modal-tab${addSubTab === 'remote' ? ' is-active' : ''}`}
|
|
534
|
+
onClick={() => setAddSubTab('remote')}
|
|
535
|
+
>
|
|
536
|
+
Remote
|
|
537
|
+
</button>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<label className="settings-field settings-field-text">
|
|
541
|
+
<span className="settings-field-label">Name</span>
|
|
542
|
+
<input
|
|
543
|
+
type="text"
|
|
544
|
+
value={addName}
|
|
545
|
+
placeholder="my-server"
|
|
546
|
+
spellCheck={false}
|
|
547
|
+
onChange={(e) => setAddName(e.target.value)}
|
|
548
|
+
/>
|
|
549
|
+
</label>
|
|
550
|
+
|
|
551
|
+
{addSubTab === 'local' ? (
|
|
552
|
+
<>
|
|
553
|
+
<label className="settings-field settings-field-text">
|
|
554
|
+
<span className="settings-field-label">Command</span>
|
|
555
|
+
<input
|
|
556
|
+
type="text"
|
|
557
|
+
value={addCommand}
|
|
558
|
+
placeholder="npx"
|
|
559
|
+
spellCheck={false}
|
|
560
|
+
onChange={(e) => setAddCommand(e.target.value)}
|
|
561
|
+
/>
|
|
562
|
+
</label>
|
|
563
|
+
<label className="settings-field settings-field-text">
|
|
564
|
+
<span className="settings-field-label">Args (one per line)</span>
|
|
565
|
+
<textarea
|
|
566
|
+
value={addArgs}
|
|
567
|
+
rows={3}
|
|
568
|
+
spellCheck={false}
|
|
569
|
+
placeholder="-y @upstash/context7-mcp"
|
|
570
|
+
onChange={(e) => setAddArgs(e.target.value)}
|
|
571
|
+
/>
|
|
572
|
+
</label>
|
|
573
|
+
<label className="settings-field settings-field-text">
|
|
574
|
+
<span className="settings-field-label">Env (KEY=value per line)</span>
|
|
575
|
+
<textarea
|
|
576
|
+
value={addEnv}
|
|
577
|
+
rows={2}
|
|
578
|
+
spellCheck={false}
|
|
579
|
+
onChange={(e) => setAddEnv(e.target.value)}
|
|
580
|
+
/>
|
|
581
|
+
</label>
|
|
582
|
+
</>
|
|
583
|
+
) : (
|
|
584
|
+
<>
|
|
585
|
+
<label className="settings-field settings-field-text">
|
|
586
|
+
<span className="settings-field-label">URL</span>
|
|
587
|
+
<input
|
|
588
|
+
type="text"
|
|
589
|
+
value={addUrl}
|
|
590
|
+
placeholder="https://mcp.example.com/sse"
|
|
591
|
+
spellCheck={false}
|
|
592
|
+
onChange={(e) => setAddUrl(e.target.value)}
|
|
593
|
+
/>
|
|
594
|
+
</label>
|
|
595
|
+
<label className="settings-field settings-field-text">
|
|
596
|
+
<span className="settings-field-label">Headers (KEY=value per line)</span>
|
|
597
|
+
<textarea
|
|
598
|
+
value={addHeaders}
|
|
599
|
+
rows={2}
|
|
600
|
+
spellCheck={false}
|
|
601
|
+
onChange={(e) => setAddHeaders(e.target.value)}
|
|
602
|
+
/>
|
|
603
|
+
</label>
|
|
604
|
+
</>
|
|
605
|
+
)}
|
|
606
|
+
|
|
607
|
+
{addError && <InlineWarn role="alert">{addError}</InlineWarn>}
|
|
608
|
+
|
|
609
|
+
<div className="mcp-add-actions">
|
|
610
|
+
<button
|
|
611
|
+
type="button"
|
|
612
|
+
className="settings-action"
|
|
613
|
+
onClick={closeModal}
|
|
614
|
+
>
|
|
615
|
+
Cancel
|
|
616
|
+
</button>
|
|
617
|
+
<button
|
|
618
|
+
type="button"
|
|
619
|
+
className="settings-action"
|
|
620
|
+
disabled={addPending}
|
|
621
|
+
onClick={() => void handleAddAndSync()}
|
|
622
|
+
>
|
|
623
|
+
{addPending ? 'Adding…' : 'Add & Sync'}
|
|
624
|
+
</button>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
)}
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
<button
|
|
631
|
+
type="button"
|
|
632
|
+
className="mcp-modal-close"
|
|
633
|
+
aria-label="Close"
|
|
634
|
+
onClick={closeModal}
|
|
635
|
+
>
|
|
636
|
+
×
|
|
637
|
+
</button>
|
|
638
|
+
</div>
|
|
639
|
+
</div>
|
|
640
|
+
)}
|
|
385
641
|
</form>
|
|
386
642
|
);
|
|
387
643
|
}
|
|
644
|
+
|
|
645
|
+
function parseSimpleKV(text: string): Record<string, string> {
|
|
646
|
+
const out: Record<string, string> = {};
|
|
647
|
+
for (const line of text.split(/\r?\n/)) {
|
|
648
|
+
const t = line.trim();
|
|
649
|
+
if (!t || t.startsWith('#')) continue;
|
|
650
|
+
const eq = t.indexOf('=');
|
|
651
|
+
if (eq === -1) continue;
|
|
652
|
+
out[t.slice(0, eq).trim()] = t.slice(eq + 1);
|
|
653
|
+
}
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
// Phase 8 — single MCP server card. The MCP page composes a list of these
|
|
2
|
-
// over the `servers` map returned by `/api/mcp`. Pure presentation; the
|
|
3
|
-
// parent owns the canonical server map and the dirty entry.
|
|
4
|
-
//
|
|
5
|
-
// The shape is defensive: `args` may be missing on legacy entries (treated
|
|
6
|
-
// as `[]`), `env` may be missing or null. We only persist back a clean
|
|
7
|
-
// `McpServer` so loadUnifiedMcp/saveUnifiedMcp round-trip stays stable.
|
|
8
|
-
|
|
9
1
|
import type { McpServer } from '../mcp-helpers';
|
|
10
2
|
import {
|
|
11
3
|
formatArgsText,
|
|
12
4
|
formatEnvText,
|
|
13
5
|
parseArgsText,
|
|
14
6
|
parseEnvText,
|
|
7
|
+
getServerTag,
|
|
15
8
|
} from '../mcp-helpers';
|
|
16
9
|
|
|
17
10
|
type Props = {
|
|
@@ -34,6 +27,9 @@ export function McpServerCard({
|
|
|
34
27
|
const id = `mcp-${name || 'unnamed'}`;
|
|
35
28
|
const argsText = formatArgsText(server.args);
|
|
36
29
|
const envText = formatEnvText(server.env);
|
|
30
|
+
const headersText = formatEnvText(server.headers);
|
|
31
|
+
const tag = getServerTag(server);
|
|
32
|
+
const isRemote = tag === 'remote';
|
|
37
33
|
|
|
38
34
|
return (
|
|
39
35
|
<article className="mcp-server-card" aria-label={`MCP server ${name || '(unnamed)'}`}>
|
|
@@ -55,57 +51,95 @@ export function McpServerCard({
|
|
|
55
51
|
</span>
|
|
56
52
|
) : null}
|
|
57
53
|
</label>
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
<div className="mcp-server-card-actions">
|
|
55
|
+
{tag ? (
|
|
56
|
+
<span className="mcp-server-tag" data-tag={tag}>[{tag}]</span>
|
|
57
|
+
) : null}
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
className="settings-action settings-action-discard"
|
|
61
|
+
onClick={onRemove}
|
|
62
|
+
aria-label={`Remove ${name || 'server'}`}
|
|
63
|
+
>
|
|
64
|
+
Remove
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
66
67
|
</header>
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
{isRemote ? (
|
|
70
|
+
<>
|
|
71
|
+
<label className="settings-field settings-field-text" htmlFor={`${id}-url`}>
|
|
72
|
+
<span className="settings-field-label">URL</span>
|
|
73
|
+
<input
|
|
74
|
+
id={`${id}-url`}
|
|
75
|
+
type="text"
|
|
76
|
+
value={server.url || ''}
|
|
77
|
+
placeholder="https://mcp.example.com/sse"
|
|
78
|
+
spellCheck={false}
|
|
79
|
+
onChange={(event) =>
|
|
80
|
+
onChange({ ...server, url: event.target.value })
|
|
81
|
+
}
|
|
82
|
+
/>
|
|
83
|
+
</label>
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
85
|
+
<label className="settings-field settings-field-text" htmlFor={`${id}-headers`}>
|
|
86
|
+
<span className="settings-field-label">Headers (KEY=value per line)</span>
|
|
87
|
+
<textarea
|
|
88
|
+
id={`${id}-headers`}
|
|
89
|
+
value={headersText}
|
|
90
|
+
rows={3}
|
|
91
|
+
spellCheck={false}
|
|
92
|
+
onChange={(event) =>
|
|
93
|
+
onChange({ ...server, headers: parseEnvText(event.target.value) })
|
|
94
|
+
}
|
|
95
|
+
/>
|
|
96
|
+
</label>
|
|
97
|
+
</>
|
|
98
|
+
) : (
|
|
99
|
+
<>
|
|
100
|
+
<label className="settings-field settings-field-text" htmlFor={`${id}-command`}>
|
|
101
|
+
<span className="settings-field-label">Command</span>
|
|
102
|
+
<input
|
|
103
|
+
id={`${id}-command`}
|
|
104
|
+
type="text"
|
|
105
|
+
value={server.command || ''}
|
|
106
|
+
placeholder="npx"
|
|
107
|
+
spellCheck={false}
|
|
108
|
+
onChange={(event) =>
|
|
109
|
+
onChange({ ...server, command: event.target.value })
|
|
110
|
+
}
|
|
111
|
+
/>
|
|
112
|
+
</label>
|
|
96
113
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
<label className="settings-field settings-field-text" htmlFor={`${id}-args`}>
|
|
115
|
+
<span className="settings-field-label">
|
|
116
|
+
Args (one per line, or comma-separated)
|
|
117
|
+
</span>
|
|
118
|
+
<textarea
|
|
119
|
+
id={`${id}-args`}
|
|
120
|
+
value={argsText}
|
|
121
|
+
rows={3}
|
|
122
|
+
spellCheck={false}
|
|
123
|
+
onChange={(event) =>
|
|
124
|
+
onChange({ ...server, args: parseArgsText(event.target.value) })
|
|
125
|
+
}
|
|
126
|
+
/>
|
|
127
|
+
</label>
|
|
128
|
+
|
|
129
|
+
<label className="settings-field settings-field-text" htmlFor={`${id}-env`}>
|
|
130
|
+
<span className="settings-field-label">Env (KEY=value per line)</span>
|
|
131
|
+
<textarea
|
|
132
|
+
id={`${id}-env`}
|
|
133
|
+
value={envText}
|
|
134
|
+
rows={3}
|
|
135
|
+
spellCheck={false}
|
|
136
|
+
onChange={(event) =>
|
|
137
|
+
onChange({ ...server, env: parseEnvText(event.target.value) })
|
|
138
|
+
}
|
|
139
|
+
/>
|
|
140
|
+
</label>
|
|
141
|
+
</>
|
|
142
|
+
)}
|
|
109
143
|
|
|
110
144
|
<label className="settings-field settings-field-toggle" htmlFor={`${id}-autostart`}>
|
|
111
145
|
<input
|