@nghyane/arcane 0.1.0
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/CHANGELOG.md +3 -0
- package/README.md +12 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +109 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/todo/index.ts +206 -0
- package/examples/extensions/README.md +143 -0
- package/examples/extensions/api-demo.ts +89 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +32 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +550 -0
- package/examples/extensions/reload-runtime.ts +37 -0
- package/examples/extensions/todo.ts +296 -0
- package/examples/extensions/tools.ts +144 -0
- package/examples/extensions/with-deps/index.ts +35 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +43 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +80 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +41 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/README.md +150 -0
- package/package.json +464 -0
- package/scripts/format-prompts.ts +184 -0
- package/scripts/generate-docs-index.ts +40 -0
- package/scripts/generate-template.ts +32 -0
- package/src/bun-imports.d.ts +22 -0
- package/src/capability/context-file.ts +39 -0
- package/src/capability/extension-module.ts +33 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +89 -0
- package/src/capability/hook.ts +39 -0
- package/src/capability/index.ts +432 -0
- package/src/capability/instruction.ts +36 -0
- package/src/capability/mcp.ts +60 -0
- package/src/capability/prompt.ts +34 -0
- package/src/capability/rule.ts +223 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +48 -0
- package/src/capability/slash-command.ts +39 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +37 -0
- package/src/capability/types.ts +156 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/config-cli.ts +357 -0
- package/src/cli/file-processor.ts +124 -0
- package/src/cli/grep-cli.ts +152 -0
- package/src/cli/jupyter-cli.ts +106 -0
- package/src/cli/list-models.ts +103 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +42 -0
- package/src/cli/setup-cli.ts +376 -0
- package/src/cli/shell-cli.ts +174 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/stats-cli.ts +197 -0
- package/src/cli/update-cli.ts +286 -0
- package/src/cli/web-search-cli.ts +143 -0
- package/src/cli.ts +65 -0
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +41 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/launch.ts +139 -0
- package/src/commands/plugin.ts +70 -0
- package/src/commands/setup.ts +42 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/ssh.ts +60 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +42 -0
- package/src/commit/agentic/agent.ts +311 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +359 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +69 -0
- package/src/commit/agentic/tools/analyze-file.ts +118 -0
- package/src/commit/agentic/tools/git-file-diff.ts +194 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +84 -0
- package/src/commit/agentic/tools/index.ts +56 -0
- package/src/commit/agentic/tools/propose-changelog.ts +128 -0
- package/src/commit/agentic/tools/propose-commit.ts +154 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/split-commit.ts +280 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +200 -0
- package/src/commit/analysis/conventional.ts +165 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +112 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +37 -0
- package/src/commit/changelog/generate.ts +110 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +93 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/git/errors.ts +9 -0
- package/src/commit/git/index.ts +211 -0
- package/src/commit/git/operations.ts +54 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +64 -0
- package/src/commit/map-reduce/map-phase.ts +178 -0
- package/src/commit/map-reduce/reduce-phase.ts +145 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +69 -0
- package/src/commit/pipeline.ts +243 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/types.ts +109 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/config/file-lock.ts +121 -0
- package/src/config/keybindings.ts +280 -0
- package/src/config/model-registry.ts +1140 -0
- package/src/config/model-resolver.ts +812 -0
- package/src/config/prompt-templates.ts +526 -0
- package/src/config/resolve-config-value.ts +92 -0
- package/src/config/settings-schema.ts +1236 -0
- package/src/config/settings.ts +706 -0
- package/src/config.ts +414 -0
- package/src/cursor.ts +239 -0
- package/src/debug/index.ts +431 -0
- package/src/debug/log-formatting.ts +60 -0
- package/src/debug/log-viewer.ts +903 -0
- package/src/debug/profiler.ts +158 -0
- package/src/debug/report-bundle.ts +366 -0
- package/src/debug/system-info.ts +112 -0
- package/src/discovery/agents-md.ts +68 -0
- package/src/discovery/agents.ts +199 -0
- package/src/discovery/builtin.ts +815 -0
- package/src/discovery/claude-plugins.ts +205 -0
- package/src/discovery/claude.ts +506 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +532 -0
- package/src/discovery/cursor.ts +218 -0
- package/src/discovery/gemini.ts +395 -0
- package/src/discovery/github.ts +117 -0
- package/src/discovery/helpers.ts +698 -0
- package/src/discovery/index.ts +89 -0
- package/src/discovery/mcp-json.ts +156 -0
- package/src/discovery/opencode.ts +394 -0
- package/src/discovery/ssh.ts +160 -0
- package/src/discovery/vscode.ts +103 -0
- package/src/discovery/windsurf.ts +145 -0
- package/src/exa/company.ts +57 -0
- package/src/exa/index.ts +62 -0
- package/src/exa/linkedin.ts +57 -0
- package/src/exa/mcp-client.ts +289 -0
- package/src/exa/render.ts +244 -0
- package/src/exa/researcher.ts +89 -0
- package/src/exa/search.ts +330 -0
- package/src/exa/types.ts +166 -0
- package/src/exa/websets.ts +247 -0
- package/src/exec/bash-executor.ts +184 -0
- package/src/exec/exec.ts +53 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/index.ts +162 -0
- package/src/export/html/template.css +889 -0
- package/src/export/html/template.generated.ts +2 -0
- package/src/export/html/template.html +45 -0
- package/src/export/html/template.js +1329 -0
- package/src/export/html/template.macro.ts +24 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/ttsr.ts +434 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +433 -0
- package/src/extensibility/custom-commands/index.ts +15 -0
- package/src/extensibility/custom-commands/loader.ts +231 -0
- package/src/extensibility/custom-commands/types.ts +111 -0
- package/src/extensibility/custom-tools/index.ts +22 -0
- package/src/extensibility/custom-tools/loader.ts +235 -0
- package/src/extensibility/custom-tools/types.ts +226 -0
- package/src/extensibility/custom-tools/wrapper.ts +45 -0
- package/src/extensibility/extensions/index.ts +136 -0
- package/src/extensibility/extensions/loader.ts +520 -0
- package/src/extensibility/extensions/runner.ts +774 -0
- package/src/extensibility/extensions/types.ts +1293 -0
- package/src/extensibility/extensions/wrapper.ts +188 -0
- package/src/extensibility/hooks/index.ts +16 -0
- package/src/extensibility/hooks/loader.ts +273 -0
- package/src/extensibility/hooks/runner.ts +441 -0
- package/src/extensibility/hooks/tool-wrapper.ts +106 -0
- package/src/extensibility/hooks/types.ts +817 -0
- package/src/extensibility/plugins/doctor.ts +65 -0
- package/src/extensibility/plugins/git-url.ts +281 -0
- package/src/extensibility/plugins/index.ts +33 -0
- package/src/extensibility/plugins/installer.ts +192 -0
- package/src/extensibility/plugins/loader.ts +338 -0
- package/src/extensibility/plugins/manager.ts +716 -0
- package/src/extensibility/plugins/parser.ts +105 -0
- package/src/extensibility/plugins/types.ts +190 -0
- package/src/extensibility/skills.ts +385 -0
- package/src/extensibility/slash-commands.ts +287 -0
- package/src/extensibility/tool-proxy.ts +25 -0
- package/src/index.ts +275 -0
- package/src/internal-urls/agent-protocol.ts +136 -0
- package/src/internal-urls/artifact-protocol.ts +97 -0
- package/src/internal-urls/docs-index.generated.ts +54 -0
- package/src/internal-urls/docs-protocol.ts +84 -0
- package/src/internal-urls/index.ts +31 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/memory-protocol.ts +133 -0
- package/src/internal-urls/router.ts +70 -0
- package/src/internal-urls/rule-protocol.ts +55 -0
- package/src/internal-urls/skill-protocol.ts +111 -0
- package/src/internal-urls/types.ts +52 -0
- package/src/ipy/executor.ts +556 -0
- package/src/ipy/gateway-coordinator.ts +426 -0
- package/src/ipy/kernel.ts +892 -0
- package/src/ipy/modules.ts +109 -0
- package/src/ipy/prelude.py +831 -0
- package/src/ipy/prelude.ts +3 -0
- package/src/ipy/runtime.ts +222 -0
- package/src/lsp/client.ts +867 -0
- package/src/lsp/clients/biome-client.ts +202 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +93 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +397 -0
- package/src/lsp/defaults.json +464 -0
- package/src/lsp/edits.ts +109 -0
- package/src/lsp/index.ts +1268 -0
- package/src/lsp/lspmux.ts +250 -0
- package/src/lsp/render.ts +689 -0
- package/src/lsp/types.ts +414 -0
- package/src/lsp/utils.ts +549 -0
- package/src/main.ts +773 -0
- package/src/mcp/client.ts +239 -0
- package/src/mcp/config-writer.ts +215 -0
- package/src/mcp/config.ts +363 -0
- package/src/mcp/index.ts +55 -0
- package/src/mcp/json-rpc.ts +84 -0
- package/src/mcp/loader.ts +124 -0
- package/src/mcp/manager.ts +490 -0
- package/src/mcp/oauth-discovery.ts +274 -0
- package/src/mcp/oauth-flow.ts +229 -0
- package/src/mcp/render.ts +123 -0
- package/src/mcp/tool-bridge.ts +372 -0
- package/src/mcp/tool-cache.ts +121 -0
- package/src/mcp/transports/http.ts +332 -0
- package/src/mcp/transports/index.ts +6 -0
- package/src/mcp/transports/stdio.ts +281 -0
- package/src/mcp/types.ts +248 -0
- package/src/memories/index.ts +1099 -0
- package/src/memories/storage.ts +563 -0
- package/src/modes/components/agent-dashboard.ts +1130 -0
- package/src/modes/components/assistant-message.ts +144 -0
- package/src/modes/components/bash-execution.ts +218 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/branch-summary-message.ts +45 -0
- package/src/modes/components/codemode-group.ts +369 -0
- package/src/modes/components/compaction-summary-message.ts +51 -0
- package/src/modes/components/countdown-timer.ts +46 -0
- package/src/modes/components/custom-editor.ts +181 -0
- package/src/modes/components/custom-message.ts +91 -0
- package/src/modes/components/diff.ts +186 -0
- package/src/modes/components/dynamic-border.ts +25 -0
- package/src/modes/components/extensions/extension-dashboard.ts +325 -0
- package/src/modes/components/extensions/extension-list.ts +484 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +321 -0
- package/src/modes/components/extensions/state-manager.ts +586 -0
- package/src/modes/components/extensions/types.ts +191 -0
- package/src/modes/components/footer.ts +315 -0
- package/src/modes/components/history-search.ts +157 -0
- package/src/modes/components/hook-editor.ts +101 -0
- package/src/modes/components/hook-input.ts +72 -0
- package/src/modes/components/hook-message.ts +100 -0
- package/src/modes/components/hook-selector.ts +155 -0
- package/src/modes/components/index.ts +41 -0
- package/src/modes/components/keybinding-hints.ts +65 -0
- package/src/modes/components/login-dialog.ts +164 -0
- package/src/modes/components/mcp-add-wizard.ts +1295 -0
- package/src/modes/components/model-selector.ts +625 -0
- package/src/modes/components/oauth-selector.ts +210 -0
- package/src/modes/components/plugin-settings.ts +477 -0
- package/src/modes/components/python-execution.ts +196 -0
- package/src/modes/components/queue-mode-selector.ts +56 -0
- package/src/modes/components/read-tool-group.ts +119 -0
- package/src/modes/components/session-selector.ts +242 -0
- package/src/modes/components/settings-defs.ts +340 -0
- package/src/modes/components/settings-selector.ts +529 -0
- package/src/modes/components/show-images-selector.ts +45 -0
- package/src/modes/components/skill-message.ts +90 -0
- package/src/modes/components/status-line/index.ts +4 -0
- package/src/modes/components/status-line/presets.ts +94 -0
- package/src/modes/components/status-line/segments.ts +352 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/types.ts +75 -0
- package/src/modes/components/status-line-segment-editor.ts +354 -0
- package/src/modes/components/status-line.ts +421 -0
- package/src/modes/components/theme-selector.ts +63 -0
- package/src/modes/components/thinking-selector.ts +64 -0
- package/src/modes/components/todo-display.ts +115 -0
- package/src/modes/components/todo-reminder.ts +40 -0
- package/src/modes/components/tool-execution.ts +703 -0
- package/src/modes/components/tree-selector.ts +904 -0
- package/src/modes/components/ttsr-notification.ts +80 -0
- package/src/modes/components/user-message-selector.ts +146 -0
- package/src/modes/components/user-message.ts +22 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +247 -0
- package/src/modes/controllers/command-controller.ts +1120 -0
- package/src/modes/controllers/event-controller.ts +479 -0
- package/src/modes/controllers/extension-ui-controller.ts +778 -0
- package/src/modes/controllers/input-controller.ts +671 -0
- package/src/modes/controllers/mcp-command-controller.ts +1315 -0
- package/src/modes/controllers/selector-controller.ts +712 -0
- package/src/modes/controllers/ssh-command-controller.ts +452 -0
- package/src/modes/index.ts +15 -0
- package/src/modes/interactive-mode.ts +1027 -0
- package/src/modes/print-mode.ts +191 -0
- package/src/modes/rpc/rpc-client.ts +583 -0
- package/src/modes/rpc/rpc-mode.ts +700 -0
- package/src/modes/rpc/rpc-types.ts +236 -0
- package/src/modes/theme/dark.json +95 -0
- package/src/modes/theme/defaults/alabaster.json +93 -0
- package/src/modes/theme/defaults/amethyst.json +96 -0
- package/src/modes/theme/defaults/anthracite.json +93 -0
- package/src/modes/theme/defaults/basalt.json +91 -0
- package/src/modes/theme/defaults/birch.json +95 -0
- package/src/modes/theme/defaults/dark-abyss.json +91 -0
- package/src/modes/theme/defaults/dark-arctic.json +104 -0
- package/src/modes/theme/defaults/dark-aurora.json +95 -0
- package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
- package/src/modes/theme/defaults/dark-cavern.json +91 -0
- package/src/modes/theme/defaults/dark-copper.json +95 -0
- package/src/modes/theme/defaults/dark-cosmos.json +90 -0
- package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/modes/theme/defaults/dark-dracula.json +98 -0
- package/src/modes/theme/defaults/dark-eclipse.json +91 -0
- package/src/modes/theme/defaults/dark-ember.json +95 -0
- package/src/modes/theme/defaults/dark-equinox.json +90 -0
- package/src/modes/theme/defaults/dark-forest.json +96 -0
- package/src/modes/theme/defaults/dark-github.json +105 -0
- package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
- package/src/modes/theme/defaults/dark-lavender.json +95 -0
- package/src/modes/theme/defaults/dark-lunar.json +89 -0
- package/src/modes/theme/defaults/dark-midnight.json +95 -0
- package/src/modes/theme/defaults/dark-monochrome.json +94 -0
- package/src/modes/theme/defaults/dark-monokai.json +98 -0
- package/src/modes/theme/defaults/dark-nebula.json +90 -0
- package/src/modes/theme/defaults/dark-nord.json +97 -0
- package/src/modes/theme/defaults/dark-ocean.json +101 -0
- package/src/modes/theme/defaults/dark-one.json +100 -0
- package/src/modes/theme/defaults/dark-rainforest.json +91 -0
- package/src/modes/theme/defaults/dark-reef.json +91 -0
- package/src/modes/theme/defaults/dark-retro.json +92 -0
- package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
- package/src/modes/theme/defaults/dark-sakura.json +95 -0
- package/src/modes/theme/defaults/dark-slate.json +95 -0
- package/src/modes/theme/defaults/dark-solarized.json +97 -0
- package/src/modes/theme/defaults/dark-solstice.json +90 -0
- package/src/modes/theme/defaults/dark-starfall.json +91 -0
- package/src/modes/theme/defaults/dark-sunset.json +99 -0
- package/src/modes/theme/defaults/dark-swamp.json +90 -0
- package/src/modes/theme/defaults/dark-synthwave.json +103 -0
- package/src/modes/theme/defaults/dark-taiga.json +91 -0
- package/src/modes/theme/defaults/dark-terminal.json +95 -0
- package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/modes/theme/defaults/dark-tundra.json +91 -0
- package/src/modes/theme/defaults/dark-twilight.json +91 -0
- package/src/modes/theme/defaults/dark-volcanic.json +91 -0
- package/src/modes/theme/defaults/graphite.json +92 -0
- package/src/modes/theme/defaults/index.ts +195 -0
- package/src/modes/theme/defaults/light-arctic.json +107 -0
- package/src/modes/theme/defaults/light-aurora-day.json +91 -0
- package/src/modes/theme/defaults/light-canyon.json +91 -0
- package/src/modes/theme/defaults/light-catppuccin.json +106 -0
- package/src/modes/theme/defaults/light-cirrus.json +90 -0
- package/src/modes/theme/defaults/light-coral.json +95 -0
- package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
- package/src/modes/theme/defaults/light-dawn.json +90 -0
- package/src/modes/theme/defaults/light-dunes.json +91 -0
- package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
- package/src/modes/theme/defaults/light-forest.json +100 -0
- package/src/modes/theme/defaults/light-frost.json +95 -0
- package/src/modes/theme/defaults/light-github.json +115 -0
- package/src/modes/theme/defaults/light-glacier.json +91 -0
- package/src/modes/theme/defaults/light-gruvbox.json +108 -0
- package/src/modes/theme/defaults/light-haze.json +90 -0
- package/src/modes/theme/defaults/light-honeycomb.json +95 -0
- package/src/modes/theme/defaults/light-lagoon.json +91 -0
- package/src/modes/theme/defaults/light-lavender.json +95 -0
- package/src/modes/theme/defaults/light-meadow.json +91 -0
- package/src/modes/theme/defaults/light-mint.json +95 -0
- package/src/modes/theme/defaults/light-monochrome.json +101 -0
- package/src/modes/theme/defaults/light-ocean.json +99 -0
- package/src/modes/theme/defaults/light-one.json +99 -0
- package/src/modes/theme/defaults/light-opal.json +91 -0
- package/src/modes/theme/defaults/light-orchard.json +91 -0
- package/src/modes/theme/defaults/light-paper.json +95 -0
- package/src/modes/theme/defaults/light-prism.json +90 -0
- package/src/modes/theme/defaults/light-retro.json +98 -0
- package/src/modes/theme/defaults/light-sand.json +95 -0
- package/src/modes/theme/defaults/light-savanna.json +91 -0
- package/src/modes/theme/defaults/light-solarized.json +102 -0
- package/src/modes/theme/defaults/light-soleil.json +90 -0
- package/src/modes/theme/defaults/light-sunset.json +99 -0
- package/src/modes/theme/defaults/light-synthwave.json +98 -0
- package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
- package/src/modes/theme/defaults/light-wetland.json +91 -0
- package/src/modes/theme/defaults/light-zenith.json +89 -0
- package/src/modes/theme/defaults/limestone.json +94 -0
- package/src/modes/theme/defaults/mahogany.json +97 -0
- package/src/modes/theme/defaults/marble.json +93 -0
- package/src/modes/theme/defaults/obsidian.json +91 -0
- package/src/modes/theme/defaults/onyx.json +91 -0
- package/src/modes/theme/defaults/pearl.json +93 -0
- package/src/modes/theme/defaults/porcelain.json +91 -0
- package/src/modes/theme/defaults/quartz.json +96 -0
- package/src/modes/theme/defaults/sandstone.json +95 -0
- package/src/modes/theme/defaults/titanium.json +90 -0
- package/src/modes/theme/light.json +93 -0
- package/src/modes/theme/mermaid-cache.ts +111 -0
- package/src/modes/theme/theme-schema.json +429 -0
- package/src/modes/theme/theme.ts +2333 -0
- package/src/modes/types.ts +216 -0
- package/src/modes/utils/ui-helpers.ts +529 -0
- package/src/patch/applicator.ts +1482 -0
- package/src/patch/diff.ts +425 -0
- package/src/patch/fuzzy.ts +784 -0
- package/src/patch/hashline.ts +972 -0
- package/src/patch/index.ts +964 -0
- package/src/patch/normalize.ts +397 -0
- package/src/patch/normative.ts +72 -0
- package/src/patch/parser.ts +532 -0
- package/src/patch/shared.ts +400 -0
- package/src/patch/types.ts +292 -0
- package/src/priority.json +35 -0
- package/src/prompts/agents/explore.md +48 -0
- package/src/prompts/agents/frontmatter.md +9 -0
- package/src/prompts/agents/init.md +36 -0
- package/src/prompts/agents/librarian.md +53 -0
- package/src/prompts/agents/oracle.md +51 -0
- package/src/prompts/agents/reviewer.md +70 -0
- package/src/prompts/agents/task.md +14 -0
- package/src/prompts/compaction/branch-summary-context.md +5 -0
- package/src/prompts/compaction/branch-summary-preamble.md +2 -0
- package/src/prompts/compaction/branch-summary.md +30 -0
- package/src/prompts/compaction/compaction-short-summary.md +9 -0
- package/src/prompts/compaction/compaction-summary-context.md +5 -0
- package/src/prompts/compaction/compaction-summary.md +38 -0
- package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
- package/src/prompts/compaction/compaction-update-summary.md +45 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/read_path.md +11 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/review-request.md +64 -0
- package/src/prompts/system/agent-creation-architect.md +65 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/custom-system-prompt.md +68 -0
- package/src/prompts/system/file-operations.md +10 -0
- package/src/prompts/system/subagent-submit-reminder.md +11 -0
- package/src/prompts/system/subagent-system-prompt.md +31 -0
- package/src/prompts/system/subagent-user-prompt.md +8 -0
- package/src/prompts/system/summarization-system.md +3 -0
- package/src/prompts/system/system-prompt.md +300 -0
- package/src/prompts/system/title-system.md +2 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/web-search.md +28 -0
- package/src/prompts/tools/ask.md +44 -0
- package/src/prompts/tools/bash.md +24 -0
- package/src/prompts/tools/browser.md +33 -0
- package/src/prompts/tools/calculator.md +12 -0
- package/src/prompts/tools/explore.md +29 -0
- package/src/prompts/tools/fetch.md +16 -0
- package/src/prompts/tools/find.md +18 -0
- package/src/prompts/tools/gemini-image.md +23 -0
- package/src/prompts/tools/grep.md +28 -0
- package/src/prompts/tools/hashline.md +232 -0
- package/src/prompts/tools/librarian.md +24 -0
- package/src/prompts/tools/lsp.md +28 -0
- package/src/prompts/tools/oracle.md +26 -0
- package/src/prompts/tools/patch.md +74 -0
- package/src/prompts/tools/python.md +66 -0
- package/src/prompts/tools/read.md +36 -0
- package/src/prompts/tools/replace.md +38 -0
- package/src/prompts/tools/reviewer.md +41 -0
- package/src/prompts/tools/ssh.md +51 -0
- package/src/prompts/tools/task-summary.md +28 -0
- package/src/prompts/tools/task.md +275 -0
- package/src/prompts/tools/todo-write.md +65 -0
- package/src/prompts/tools/undo-edit.md +7 -0
- package/src/prompts/tools/web-search.md +19 -0
- package/src/prompts/tools/write.md +18 -0
- package/src/sdk.ts +1287 -0
- package/src/secrets/index.ts +116 -0
- package/src/secrets/obfuscator.ts +269 -0
- package/src/secrets/regex.ts +21 -0
- package/src/session/agent-session.ts +4669 -0
- package/src/session/agent-storage.ts +621 -0
- package/src/session/artifacts.ts +132 -0
- package/src/session/auth-storage.ts +1433 -0
- package/src/session/blob-store.ts +103 -0
- package/src/session/compaction/branch-summarization.ts +315 -0
- package/src/session/compaction/compaction.ts +864 -0
- package/src/session/compaction/index.ts +7 -0
- package/src/session/compaction/pruning.ts +91 -0
- package/src/session/compaction/utils.ts +171 -0
- package/src/session/history-storage.ts +170 -0
- package/src/session/messages.ts +317 -0
- package/src/session/session-manager.ts +2276 -0
- package/src/session/session-storage.ts +342 -0
- package/src/session/streaming-output.ts +565 -0
- package/src/slash-commands/builtin-registry.ts +439 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +444 -0
- package/src/ssh/ssh-executor.ts +127 -0
- package/src/ssh/sshfs-mount.ts +135 -0
- package/src/stt/downloader.ts +71 -0
- package/src/stt/index.ts +3 -0
- package/src/stt/recorder.ts +351 -0
- package/src/stt/setup.ts +52 -0
- package/src/stt/stt-controller.ts +160 -0
- package/src/stt/transcribe.py +70 -0
- package/src/stt/transcriber.ts +91 -0
- package/src/system-prompt.ts +685 -0
- package/src/task/agents.ts +155 -0
- package/src/task/batch.ts +102 -0
- package/src/task/commands.ts +134 -0
- package/src/task/discovery.ts +126 -0
- package/src/task/executor.ts +908 -0
- package/src/task/index.ts +223 -0
- package/src/task/output-manager.ts +107 -0
- package/src/task/parallel.ts +84 -0
- package/src/task/render.ts +326 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/template.ts +32 -0
- package/src/task/types.ts +144 -0
- package/src/tools/ask.ts +523 -0
- package/src/tools/bash-interactive.ts +419 -0
- package/src/tools/bash-interceptor.ts +105 -0
- package/src/tools/bash-normalize.ts +107 -0
- package/src/tools/bash-skill-urls.ts +177 -0
- package/src/tools/bash.ts +347 -0
- package/src/tools/browser.ts +1374 -0
- package/src/tools/calculator.ts +537 -0
- package/src/tools/context.ts +39 -0
- package/src/tools/explore.ts +23 -0
- package/src/tools/fetch.ts +1091 -0
- package/src/tools/find.ts +540 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gemini-image.ts +907 -0
- package/src/tools/grep.ts +489 -0
- package/src/tools/index.ts +337 -0
- package/src/tools/json-tree.ts +231 -0
- package/src/tools/jtd-to-json-schema.ts +247 -0
- package/src/tools/jtd-to-typescript.ts +198 -0
- package/src/tools/librarian.ts +33 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/notebook.ts +287 -0
- package/src/tools/oracle.ts +40 -0
- package/src/tools/output-meta.ts +459 -0
- package/src/tools/output-utils.ts +63 -0
- package/src/tools/path-utils.ts +116 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
- package/src/tools/python.ts +1118 -0
- package/src/tools/read.ts +1193 -0
- package/src/tools/render-utils.ts +680 -0
- package/src/tools/renderers.ts +60 -0
- package/src/tools/reviewer-tool.ts +41 -0
- package/src/tools/ssh.ts +326 -0
- package/src/tools/subagent-tool.ts +169 -0
- package/src/tools/submit-result.ts +152 -0
- package/src/tools/todo-write.ts +255 -0
- package/src/tools/tool-errors.ts +92 -0
- package/src/tools/tool-result.ts +86 -0
- package/src/tools/undo-edit.ts +145 -0
- package/src/tools/undo-history.ts +22 -0
- package/src/tools/write.ts +274 -0
- package/src/tui/code-cell.ts +108 -0
- package/src/tui/file-list.ts +47 -0
- package/src/tui/index.ts +11 -0
- package/src/tui/output-block.ts +144 -0
- package/src/tui/status-line.ts +39 -0
- package/src/tui/tree-list.ts +53 -0
- package/src/tui/types.ts +16 -0
- package/src/tui/utils.ts +116 -0
- package/src/utils/changelog.ts +98 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +59 -0
- package/src/utils/file-display-mode.ts +36 -0
- package/src/utils/file-mentions.ts +384 -0
- package/src/utils/frontmatter.ts +101 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/ignore-files.ts +119 -0
- package/src/utils/image-convert.ts +27 -0
- package/src/utils/image-resize.ts +236 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/open.ts +20 -0
- package/src/utils/shell-snapshot.ts +199 -0
- package/src/utils/timings.ts +26 -0
- package/src/utils/title-generator.ts +167 -0
- package/src/utils/tools-manager.ts +362 -0
- package/src/web/scrapers/artifacthub.ts +215 -0
- package/src/web/scrapers/arxiv.ts +88 -0
- package/src/web/scrapers/aur.ts +175 -0
- package/src/web/scrapers/biorxiv.ts +141 -0
- package/src/web/scrapers/bluesky.ts +284 -0
- package/src/web/scrapers/brew.ts +177 -0
- package/src/web/scrapers/cheatsh.ts +78 -0
- package/src/web/scrapers/chocolatey.ts +158 -0
- package/src/web/scrapers/choosealicense.ts +110 -0
- package/src/web/scrapers/cisa-kev.ts +100 -0
- package/src/web/scrapers/clojars.ts +180 -0
- package/src/web/scrapers/coingecko.ts +184 -0
- package/src/web/scrapers/crates-io.ts +128 -0
- package/src/web/scrapers/crossref.ts +149 -0
- package/src/web/scrapers/devto.ts +177 -0
- package/src/web/scrapers/discogs.ts +307 -0
- package/src/web/scrapers/discourse.ts +221 -0
- package/src/web/scrapers/dockerhub.ts +160 -0
- package/src/web/scrapers/fdroid.ts +158 -0
- package/src/web/scrapers/firefox-addons.ts +214 -0
- package/src/web/scrapers/flathub.ts +239 -0
- package/src/web/scrapers/github-gist.ts +68 -0
- package/src/web/scrapers/github.ts +490 -0
- package/src/web/scrapers/gitlab.ts +456 -0
- package/src/web/scrapers/go-pkg.ts +275 -0
- package/src/web/scrapers/hackage.ts +94 -0
- package/src/web/scrapers/hackernews.ts +208 -0
- package/src/web/scrapers/hex.ts +121 -0
- package/src/web/scrapers/huggingface.ts +385 -0
- package/src/web/scrapers/iacr.ts +86 -0
- package/src/web/scrapers/index.ts +249 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +169 -0
- package/src/web/scrapers/lemmy.ts +220 -0
- package/src/web/scrapers/lobsters.ts +186 -0
- package/src/web/scrapers/mastodon.ts +310 -0
- package/src/web/scrapers/maven.ts +152 -0
- package/src/web/scrapers/mdn.ts +172 -0
- package/src/web/scrapers/metacpan.ts +253 -0
- package/src/web/scrapers/musicbrainz.ts +272 -0
- package/src/web/scrapers/npm.ts +114 -0
- package/src/web/scrapers/nuget.ts +205 -0
- package/src/web/scrapers/nvd.ts +243 -0
- package/src/web/scrapers/ollama.ts +265 -0
- package/src/web/scrapers/open-vsx.ts +119 -0
- package/src/web/scrapers/opencorporates.ts +275 -0
- package/src/web/scrapers/openlibrary.ts +319 -0
- package/src/web/scrapers/orcid.ts +298 -0
- package/src/web/scrapers/osv.ts +192 -0
- package/src/web/scrapers/packagist.ts +174 -0
- package/src/web/scrapers/pub-dev.ts +185 -0
- package/src/web/scrapers/pubmed.ts +177 -0
- package/src/web/scrapers/pypi.ts +129 -0
- package/src/web/scrapers/rawg.ts +124 -0
- package/src/web/scrapers/readthedocs.ts +125 -0
- package/src/web/scrapers/reddit.ts +104 -0
- package/src/web/scrapers/repology.ts +262 -0
- package/src/web/scrapers/rfc.ts +209 -0
- package/src/web/scrapers/rubygems.ts +117 -0
- package/src/web/scrapers/searchcode.ts +217 -0
- package/src/web/scrapers/sec-edgar.ts +274 -0
- package/src/web/scrapers/semantic-scholar.ts +190 -0
- package/src/web/scrapers/snapcraft.ts +200 -0
- package/src/web/scrapers/sourcegraph.ts +373 -0
- package/src/web/scrapers/spdx.ts +121 -0
- package/src/web/scrapers/spotify.ts +217 -0
- package/src/web/scrapers/stackoverflow.ts +124 -0
- package/src/web/scrapers/terraform.ts +304 -0
- package/src/web/scrapers/tldr.ts +51 -0
- package/src/web/scrapers/twitter.ts +97 -0
- package/src/web/scrapers/types.ts +200 -0
- package/src/web/scrapers/utils.ts +142 -0
- package/src/web/scrapers/vimeo.ts +152 -0
- package/src/web/scrapers/vscode-marketplace.ts +195 -0
- package/src/web/scrapers/w3c.ts +163 -0
- package/src/web/scrapers/wikidata.ts +357 -0
- package/src/web/scrapers/wikipedia.ts +95 -0
- package/src/web/scrapers/youtube.ts +312 -0
- package/src/web/search/auth.ts +178 -0
- package/src/web/search/index.ts +598 -0
- package/src/web/search/provider.ts +77 -0
- package/src/web/search/providers/anthropic.ts +284 -0
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/brave.ts +165 -0
- package/src/web/search/providers/codex.ts +377 -0
- package/src/web/search/providers/exa.ts +158 -0
- package/src/web/search/providers/gemini.ts +437 -0
- package/src/web/search/providers/jina.ts +99 -0
- package/src/web/search/providers/kimi.ts +196 -0
- package/src/web/search/providers/perplexity.ts +546 -0
- package/src/web/search/providers/synthetic.ts +136 -0
- package/src/web/search/providers/zai.ts +352 -0
- package/src/web/search/render.ts +299 -0
- package/src/web/search/types.ts +437 -0
|
@@ -0,0 +1,1315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Command Controller
|
|
3
|
+
*
|
|
4
|
+
* Handles /mcp subcommands for managing MCP servers.
|
|
5
|
+
*/
|
|
6
|
+
import { Spacer, Text } from "@nghyane/arcane-tui";
|
|
7
|
+
import { getMCPConfigPath, getProjectDir } from "@nghyane/arcane-utils/dirs";
|
|
8
|
+
import type { SourceMeta } from "../../capability/types";
|
|
9
|
+
import { analyzeAuthError, discoverOAuthEndpoints, MCPManager } from "../../mcp";
|
|
10
|
+
import { connectToServer, disconnectServer, listTools } from "../../mcp/client";
|
|
11
|
+
import {
|
|
12
|
+
addMCPServer,
|
|
13
|
+
readDisabledServers,
|
|
14
|
+
readMCPConfigFile,
|
|
15
|
+
removeMCPServer,
|
|
16
|
+
setServerDisabled,
|
|
17
|
+
updateMCPServer,
|
|
18
|
+
} from "../../mcp/config-writer";
|
|
19
|
+
import { MCPOAuthFlow } from "../../mcp/oauth-flow";
|
|
20
|
+
import type { MCPServerConfig, MCPServerConnection } from "../../mcp/types";
|
|
21
|
+
import type { OAuthCredential } from "../../session/auth-storage";
|
|
22
|
+
import { openPath } from "../../utils/open";
|
|
23
|
+
import { DynamicBorder } from "../components/dynamic-border";
|
|
24
|
+
import { MCPAddWizard } from "../components/mcp-add-wizard";
|
|
25
|
+
import { theme } from "../theme/theme";
|
|
26
|
+
import type { InteractiveModeContext } from "../types";
|
|
27
|
+
|
|
28
|
+
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
29
|
+
const { promise: timeoutPromise, reject } = Promise.withResolvers<T>();
|
|
30
|
+
const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
31
|
+
return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timer));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseCommandArgs(argsString: string): string[] {
|
|
35
|
+
const args: string[] = [];
|
|
36
|
+
let current = "";
|
|
37
|
+
let inQuote: string | null = null;
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < argsString.length; i++) {
|
|
40
|
+
const char = argsString[i];
|
|
41
|
+
|
|
42
|
+
if (inQuote) {
|
|
43
|
+
if (char === inQuote) {
|
|
44
|
+
inQuote = null;
|
|
45
|
+
} else {
|
|
46
|
+
current += char;
|
|
47
|
+
}
|
|
48
|
+
} else if (char === '"' || char === "'") {
|
|
49
|
+
inQuote = char;
|
|
50
|
+
} else if (char === " " || char === "\t") {
|
|
51
|
+
if (current) {
|
|
52
|
+
args.push(current);
|
|
53
|
+
current = "";
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
current += char;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (current) {
|
|
61
|
+
args.push(current);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return args;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type MCPAddScope = "user" | "project";
|
|
68
|
+
type MCPAddTransport = "http" | "sse";
|
|
69
|
+
|
|
70
|
+
type MCPAddParsed = {
|
|
71
|
+
initialName?: string;
|
|
72
|
+
scope: MCPAddScope;
|
|
73
|
+
quickConfig?: MCPServerConfig;
|
|
74
|
+
isCommandQuickAdd?: boolean;
|
|
75
|
+
hasAuthToken?: boolean;
|
|
76
|
+
error?: string;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export class MCPCommandController {
|
|
80
|
+
constructor(private ctx: InteractiveModeContext) {}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Handle /mcp command and route to subcommands
|
|
84
|
+
*/
|
|
85
|
+
async handle(text: string): Promise<void> {
|
|
86
|
+
const parts = text.trim().split(/\s+/);
|
|
87
|
+
const subcommand = parts[1]?.toLowerCase();
|
|
88
|
+
|
|
89
|
+
if (!subcommand || subcommand === "help") {
|
|
90
|
+
this.#showHelp();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
switch (subcommand) {
|
|
95
|
+
case "add":
|
|
96
|
+
await this.#handleAdd(text);
|
|
97
|
+
break;
|
|
98
|
+
case "list":
|
|
99
|
+
await this.#handleList();
|
|
100
|
+
break;
|
|
101
|
+
case "remove":
|
|
102
|
+
case "rm":
|
|
103
|
+
await this.#handleRemove(text);
|
|
104
|
+
break;
|
|
105
|
+
case "test":
|
|
106
|
+
await this.#handleTest(parts[2]);
|
|
107
|
+
break;
|
|
108
|
+
case "reauth":
|
|
109
|
+
await this.#handleReauth(parts[2]);
|
|
110
|
+
break;
|
|
111
|
+
case "unauth":
|
|
112
|
+
await this.#handleUnauth(parts[2]);
|
|
113
|
+
break;
|
|
114
|
+
case "enable":
|
|
115
|
+
await this.#handleSetEnabled(parts[2], true);
|
|
116
|
+
break;
|
|
117
|
+
case "disable":
|
|
118
|
+
await this.#handleSetEnabled(parts[2], false);
|
|
119
|
+
break;
|
|
120
|
+
case "reload":
|
|
121
|
+
await this.#handleReload();
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
this.ctx.showError(`Unknown subcommand: ${subcommand}. Type /mcp help for usage.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Show help text
|
|
130
|
+
*/
|
|
131
|
+
#showHelp(): void {
|
|
132
|
+
const helpText = [
|
|
133
|
+
"",
|
|
134
|
+
theme.bold("MCP Server Management"),
|
|
135
|
+
"",
|
|
136
|
+
"Manage Model Context Protocol (MCP) servers for external tool integrations.",
|
|
137
|
+
"",
|
|
138
|
+
theme.fg("accent", "Commands:"),
|
|
139
|
+
" /mcp add Add a new MCP server (interactive wizard)",
|
|
140
|
+
" /mcp add <name> [--scope project|user] [--url <url> --transport http|sse] [--token <token>] [-- <command...>]",
|
|
141
|
+
" /mcp list List all configured MCP servers",
|
|
142
|
+
" /mcp remove <name> [--scope project|user] Remove an MCP server (default: project)",
|
|
143
|
+
" /mcp test <name> Test connection to an MCP server",
|
|
144
|
+
" /mcp reauth <name> Reauthorize OAuth for an MCP server",
|
|
145
|
+
" /mcp unauth <name> Remove OAuth auth from an MCP server",
|
|
146
|
+
" /mcp enable <name> Enable an MCP server",
|
|
147
|
+
" /mcp disable <name> Disable an MCP server",
|
|
148
|
+
" /mcp reload Force reload and rediscover MCP runtime tools",
|
|
149
|
+
" /mcp help Show this help message",
|
|
150
|
+
"",
|
|
151
|
+
].join("\n");
|
|
152
|
+
|
|
153
|
+
this.#showMessage(helpText);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#parseAddCommand(text: string): MCPAddParsed {
|
|
157
|
+
const prefixMatch = text.match(/^\/mcp\s+add\b\s*(.*)$/i);
|
|
158
|
+
const rest = prefixMatch?.[1]?.trim() ?? "";
|
|
159
|
+
if (!rest) {
|
|
160
|
+
return { scope: "project" };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const tokens = parseCommandArgs(rest);
|
|
164
|
+
if (tokens.length === 0) {
|
|
165
|
+
return { scope: "project" };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let name: string | undefined;
|
|
169
|
+
let scope: MCPAddScope = "project";
|
|
170
|
+
let url: string | undefined;
|
|
171
|
+
let transport: MCPAddTransport = "http";
|
|
172
|
+
let authToken: string | undefined;
|
|
173
|
+
let commandTokens: string[] | undefined;
|
|
174
|
+
|
|
175
|
+
let i = 0;
|
|
176
|
+
if (!tokens[0].startsWith("-")) {
|
|
177
|
+
name = tokens[0];
|
|
178
|
+
i = 1;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
while (i < tokens.length) {
|
|
182
|
+
const argToken = tokens[i];
|
|
183
|
+
if (argToken === "--") {
|
|
184
|
+
commandTokens = tokens.slice(i + 1);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (argToken === "--scope") {
|
|
188
|
+
const value = tokens[i + 1];
|
|
189
|
+
if (!value || (value !== "project" && value !== "user")) {
|
|
190
|
+
return { scope, error: "Invalid --scope value. Use project or user." };
|
|
191
|
+
}
|
|
192
|
+
scope = value;
|
|
193
|
+
i += 2;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (argToken === "--url") {
|
|
197
|
+
const value = tokens[i + 1];
|
|
198
|
+
if (!value) {
|
|
199
|
+
return { scope, error: "Missing value for --url." };
|
|
200
|
+
}
|
|
201
|
+
url = value;
|
|
202
|
+
i += 2;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (argToken === "--transport") {
|
|
206
|
+
const value = tokens[i + 1];
|
|
207
|
+
if (!value || (value !== "http" && value !== "sse")) {
|
|
208
|
+
return { scope, error: "Invalid --transport value. Use http or sse." };
|
|
209
|
+
}
|
|
210
|
+
transport = value;
|
|
211
|
+
i += 2;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (argToken === "--token") {
|
|
215
|
+
const value = tokens[i + 1];
|
|
216
|
+
if (!value) {
|
|
217
|
+
return { scope, error: "Missing value for --token." };
|
|
218
|
+
}
|
|
219
|
+
authToken = value;
|
|
220
|
+
i += 2;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
return { scope, error: `Unknown option: ${argToken}` };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const hasQuick = Boolean(url) || Boolean(commandTokens && commandTokens.length > 0);
|
|
227
|
+
if (!hasQuick) {
|
|
228
|
+
return { scope, initialName: name };
|
|
229
|
+
}
|
|
230
|
+
if (!name) {
|
|
231
|
+
return { scope, error: "Server name required for quick add. Usage: /mcp add <name> ..." };
|
|
232
|
+
}
|
|
233
|
+
if (url && commandTokens && commandTokens.length > 0) {
|
|
234
|
+
return { scope, error: "Use either --url or -- <command...>, not both." };
|
|
235
|
+
}
|
|
236
|
+
if (authToken && !url) {
|
|
237
|
+
return { scope, error: "--token requires --url (HTTP/SSE transport)." };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (commandTokens && commandTokens.length > 0) {
|
|
241
|
+
const [command, ...args] = commandTokens;
|
|
242
|
+
const config: MCPServerConfig = {
|
|
243
|
+
type: "stdio",
|
|
244
|
+
command,
|
|
245
|
+
args: args.length > 0 ? args : undefined,
|
|
246
|
+
};
|
|
247
|
+
return { scope, initialName: name, quickConfig: config, isCommandQuickAdd: true };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const useHttpTransport = transport === "http";
|
|
251
|
+
let normalizedUrl = url!;
|
|
252
|
+
if (!/^https?:\/\//i.test(normalizedUrl)) {
|
|
253
|
+
normalizedUrl = `https://${normalizedUrl}`;
|
|
254
|
+
}
|
|
255
|
+
const config: MCPServerConfig = {
|
|
256
|
+
type: useHttpTransport ? "http" : "sse",
|
|
257
|
+
url: normalizedUrl,
|
|
258
|
+
headers: authToken ? { Authorization: `Bearer ${authToken}` } : undefined,
|
|
259
|
+
};
|
|
260
|
+
return {
|
|
261
|
+
scope,
|
|
262
|
+
initialName: name,
|
|
263
|
+
quickConfig: config,
|
|
264
|
+
isCommandQuickAdd: false,
|
|
265
|
+
hasAuthToken: Boolean(authToken),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Handle /mcp add - Launch interactive wizard or quick-add from args
|
|
271
|
+
*/
|
|
272
|
+
async #handleAdd(text: string): Promise<void> {
|
|
273
|
+
const parsed = this.#parseAddCommand(text);
|
|
274
|
+
if (parsed.error) {
|
|
275
|
+
this.ctx.showError(parsed.error);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (parsed.quickConfig && parsed.initialName) {
|
|
279
|
+
let finalConfig = parsed.quickConfig;
|
|
280
|
+
|
|
281
|
+
// Quick-add with URL should still perform auth detection and OAuth flow,
|
|
282
|
+
// matching wizard behavior. Command quick-add intentionally skips this.
|
|
283
|
+
if (!parsed.isCommandQuickAdd && (finalConfig.type === "http" || finalConfig.type === "sse")) {
|
|
284
|
+
try {
|
|
285
|
+
await this.#handleTestConnection(finalConfig);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (parsed.hasAuthToken) {
|
|
288
|
+
this.ctx.showError(
|
|
289
|
+
`Authentication failed for "${parsed.initialName}": ${error instanceof Error ? error.message : String(error)}`,
|
|
290
|
+
);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const authResult = analyzeAuthError(error as Error);
|
|
294
|
+
if (authResult.requiresAuth) {
|
|
295
|
+
let oauth = authResult.authType === "oauth" ? (authResult.oauth ?? null) : null;
|
|
296
|
+
if (!oauth && finalConfig.url) {
|
|
297
|
+
try {
|
|
298
|
+
oauth = await discoverOAuthEndpoints(finalConfig.url);
|
|
299
|
+
} catch {
|
|
300
|
+
// Ignore discovery error and handle below.
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!oauth) {
|
|
305
|
+
this.ctx.showError(
|
|
306
|
+
`Authentication required for "${parsed.initialName}", but OAuth endpoints could not be discovered. ` +
|
|
307
|
+
`Use /mcp add ${parsed.initialName} (wizard) or configure auth manually.`,
|
|
308
|
+
);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const credentialId = await this.#handleOAuthFlow(
|
|
314
|
+
oauth.authorizationUrl,
|
|
315
|
+
oauth.tokenUrl,
|
|
316
|
+
oauth.clientId ?? "",
|
|
317
|
+
"",
|
|
318
|
+
oauth.scopes ?? "",
|
|
319
|
+
);
|
|
320
|
+
finalConfig = {
|
|
321
|
+
...finalConfig,
|
|
322
|
+
auth: {
|
|
323
|
+
type: "oauth",
|
|
324
|
+
credentialId,
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
} catch (oauthError) {
|
|
328
|
+
this.ctx.showError(
|
|
329
|
+
`OAuth flow failed for "${parsed.initialName}": ${oauthError instanceof Error ? oauthError.message : String(oauthError)}`,
|
|
330
|
+
);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
await this.#handleWizardComplete(parsed.initialName, finalConfig, parsed.scope);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Save current editor state
|
|
342
|
+
const done = () => {
|
|
343
|
+
this.ctx.editorContainer.clear();
|
|
344
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
345
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Create wizard with OAuth handler and connection test
|
|
349
|
+
const wizard = new MCPAddWizard(
|
|
350
|
+
async (name: string, config: MCPServerConfig, scope: "user" | "project") => {
|
|
351
|
+
done();
|
|
352
|
+
await this.#handleWizardComplete(name, config, scope);
|
|
353
|
+
},
|
|
354
|
+
() => {
|
|
355
|
+
done();
|
|
356
|
+
this.#handleWizardCancel();
|
|
357
|
+
},
|
|
358
|
+
async (authUrl: string, tokenUrl: string, clientId: string, clientSecret: string, scopes: string) => {
|
|
359
|
+
return await this.#handleOAuthFlow(authUrl, tokenUrl, clientId, clientSecret, scopes);
|
|
360
|
+
},
|
|
361
|
+
async (config: MCPServerConfig) => {
|
|
362
|
+
return await this.#handleTestConnection(config);
|
|
363
|
+
},
|
|
364
|
+
() => {
|
|
365
|
+
this.ctx.ui.requestRender();
|
|
366
|
+
},
|
|
367
|
+
parsed.initialName,
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
// Replace editor with wizard
|
|
371
|
+
this.ctx.editorContainer.clear();
|
|
372
|
+
this.ctx.editorContainer.addChild(wizard);
|
|
373
|
+
this.ctx.ui.setFocus(wizard);
|
|
374
|
+
this.ctx.ui.requestRender();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Handle OAuth authentication flow for MCP server
|
|
379
|
+
*/
|
|
380
|
+
async #handleOAuthFlow(
|
|
381
|
+
authUrl: string,
|
|
382
|
+
tokenUrl: string,
|
|
383
|
+
clientId: string,
|
|
384
|
+
clientSecret: string,
|
|
385
|
+
scopes: string,
|
|
386
|
+
): Promise<string> {
|
|
387
|
+
const authStorage = this.ctx.session.modelRegistry.authStorage;
|
|
388
|
+
let parsedAuthUrl: URL;
|
|
389
|
+
|
|
390
|
+
// Validate OAuth URLs
|
|
391
|
+
try {
|
|
392
|
+
parsedAuthUrl = new URL(authUrl);
|
|
393
|
+
new URL(tokenUrl);
|
|
394
|
+
} catch (_error) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
`Invalid OAuth URLs. Please check:\n Authorization URL: ${authUrl}\n Token URL: ${tokenUrl}`,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const resolvedClientId = clientId.trim() || parsedAuthUrl.searchParams.get("client_id") || undefined;
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
// Create OAuth flow
|
|
404
|
+
const flow = new MCPOAuthFlow(
|
|
405
|
+
{
|
|
406
|
+
authorizationUrl: authUrl,
|
|
407
|
+
tokenUrl: tokenUrl,
|
|
408
|
+
clientId: resolvedClientId,
|
|
409
|
+
clientSecret: clientSecret || undefined,
|
|
410
|
+
scopes: scopes || undefined,
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
onAuth: (info: { url: string; instructions?: string }) => {
|
|
414
|
+
// Show auth URL prominently in chat
|
|
415
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
416
|
+
this.ctx.chatContainer.addChild(
|
|
417
|
+
new Text(theme.fg("accent", "━━━ OAuth Authorization Required ━━━"), 1, 0),
|
|
418
|
+
);
|
|
419
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
420
|
+
this.ctx.chatContainer.addChild(
|
|
421
|
+
new Text(theme.fg("muted", "Preparing browser authorization..."), 1, 0),
|
|
422
|
+
);
|
|
423
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
424
|
+
this.ctx.chatContainer.addChild(
|
|
425
|
+
new Text(
|
|
426
|
+
theme.fg("muted", "Waiting for authorization... (Press Ctrl+C to cancel, 5 minute timeout)"),
|
|
427
|
+
1,
|
|
428
|
+
0,
|
|
429
|
+
),
|
|
430
|
+
);
|
|
431
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
432
|
+
this.ctx.chatContainer.addChild(
|
|
433
|
+
new Text(theme.fg("accent", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), 1, 0),
|
|
434
|
+
);
|
|
435
|
+
this.ctx.ui.requestRender();
|
|
436
|
+
const isWindows = process.platform === "win32";
|
|
437
|
+
|
|
438
|
+
// Try to open browser automatically
|
|
439
|
+
try {
|
|
440
|
+
openPath(info.url);
|
|
441
|
+
|
|
442
|
+
// Show confirmation that browser should open
|
|
443
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
444
|
+
this.ctx.chatContainer.addChild(
|
|
445
|
+
new Text(theme.fg("success", "→ Opening browser automatically..."), 1, 0),
|
|
446
|
+
);
|
|
447
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
448
|
+
this.ctx.chatContainer.addChild(
|
|
449
|
+
new Text(theme.fg("muted", "Alternative if browser did not open:"), 1, 0),
|
|
450
|
+
);
|
|
451
|
+
this.ctx.chatContainer.addChild(
|
|
452
|
+
new Text(theme.fg("success", "Copy this exact URL in your browser:"), 1, 0),
|
|
453
|
+
);
|
|
454
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("accent", info.url), 1, 0));
|
|
455
|
+
if (isWindows) {
|
|
456
|
+
const openCmd = `rundll32.exe url.dll,FileProtocolHandler "${info.url.replace(/"/g, '""')}"`;
|
|
457
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
458
|
+
this.ctx.chatContainer.addChild(new Text("Windows manual open command:", 1, 0));
|
|
459
|
+
this.ctx.chatContainer.addChild(new Text(openCmd, 1, 0));
|
|
460
|
+
}
|
|
461
|
+
this.ctx.ui.requestRender();
|
|
462
|
+
} catch (_error) {
|
|
463
|
+
// Show error if browser doesn't open
|
|
464
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
465
|
+
this.ctx.chatContainer.addChild(
|
|
466
|
+
new Text(theme.fg("warning", "→ Could not open browser automatically"), 1, 0),
|
|
467
|
+
);
|
|
468
|
+
this.ctx.chatContainer.addChild(
|
|
469
|
+
new Text(theme.fg("success", "Copy this exact URL in your browser:"), 1, 0),
|
|
470
|
+
);
|
|
471
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("accent", info.url), 1, 0));
|
|
472
|
+
if (isWindows) {
|
|
473
|
+
const openCmd = `rundll32.exe url.dll,FileProtocolHandler "${info.url.replace(/"/g, '""')}"`;
|
|
474
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
475
|
+
this.ctx.chatContainer.addChild(new Text("Windows manual open command:", 1, 0));
|
|
476
|
+
this.ctx.chatContainer.addChild(new Text(openCmd, 1, 0));
|
|
477
|
+
}
|
|
478
|
+
this.ctx.ui.requestRender();
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
onProgress: (message: string) => {
|
|
482
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
483
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("muted", message), 1, 0));
|
|
484
|
+
this.ctx.ui.requestRender();
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
// Execute OAuth flow with 5 minute timeout
|
|
490
|
+
const credentials = await withTimeout(flow.login(), 5 * 60 * 1000, "OAuth flow timed out after 5 minutes");
|
|
491
|
+
|
|
492
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
493
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("success", "✓ Authorization completed in browser."), 1, 0));
|
|
494
|
+
this.ctx.ui.requestRender();
|
|
495
|
+
|
|
496
|
+
// Generate a unique credential ID
|
|
497
|
+
const credentialId = `mcp_oauth_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
498
|
+
|
|
499
|
+
// Store credentials in auth storage
|
|
500
|
+
const oauthCredential: OAuthCredential = {
|
|
501
|
+
type: "oauth",
|
|
502
|
+
...credentials,
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// Store under a synthetic provider name
|
|
506
|
+
await authStorage.set(credentialId, oauthCredential);
|
|
507
|
+
|
|
508
|
+
return credentialId;
|
|
509
|
+
} catch (error) {
|
|
510
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
511
|
+
|
|
512
|
+
// Provide helpful error messages based on failure type
|
|
513
|
+
if (errorMsg.includes("timeout") || errorMsg.includes("timed out")) {
|
|
514
|
+
throw new Error("OAuth flow timed out. Please try again.");
|
|
515
|
+
} else if (errorMsg.includes("403") || errorMsg.includes("unauthorized")) {
|
|
516
|
+
throw new Error("OAuth authorization failed. Please check your client credentials.");
|
|
517
|
+
} else if (errorMsg.includes("invalid_grant")) {
|
|
518
|
+
throw new Error("OAuth authorization code is invalid or expired. Please try again.");
|
|
519
|
+
} else if (errorMsg.includes("ECONNREFUSED") || errorMsg.includes("fetch failed")) {
|
|
520
|
+
throw new Error("Could not connect to OAuth server. Please check the URLs and your network connection.");
|
|
521
|
+
} else {
|
|
522
|
+
throw new Error(`OAuth authentication failed: ${errorMsg}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Test connection to an MCP server.
|
|
529
|
+
* Throws an error if connection fails (used for auto-detection).
|
|
530
|
+
*/
|
|
531
|
+
async #handleTestConnection(config: MCPServerConfig): Promise<void> {
|
|
532
|
+
// Create temporary connection using a test name
|
|
533
|
+
const testName = `test_${Date.now()}`;
|
|
534
|
+
let resolvedConfig: MCPServerConfig;
|
|
535
|
+
if (this.ctx.mcpManager) {
|
|
536
|
+
resolvedConfig = await this.ctx.mcpManager.prepareConfig(config);
|
|
537
|
+
} else {
|
|
538
|
+
const tempManager = new MCPManager(getProjectDir());
|
|
539
|
+
tempManager.setAuthStorage(this.ctx.session.modelRegistry.authStorage);
|
|
540
|
+
resolvedConfig = await tempManager.prepareConfig(config);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const connection = await connectToServer(testName, resolvedConfig);
|
|
544
|
+
await disconnectServer(connection);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async #findConfiguredServer(
|
|
548
|
+
name: string,
|
|
549
|
+
): Promise<{ filePath: string; scope: "user" | "project"; config: MCPServerConfig } | null> {
|
|
550
|
+
const cwd = getProjectDir();
|
|
551
|
+
const userPath = getMCPConfigPath("user", cwd);
|
|
552
|
+
const projectPath = getMCPConfigPath("project", cwd);
|
|
553
|
+
|
|
554
|
+
const [userConfig, projectConfig] = await Promise.all([
|
|
555
|
+
readMCPConfigFile(userPath),
|
|
556
|
+
readMCPConfigFile(projectPath),
|
|
557
|
+
]);
|
|
558
|
+
|
|
559
|
+
if (userConfig.mcpServers?.[name]) {
|
|
560
|
+
return { filePath: userPath, scope: "user", config: userConfig.mcpServers[name] };
|
|
561
|
+
}
|
|
562
|
+
if (projectConfig.mcpServers?.[name]) {
|
|
563
|
+
return { filePath: projectPath, scope: "project", config: projectConfig.mcpServers[name] };
|
|
564
|
+
}
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async #removeManagedOAuthCredential(credentialId: string | undefined): Promise<void> {
|
|
569
|
+
if (!credentialId || !credentialId.startsWith("mcp_oauth_")) return;
|
|
570
|
+
await this.ctx.session.modelRegistry.authStorage.remove(credentialId);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
#stripOAuthAuth(config: MCPServerConfig): MCPServerConfig {
|
|
574
|
+
const next = { ...config } as MCPServerConfig & { auth?: { type: "oauth" | "apikey"; credentialId?: string } };
|
|
575
|
+
delete next.auth;
|
|
576
|
+
return next;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async #resolveOAuthEndpointsFromServer(config: MCPServerConfig): Promise<{
|
|
580
|
+
authorizationUrl: string;
|
|
581
|
+
tokenUrl: string;
|
|
582
|
+
clientId?: string;
|
|
583
|
+
scopes?: string;
|
|
584
|
+
}> {
|
|
585
|
+
// First test if server actually needs auth by connecting without OAuth
|
|
586
|
+
let connectionSucceeded = false;
|
|
587
|
+
let connectionError: Error | undefined;
|
|
588
|
+
try {
|
|
589
|
+
await this.#handleTestConnection(this.#stripOAuthAuth(config));
|
|
590
|
+
connectionSucceeded = true;
|
|
591
|
+
} catch (error) {
|
|
592
|
+
connectionError = error as Error;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Server connected fine without auth — reauth is not needed
|
|
596
|
+
if (connectionSucceeded) {
|
|
597
|
+
throw new Error("Server connection succeeded without OAuth; reauthorization is not required.");
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Analyze the connection error to extract OAuth endpoints
|
|
601
|
+
const authResult = analyzeAuthError(connectionError!);
|
|
602
|
+
let oauth = authResult.authType === "oauth" ? (authResult.oauth ?? null) : null;
|
|
603
|
+
|
|
604
|
+
if (!oauth && (config.type === "http" || config.type === "sse") && config.url) {
|
|
605
|
+
oauth = await discoverOAuthEndpoints(config.url);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (!oauth) {
|
|
609
|
+
throw new Error("Could not discover OAuth endpoints from server response.");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return oauth;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async #waitForServerConnectionWithAnimation(
|
|
616
|
+
name: string,
|
|
617
|
+
options?: { suppressDisconnectedWarning?: boolean },
|
|
618
|
+
): Promise<"connected" | "connecting" | "disconnected"> {
|
|
619
|
+
if (!this.ctx.mcpManager) return "disconnected";
|
|
620
|
+
|
|
621
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
622
|
+
const statusText = new Text(theme.fg("muted", `| Connecting to "${name}"...`), 1, 0);
|
|
623
|
+
this.ctx.chatContainer.addChild(statusText);
|
|
624
|
+
this.ctx.ui.requestRender();
|
|
625
|
+
|
|
626
|
+
const frames = ["|", "/", "-", "\\"];
|
|
627
|
+
let frame = 0;
|
|
628
|
+
const interval = setInterval(() => {
|
|
629
|
+
statusText.setText(theme.fg("muted", `${frames[frame % frames.length]} Connecting to "${name}"...`));
|
|
630
|
+
frame++;
|
|
631
|
+
this.ctx.ui.requestRender();
|
|
632
|
+
}, 120);
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
try {
|
|
636
|
+
await withTimeout(this.ctx.mcpManager.waitForConnection(name), 10_000, "Connection still pending");
|
|
637
|
+
} catch {
|
|
638
|
+
// Ignore timeout/errors here and use status check below.
|
|
639
|
+
}
|
|
640
|
+
const state = this.ctx.mcpManager.getConnectionStatus(name);
|
|
641
|
+
if (state === "connected") {
|
|
642
|
+
// Connection may complete after initial reload; rebind runtime MCP tools now.
|
|
643
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
644
|
+
}
|
|
645
|
+
if (state === "connected") {
|
|
646
|
+
statusText.setText(theme.fg("success", `✓ Connected to "${name}"`));
|
|
647
|
+
} else if (state === "connecting") {
|
|
648
|
+
statusText.setText(theme.fg("muted", `◌ "${name}" is still connecting...`));
|
|
649
|
+
} else {
|
|
650
|
+
statusText.setText(
|
|
651
|
+
options?.suppressDisconnectedWarning
|
|
652
|
+
? theme.fg("muted", `◌ Connection check complete for "${name}"`)
|
|
653
|
+
: theme.fg("warning", `⚠ Could not connect to "${name}" yet`),
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
this.ctx.ui.requestRender();
|
|
657
|
+
return state;
|
|
658
|
+
} finally {
|
|
659
|
+
clearInterval(interval);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async #syncManagerConnection(name: string, config: MCPServerConfig): Promise<void> {
|
|
664
|
+
if (!this.ctx.mcpManager) return;
|
|
665
|
+
if (this.ctx.mcpManager.getConnectionStatus(name) !== "disconnected") return;
|
|
666
|
+
await this.ctx.mcpManager.connectServers({ [name]: config }, {});
|
|
667
|
+
if (this.ctx.mcpManager.getConnectionStatus(name) === "connected") {
|
|
668
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async #handleWizardComplete(name: string, config: MCPServerConfig, scope: "user" | "project"): Promise<void> {
|
|
673
|
+
try {
|
|
674
|
+
// Determine file path
|
|
675
|
+
const cwd = getProjectDir();
|
|
676
|
+
const filePath = getMCPConfigPath(scope, cwd);
|
|
677
|
+
|
|
678
|
+
// Add server to config
|
|
679
|
+
await addMCPServer(filePath, name, config);
|
|
680
|
+
|
|
681
|
+
// Reload MCP manager
|
|
682
|
+
await this.#reloadMCP();
|
|
683
|
+
const state =
|
|
684
|
+
config.enabled === false
|
|
685
|
+
? "disconnected"
|
|
686
|
+
: await this.#waitForServerConnectionWithAnimation(name, { suppressDisconnectedWarning: true });
|
|
687
|
+
let isConnected = state === "connected";
|
|
688
|
+
const isConnecting = state === "connecting";
|
|
689
|
+
|
|
690
|
+
// Fallback: if manager state is still disconnected but direct test works,
|
|
691
|
+
// report as connected to avoid false-negative messaging.
|
|
692
|
+
if (!isConnected && !isConnecting && config.enabled !== false) {
|
|
693
|
+
try {
|
|
694
|
+
await this.#handleTestConnection(config);
|
|
695
|
+
isConnected = true;
|
|
696
|
+
await this.#syncManagerConnection(name, config);
|
|
697
|
+
} catch {
|
|
698
|
+
// Keep disconnected status
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Show success message
|
|
703
|
+
const scopeLabel = scope === "user" ? "user" : "project";
|
|
704
|
+
const lines = ["", theme.fg("success", `✓ Added server "${name}" to ${scopeLabel} config`), ""];
|
|
705
|
+
|
|
706
|
+
if (isConnected) {
|
|
707
|
+
lines.push(theme.fg("success", `✓ Successfully connected to server`));
|
|
708
|
+
lines.push("");
|
|
709
|
+
} else if (isConnecting) {
|
|
710
|
+
lines.push(theme.fg("muted", `◌ Server is connecting in background...`));
|
|
711
|
+
lines.push(theme.fg("muted", ` Run ${theme.fg("accent", `/mcp test ${name}`)} in a few seconds.`));
|
|
712
|
+
lines.push("");
|
|
713
|
+
} else {
|
|
714
|
+
lines.push(theme.fg("warning", `⚠ Server added but not yet connected`));
|
|
715
|
+
lines.push(theme.fg("muted", ` Run ${theme.fg("accent", `/mcp test ${name}`)} to test the connection.`));
|
|
716
|
+
lines.push("");
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
lines.push(theme.fg("muted", `Run ${theme.fg("accent", "/mcp list")} to see all configured servers.`));
|
|
720
|
+
lines.push("");
|
|
721
|
+
|
|
722
|
+
this.#showMessage(lines.join("\n"));
|
|
723
|
+
} catch (error) {
|
|
724
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
725
|
+
|
|
726
|
+
// Provide helpful error messages
|
|
727
|
+
let helpText = "";
|
|
728
|
+
if (errorMsg.includes("EACCES") || errorMsg.includes("permission denied")) {
|
|
729
|
+
helpText = "\n\nTip: Check file permissions for the config directory.";
|
|
730
|
+
} else if (errorMsg.includes("ENOSPC")) {
|
|
731
|
+
helpText = "\n\nTip: Insufficient disk space.";
|
|
732
|
+
} else if (errorMsg.includes("already exists")) {
|
|
733
|
+
helpText = `\n\nTip: Use ${theme.fg("accent", "/mcp list")} to see existing servers.`;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
this.ctx.showError(`Failed to add server: ${errorMsg}${helpText}`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
#handleWizardCancel(): void {
|
|
741
|
+
this.#showMessage(
|
|
742
|
+
[
|
|
743
|
+
"",
|
|
744
|
+
theme.fg("muted", "Server creation cancelled."),
|
|
745
|
+
"",
|
|
746
|
+
theme.fg("dim", "Tip: Press Ctrl+C or Esc anytime to cancel"),
|
|
747
|
+
"",
|
|
748
|
+
].join("\n"),
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Handle /mcp list - Show all configured servers
|
|
754
|
+
*/
|
|
755
|
+
async #handleList(): Promise<void> {
|
|
756
|
+
try {
|
|
757
|
+
const cwd = getProjectDir();
|
|
758
|
+
|
|
759
|
+
// Load from both user and project configs
|
|
760
|
+
const userPath = getMCPConfigPath("user", cwd);
|
|
761
|
+
const projectPath = getMCPConfigPath("project", cwd);
|
|
762
|
+
|
|
763
|
+
const [userConfig, projectConfig] = await Promise.all([
|
|
764
|
+
readMCPConfigFile(userPath),
|
|
765
|
+
readMCPConfigFile(projectPath),
|
|
766
|
+
]);
|
|
767
|
+
|
|
768
|
+
const userServers = Object.keys(userConfig.mcpServers ?? {});
|
|
769
|
+
const projectServers = Object.keys(projectConfig.mcpServers ?? {});
|
|
770
|
+
|
|
771
|
+
// Collect runtime-discovered servers not in config files
|
|
772
|
+
const configServerNames = new Set([...userServers, ...projectServers]);
|
|
773
|
+
const discoveredServers: { name: string; source: SourceMeta }[] = [];
|
|
774
|
+
if (this.ctx.mcpManager) {
|
|
775
|
+
for (const name of this.ctx.mcpManager.getAllServerNames()) {
|
|
776
|
+
if (configServerNames.has(name)) continue;
|
|
777
|
+
const source = this.ctx.mcpManager.getSource(name);
|
|
778
|
+
if (source) {
|
|
779
|
+
discoveredServers.push({ name, source });
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (
|
|
785
|
+
userServers.length === 0 &&
|
|
786
|
+
projectServers.length === 0 &&
|
|
787
|
+
discoveredServers.length === 0 &&
|
|
788
|
+
(userConfig.disabledServers ?? []).length === 0
|
|
789
|
+
) {
|
|
790
|
+
this.#showMessage(
|
|
791
|
+
[
|
|
792
|
+
"",
|
|
793
|
+
theme.fg("muted", "No MCP servers configured."),
|
|
794
|
+
"",
|
|
795
|
+
`Use ${theme.fg("accent", "/mcp add")} to add a server.`,
|
|
796
|
+
"",
|
|
797
|
+
].join("\n"),
|
|
798
|
+
);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const lines: string[] = ["", theme.bold("Configured MCP Servers"), ""];
|
|
803
|
+
|
|
804
|
+
// Show user-level servers
|
|
805
|
+
if (userServers.length > 0) {
|
|
806
|
+
lines.push(theme.fg("accent", "User level") + theme.fg("muted", ` (~/.arcane/mcp.json):`));
|
|
807
|
+
for (const name of userServers) {
|
|
808
|
+
const config = userConfig.mcpServers![name];
|
|
809
|
+
const type = config.type ?? "stdio";
|
|
810
|
+
const state =
|
|
811
|
+
config.enabled === false
|
|
812
|
+
? "inactive"
|
|
813
|
+
: (this.ctx.mcpManager?.getConnectionStatus(name) ?? "disconnected");
|
|
814
|
+
const status =
|
|
815
|
+
state === "inactive"
|
|
816
|
+
? theme.fg("warning", " ◌ inactive")
|
|
817
|
+
: state === "connected"
|
|
818
|
+
? theme.fg("success", " ● connected")
|
|
819
|
+
: state === "connecting"
|
|
820
|
+
? theme.fg("muted", " ◌ connecting")
|
|
821
|
+
: theme.fg("muted", " ○ not connected");
|
|
822
|
+
lines.push(` ${theme.fg("accent", name)}${status} ${theme.fg("dim", `[${type}]`)}`);
|
|
823
|
+
}
|
|
824
|
+
lines.push("");
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Show project-level servers
|
|
828
|
+
if (projectServers.length > 0) {
|
|
829
|
+
lines.push(theme.fg("accent", "Project level") + theme.fg("muted", ` (.arcane/mcp.json):`));
|
|
830
|
+
for (const name of projectServers) {
|
|
831
|
+
const config = projectConfig.mcpServers![name];
|
|
832
|
+
const type = config.type ?? "stdio";
|
|
833
|
+
const state =
|
|
834
|
+
config.enabled === false
|
|
835
|
+
? "inactive"
|
|
836
|
+
: (this.ctx.mcpManager?.getConnectionStatus(name) ?? "disconnected");
|
|
837
|
+
const status =
|
|
838
|
+
state === "inactive"
|
|
839
|
+
? theme.fg("warning", " ◌ inactive")
|
|
840
|
+
: state === "connected"
|
|
841
|
+
? theme.fg("success", " ● connected")
|
|
842
|
+
: state === "connecting"
|
|
843
|
+
? theme.fg("muted", " ◌ connecting")
|
|
844
|
+
: theme.fg("muted", " ○ not connected");
|
|
845
|
+
lines.push(` ${theme.fg("accent", name)}${status} ${theme.fg("dim", `[${type}]`)}`);
|
|
846
|
+
}
|
|
847
|
+
lines.push("");
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Show discovered servers (from .claude.json, .cursor/mcp.json, .vscode/mcp.json, etc.)
|
|
851
|
+
if (discoveredServers.length > 0) {
|
|
852
|
+
// Group by source display name + path
|
|
853
|
+
const bySource = new Map<string, typeof discoveredServers>();
|
|
854
|
+
for (const entry of discoveredServers) {
|
|
855
|
+
const key = `${entry.source.providerName}|${entry.source.path}`;
|
|
856
|
+
let group = bySource.get(key);
|
|
857
|
+
if (!group) {
|
|
858
|
+
group = [];
|
|
859
|
+
bySource.set(key, group);
|
|
860
|
+
}
|
|
861
|
+
group.push(entry);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
for (const [key, entries] of bySource) {
|
|
865
|
+
const sepIdx = key.indexOf("|");
|
|
866
|
+
const providerName = key.slice(0, sepIdx);
|
|
867
|
+
const sourcePath = key.slice(sepIdx + 1);
|
|
868
|
+
const shortPath = sourcePath.replace(process.env.HOME ?? "", "~");
|
|
869
|
+
lines.push(theme.fg("accent", providerName) + theme.fg("muted", ` (${shortPath}):`));
|
|
870
|
+
for (const { name } of entries) {
|
|
871
|
+
const state = this.ctx.mcpManager!.getConnectionStatus(name);
|
|
872
|
+
const status =
|
|
873
|
+
state === "connected"
|
|
874
|
+
? theme.fg("success", " ● connected")
|
|
875
|
+
: state === "connecting"
|
|
876
|
+
? theme.fg("muted", " ◌ connecting")
|
|
877
|
+
: theme.fg("muted", " ○ not connected");
|
|
878
|
+
lines.push(` ${theme.fg("accent", name)}${status}`);
|
|
879
|
+
}
|
|
880
|
+
lines.push("");
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Show servers disabled via /mcp disable (from third-party configs)
|
|
885
|
+
const disabledServers = await readDisabledServers(userPath);
|
|
886
|
+
const relevantDisabled = disabledServers.filter(n => !configServerNames.has(n));
|
|
887
|
+
if (relevantDisabled.length > 0) {
|
|
888
|
+
lines.push(theme.fg("accent", "Disabled") + theme.fg("muted", " (discovered servers):"));
|
|
889
|
+
for (const name of relevantDisabled) {
|
|
890
|
+
lines.push(` ${theme.fg("accent", name)}${theme.fg("warning", " ◌ disabled")}`);
|
|
891
|
+
}
|
|
892
|
+
lines.push("");
|
|
893
|
+
}
|
|
894
|
+
this.#showMessage(lines.join("\n"));
|
|
895
|
+
} catch (error) {
|
|
896
|
+
this.ctx.showError(`Failed to list servers: ${error instanceof Error ? error.message : String(error)}`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Handle /mcp remove <name> - Remove a server
|
|
902
|
+
*/
|
|
903
|
+
async #handleRemove(text: string): Promise<void> {
|
|
904
|
+
const match = text.match(/^\/mcp\s+(?:remove|rm)\b\s*(.*)$/i);
|
|
905
|
+
const rest = match?.[1]?.trim() ?? "";
|
|
906
|
+
const tokens = parseCommandArgs(rest);
|
|
907
|
+
|
|
908
|
+
let name: string | undefined;
|
|
909
|
+
let scope: "project" | "user" = "project";
|
|
910
|
+
let i = 0;
|
|
911
|
+
|
|
912
|
+
if (tokens.length > 0 && !tokens[0].startsWith("-")) {
|
|
913
|
+
name = tokens[0];
|
|
914
|
+
i = 1;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
while (i < tokens.length) {
|
|
918
|
+
const token = tokens[i];
|
|
919
|
+
if (token === "--scope") {
|
|
920
|
+
const value = tokens[i + 1];
|
|
921
|
+
if (!value || (value !== "project" && value !== "user")) {
|
|
922
|
+
this.ctx.showError("Invalid --scope value. Use project or user.");
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
scope = value;
|
|
926
|
+
i += 2;
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
this.ctx.showError(`Unknown option: ${token}`);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (!name) {
|
|
934
|
+
this.ctx.showError("Server name required. Usage: /mcp remove <name> [--scope project|user]");
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
try {
|
|
939
|
+
const cwd = getProjectDir();
|
|
940
|
+
const userPath = getMCPConfigPath("user", cwd);
|
|
941
|
+
const projectPath = getMCPConfigPath("project", cwd);
|
|
942
|
+
const filePath = scope === "user" ? userPath : projectPath;
|
|
943
|
+
const config = await readMCPConfigFile(filePath);
|
|
944
|
+
if (!config.mcpServers?.[name]) {
|
|
945
|
+
this.ctx.showError(`Server "${name}" not found in ${scope} config.`);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Disconnect if connected
|
|
950
|
+
if (this.ctx.mcpManager?.getConnection(name)) {
|
|
951
|
+
await this.ctx.mcpManager.disconnectServer(name);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Remove from config
|
|
955
|
+
await removeMCPServer(filePath, name);
|
|
956
|
+
|
|
957
|
+
// Reload MCP manager
|
|
958
|
+
await this.#reloadMCP();
|
|
959
|
+
|
|
960
|
+
this.#showMessage(["", theme.fg("success", `✓ Removed server "${name}" from ${scope} config`), ""].join("\n"));
|
|
961
|
+
} catch (error) {
|
|
962
|
+
this.ctx.showError(`Failed to remove server: ${error instanceof Error ? error.message : String(error)}`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Handle /mcp test <name> - Test connection to a server
|
|
968
|
+
*/
|
|
969
|
+
async #handleTest(name: string | undefined): Promise<void> {
|
|
970
|
+
if (!name) {
|
|
971
|
+
this.ctx.showError("Server name required. Usage: /mcp test <name>");
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const originalOnEscape = this.ctx.editor.onEscape;
|
|
976
|
+
const abortController = new AbortController();
|
|
977
|
+
this.ctx.editor.onEscape = () => {
|
|
978
|
+
abortController.abort();
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
let connection: MCPServerConnection | undefined;
|
|
982
|
+
try {
|
|
983
|
+
const cwd = getProjectDir();
|
|
984
|
+
const userPath = getMCPConfigPath("user", cwd);
|
|
985
|
+
const projectPath = getMCPConfigPath("project", cwd);
|
|
986
|
+
|
|
987
|
+
// Find the server config
|
|
988
|
+
const [userConfig, projectConfig] = await Promise.all([
|
|
989
|
+
readMCPConfigFile(userPath),
|
|
990
|
+
readMCPConfigFile(projectPath),
|
|
991
|
+
]);
|
|
992
|
+
|
|
993
|
+
const config = userConfig.mcpServers?.[name] ?? projectConfig.mcpServers?.[name];
|
|
994
|
+
|
|
995
|
+
if (!config) {
|
|
996
|
+
this.ctx.showError(
|
|
997
|
+
`Server "${name}" not found.\n\nTip: Run ${theme.fg("accent", "/mcp list")} to see available servers.`,
|
|
998
|
+
);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
if (config.enabled === false) {
|
|
1002
|
+
this.ctx.showError(`Server "${name}" is disabled. Run /mcp enable ${name} first.`);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
this.#showMessage(
|
|
1007
|
+
["", theme.fg("muted", `Testing connection to "${name}"... (esc to cancel)`), ""].join("\n"),
|
|
1008
|
+
);
|
|
1009
|
+
|
|
1010
|
+
// Resolve auth config if needed
|
|
1011
|
+
let resolvedConfig: MCPServerConfig;
|
|
1012
|
+
if (this.ctx.mcpManager) {
|
|
1013
|
+
resolvedConfig = await this.ctx.mcpManager.prepareConfig(config);
|
|
1014
|
+
} else {
|
|
1015
|
+
const tempManager = new MCPManager(getProjectDir());
|
|
1016
|
+
tempManager.setAuthStorage(this.ctx.session.modelRegistry.authStorage);
|
|
1017
|
+
resolvedConfig = await tempManager.prepareConfig(config);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Create temporary connection
|
|
1021
|
+
connection = await connectToServer(name, resolvedConfig, { signal: abortController.signal });
|
|
1022
|
+
|
|
1023
|
+
// List tools to verify connection
|
|
1024
|
+
const tools = await listTools(connection, { signal: abortController.signal });
|
|
1025
|
+
|
|
1026
|
+
const lines = [
|
|
1027
|
+
"",
|
|
1028
|
+
theme.fg("success", `✓ Successfully connected to "${name}"`),
|
|
1029
|
+
"",
|
|
1030
|
+
` Server: ${connection.serverInfo.name} v${connection.serverInfo.version}`,
|
|
1031
|
+
` Tools: ${tools.length}`,
|
|
1032
|
+
];
|
|
1033
|
+
|
|
1034
|
+
// Show tool names if there are any
|
|
1035
|
+
if (tools.length > 0 && tools.length <= 10) {
|
|
1036
|
+
lines.push("");
|
|
1037
|
+
lines.push(" Available tools:");
|
|
1038
|
+
for (const tool of tools) {
|
|
1039
|
+
lines.push(` • ${tool.name}`);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
lines.push("");
|
|
1044
|
+
await this.#syncManagerConnection(name, config);
|
|
1045
|
+
this.#showMessage(lines.join("\n"));
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
if (abortController.signal.aborted || (error instanceof Error && error.name === "AbortError")) {
|
|
1048
|
+
this.ctx.showStatus(`Cancelled MCP test for "${name}"`);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1053
|
+
|
|
1054
|
+
// Provide helpful error messages
|
|
1055
|
+
let helpText = "";
|
|
1056
|
+
if (errorMsg.includes("ENOENT") || errorMsg.includes("not found")) {
|
|
1057
|
+
helpText = "\n\nTip: Check that the command or URL is correct.";
|
|
1058
|
+
} else if (errorMsg.includes("EACCES")) {
|
|
1059
|
+
helpText = "\n\nTip: Check file/command permissions.";
|
|
1060
|
+
} else if (errorMsg.includes("ECONNREFUSED")) {
|
|
1061
|
+
helpText = "\n\nTip: Check that the server is running and the URL/port is correct.";
|
|
1062
|
+
} else if (errorMsg.includes("timeout")) {
|
|
1063
|
+
helpText = "\n\nTip: The server may be slow or unresponsive. Try increasing the timeout.";
|
|
1064
|
+
} else if (errorMsg.includes("401") || errorMsg.includes("403")) {
|
|
1065
|
+
helpText = "\n\nTip: Check your authentication credentials.";
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
this.ctx.showError(`Failed to connect to "${name}": ${errorMsg}${helpText}`);
|
|
1069
|
+
} finally {
|
|
1070
|
+
this.ctx.editor.onEscape = originalOnEscape;
|
|
1071
|
+
if (connection) {
|
|
1072
|
+
// Best-effort: don't block UI on cleanup.
|
|
1073
|
+
void disconnectServer(connection);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
async #handleSetEnabled(name: string | undefined, enabled: boolean): Promise<void> {
|
|
1079
|
+
if (!name) {
|
|
1080
|
+
this.ctx.showError(`Server name required. Usage: /mcp ${enabled ? "enable" : "disable"} <name>`);
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
try {
|
|
1085
|
+
const found = await this.#findConfiguredServer(name);
|
|
1086
|
+
if (!found) {
|
|
1087
|
+
// Check if this is a discovered server from a third-party config
|
|
1088
|
+
const userConfigPath = getMCPConfigPath("user", getProjectDir());
|
|
1089
|
+
const disabledServers = new Set(await readDisabledServers(userConfigPath));
|
|
1090
|
+
const isDiscovered = this.ctx.mcpManager?.getSource(name);
|
|
1091
|
+
const isCurrentlyDisabled = disabledServers.has(name);
|
|
1092
|
+
if (!isDiscovered && !isCurrentlyDisabled) {
|
|
1093
|
+
this.ctx.showError(`Server "${name}" not found.`);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (isCurrentlyDisabled === !enabled) {
|
|
1097
|
+
this.#showMessage(
|
|
1098
|
+
["", theme.fg("muted", `Server "${name}" is already ${enabled ? "enabled" : "disabled"}.`), ""].join(
|
|
1099
|
+
"\n",
|
|
1100
|
+
),
|
|
1101
|
+
);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
await setServerDisabled(userConfigPath, name, !enabled);
|
|
1105
|
+
if (enabled) {
|
|
1106
|
+
await this.#reloadMCP();
|
|
1107
|
+
const state = await this.#waitForServerConnectionWithAnimation(name);
|
|
1108
|
+
const status =
|
|
1109
|
+
state === "connected"
|
|
1110
|
+
? theme.fg("success", "Connected")
|
|
1111
|
+
: state === "connecting"
|
|
1112
|
+
? theme.fg("muted", "Connecting")
|
|
1113
|
+
: theme.fg("warning", "Not connected yet");
|
|
1114
|
+
this.#showMessage(
|
|
1115
|
+
["", theme.fg("success", `\u2713 Enabled "${name}"`), "", ` Status: ${status}`, ""].join("\n"),
|
|
1116
|
+
);
|
|
1117
|
+
} else {
|
|
1118
|
+
await this.ctx.mcpManager?.disconnectServer(name);
|
|
1119
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager?.getTools() ?? []);
|
|
1120
|
+
this.#showMessage(["", theme.fg("success", `\u2713 Disabled "${name}"`), ""].join("\n"));
|
|
1121
|
+
}
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if ((found.config.enabled ?? true) === enabled) {
|
|
1126
|
+
this.#showMessage(
|
|
1127
|
+
["", theme.fg("muted", `Server "${name}" is already ${enabled ? "enabled" : "disabled"}.`), ""].join(
|
|
1128
|
+
"\n",
|
|
1129
|
+
),
|
|
1130
|
+
);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const updated: MCPServerConfig = { ...found.config, enabled };
|
|
1135
|
+
await updateMCPServer(found.filePath, name, updated);
|
|
1136
|
+
await this.#reloadMCP();
|
|
1137
|
+
|
|
1138
|
+
let status = "";
|
|
1139
|
+
if (enabled) {
|
|
1140
|
+
const state = await this.#waitForServerConnectionWithAnimation(name);
|
|
1141
|
+
status =
|
|
1142
|
+
state === "connected"
|
|
1143
|
+
? theme.fg("success", "Connected")
|
|
1144
|
+
: state === "connecting"
|
|
1145
|
+
? theme.fg("muted", "Connecting")
|
|
1146
|
+
: theme.fg("warning", "Not connected yet");
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const lines = [
|
|
1150
|
+
"",
|
|
1151
|
+
theme.fg("success", `✓ ${enabled ? "Enabled" : "Disabled"} "${name}" (${found.scope} config)`),
|
|
1152
|
+
];
|
|
1153
|
+
if (status) {
|
|
1154
|
+
lines.push("");
|
|
1155
|
+
lines.push(` Status: ${status}`);
|
|
1156
|
+
}
|
|
1157
|
+
lines.push("");
|
|
1158
|
+
this.#showMessage(lines.join("\n"));
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
this.ctx.showError(
|
|
1161
|
+
`Failed to ${enabled ? "enable" : "disable"} server: ${error instanceof Error ? error.message : String(error)}`,
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
async #handleUnauth(name: string | undefined): Promise<void> {
|
|
1167
|
+
if (!name) {
|
|
1168
|
+
this.ctx.showError("Server name required. Usage: /mcp unauth <name>");
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
try {
|
|
1173
|
+
const found = await this.#findConfiguredServer(name);
|
|
1174
|
+
if (!found) {
|
|
1175
|
+
this.ctx.showError(`Server "${name}" not found.`);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
const currentAuth = (
|
|
1180
|
+
found.config as MCPServerConfig & { auth?: { type: "oauth" | "apikey"; credentialId?: string } }
|
|
1181
|
+
).auth;
|
|
1182
|
+
if (currentAuth?.type === "oauth") {
|
|
1183
|
+
await this.#removeManagedOAuthCredential(currentAuth.credentialId);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const updated = this.#stripOAuthAuth(found.config);
|
|
1187
|
+
await updateMCPServer(found.filePath, name, updated);
|
|
1188
|
+
await this.#reloadMCP();
|
|
1189
|
+
|
|
1190
|
+
this.#showMessage(
|
|
1191
|
+
["", theme.fg("success", `✓ Cleared auth for "${name}" (${found.scope} config)`), ""].join("\n"),
|
|
1192
|
+
);
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
this.ctx.showError(`Failed to clear auth: ${error instanceof Error ? error.message : String(error)}`);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
async #handleReauth(name: string | undefined): Promise<void> {
|
|
1199
|
+
if (!name) {
|
|
1200
|
+
this.ctx.showError("Server name required. Usage: /mcp reauth <name>");
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
try {
|
|
1205
|
+
const found = await this.#findConfiguredServer(name);
|
|
1206
|
+
if (!found) {
|
|
1207
|
+
this.ctx.showError(`Server "${name}" not found.`);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (found.config.enabled === false) {
|
|
1212
|
+
this.ctx.showError(`Server "${name}" is disabled. Run /mcp enable ${name} first.`);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
const currentAuth = (
|
|
1217
|
+
found.config as MCPServerConfig & { auth?: { type: "oauth" | "apikey"; credentialId?: string } }
|
|
1218
|
+
).auth;
|
|
1219
|
+
if (currentAuth?.type === "oauth") {
|
|
1220
|
+
await this.#removeManagedOAuthCredential(currentAuth.credentialId);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const baseConfig = this.#stripOAuthAuth(found.config);
|
|
1224
|
+
const oauth = await this.#resolveOAuthEndpointsFromServer(baseConfig);
|
|
1225
|
+
|
|
1226
|
+
this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
|
|
1227
|
+
|
|
1228
|
+
const credentialId = await this.#handleOAuthFlow(
|
|
1229
|
+
oauth.authorizationUrl,
|
|
1230
|
+
oauth.tokenUrl,
|
|
1231
|
+
oauth.clientId ?? "",
|
|
1232
|
+
"",
|
|
1233
|
+
oauth.scopes ?? "",
|
|
1234
|
+
);
|
|
1235
|
+
|
|
1236
|
+
const updated: MCPServerConfig = {
|
|
1237
|
+
...baseConfig,
|
|
1238
|
+
auth: {
|
|
1239
|
+
type: "oauth",
|
|
1240
|
+
credentialId,
|
|
1241
|
+
},
|
|
1242
|
+
};
|
|
1243
|
+
await updateMCPServer(found.filePath, name, updated);
|
|
1244
|
+
await this.#reloadMCP();
|
|
1245
|
+
const state = await this.#waitForServerConnectionWithAnimation(name);
|
|
1246
|
+
|
|
1247
|
+
const lines = [
|
|
1248
|
+
"",
|
|
1249
|
+
theme.fg("success", `✓ Reauthorized "${name}" (${found.scope} config)`),
|
|
1250
|
+
"",
|
|
1251
|
+
` Status: ${
|
|
1252
|
+
state === "connected"
|
|
1253
|
+
? theme.fg("success", "connected")
|
|
1254
|
+
: state === "connecting"
|
|
1255
|
+
? theme.fg("muted", "connecting")
|
|
1256
|
+
: theme.fg("warning", "not connected")
|
|
1257
|
+
}`,
|
|
1258
|
+
"",
|
|
1259
|
+
];
|
|
1260
|
+
this.#showMessage(lines.join("\n"));
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
this.ctx.showError(`Failed to reauthorize server: ${error instanceof Error ? error.message : String(error)}`);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
async #handleReload(): Promise<void> {
|
|
1267
|
+
try {
|
|
1268
|
+
this.#showMessage(["", theme.fg("muted", "Reloading MCP servers and runtime tools..."), ""].join("\n"));
|
|
1269
|
+
await this.#reloadMCP();
|
|
1270
|
+
const connectedCount = this.ctx.mcpManager?.getConnectedServers().length ?? 0;
|
|
1271
|
+
this.#showMessage(
|
|
1272
|
+
["", theme.fg("success", "✓ MCP reload complete"), ` Connected servers: ${connectedCount}`, ""].join("\n"),
|
|
1273
|
+
);
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
this.ctx.showError(`Failed to reload MCP: ${error instanceof Error ? error.message : String(error)}`);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Reload MCP manager with new configs
|
|
1281
|
+
*/
|
|
1282
|
+
async #reloadMCP(): Promise<void> {
|
|
1283
|
+
if (!this.ctx.mcpManager) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// Disconnect all existing servers
|
|
1288
|
+
await this.ctx.mcpManager.disconnectAll();
|
|
1289
|
+
|
|
1290
|
+
// Rediscover and connect
|
|
1291
|
+
const result = await this.ctx.mcpManager.discoverAndConnect();
|
|
1292
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
1293
|
+
|
|
1294
|
+
// Show any connection errors
|
|
1295
|
+
if (result.errors.size > 0) {
|
|
1296
|
+
const errorLines = ["", theme.fg("warning", "Some servers failed to connect:"), ""];
|
|
1297
|
+
for (const [serverName, error] of result.errors.entries()) {
|
|
1298
|
+
errorLines.push(` ${serverName}: ${error}`);
|
|
1299
|
+
}
|
|
1300
|
+
errorLines.push("");
|
|
1301
|
+
this.#showMessage(errorLines.join("\n"));
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Show a message in the chat
|
|
1307
|
+
*/
|
|
1308
|
+
#showMessage(text: string): void {
|
|
1309
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
1310
|
+
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
1311
|
+
this.ctx.chatContainer.addChild(new Text(text, 1, 1));
|
|
1312
|
+
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
1313
|
+
this.ctx.ui.requestRender();
|
|
1314
|
+
}
|
|
1315
|
+
}
|