@gajae-code/coding-agent 0.17.2 → 0.17.5
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 +267 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/github-release.d.ts +17 -0
- package/dist/types/cli/public-command-entry.d.ts +56 -0
- package/dist/types/cli/public-command-errors.d.ts +126 -0
- package/dist/types/cli/public-command-evidence.d.ts +99 -0
- package/dist/types/cli/public-command-help.d.ts +65 -0
- package/dist/types/cli/public-command-registry.d.ts +69 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/cli/skills-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +4 -50
- package/dist/types/commands/sdk.d.ts +20 -37
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/commands/skills.d.ts +9 -0
- package/dist/types/config/autorouting-tier-map.d.ts +1049 -3
- package/dist/types/config/file-lock.d.ts +13 -3
- package/dist/types/config/model-profile-activation.d.ts +45 -1
- package/dist/types/config/model-registry.d.ts +14 -3
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +11 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
- package/dist/types/daemon/operator-contract.d.ts +5 -1
- package/dist/types/eval/js/worker-protocol.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +12 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +11 -2
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
- package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
- package/dist/types/harness-control-plane/finalize.d.ts +14 -3
- package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/parse.d.ts +9 -1
- package/dist/types/main.d.ts +23 -1
- package/dist/types/modes/acp/acp-agent.d.ts +55 -1
- package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
- package/dist/types/modes/components/session-selector.d.ts +5 -2
- package/dist/types/modes/components/user-message-selector.d.ts +5 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/runtime/process-lifecycle.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +69 -6
- package/dist/types/runtime-mcp/pool.d.ts +17 -0
- package/dist/types/runtime-mcp/redaction.d.ts +20 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
- package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
- package/dist/types/sdk/broker/broker.d.ts +17 -4
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/identity.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
- package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
- package/dist/types/sdk/broker/managed-task-dag.d.ts +1335 -0
- package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/session-index.d.ts +22 -3
- package/dist/types/sdk/broker/spawn-authority.d.ts +12 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -0
- package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
- package/dist/types/sdk/bus/index.d.ts +19 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
- package/dist/types/sdk/cli/master-cli.d.ts +6 -10
- package/dist/types/sdk/cli/rows.d.ts +19 -1
- package/dist/types/sdk/cli/session-cli.d.ts +22 -5
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/guides/cli.d.ts +3 -4
- package/dist/types/sdk/guides/verify.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +9 -0
- package/dist/types/sdk/host/session-runtime.d.ts +31 -0
- package/dist/types/sdk/host/types.d.ts +10 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +85 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
- package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +3 -3
- package/dist/types/sdk/startup-capability.d.ts +2 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
- package/dist/types/sdk/transport/stdio.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +175 -23
- package/dist/types/session/fallback-chain-controller.d.ts +6 -6
- package/dist/types/session/fold-coordinator.d.ts +17 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
- package/dist/types/session/session-manager.d.ts +64 -1
- package/dist/types/session/session-work-lease.d.ts +14 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
- package/dist/types/setup/provider-onboarding.d.ts +81 -1
- package/dist/types/task/types.d.ts +2 -0
- package/dist/types/tools/bash-interactive.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +37 -11
- package/dist/types/tools/fetch.d.ts +10 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/steer-fold.d.ts +31 -0
- package/dist/types/tools/subagent.d.ts +8 -1
- package/dist/types/utils/git.d.ts +28 -2
- package/dist/types/utils/mupdf-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf.d.ts +4 -0
- package/dist/types/utils/session-title-generation.d.ts +6 -0
- package/dist/types/utils/title-generator.d.ts +11 -2
- package/dist/types/web/scrapers/types.d.ts +3 -0
- package/package.json +18 -10
- package/scripts/build-binary.ts +9 -3
- package/scripts/compile-args.ts +0 -9
- package/scripts/embed-mupdf.ts +52 -0
- package/scripts/generate-sdk-operation-inventory.ts +31 -1
- package/scripts/vendor-markit.ts +196 -0
- package/src/async/job-manager.ts +22 -1
- package/src/cli/daemon-cli.ts +53 -18
- package/src/cli/github-release.ts +87 -5
- package/src/cli/mcp-cli.ts +38 -4
- package/src/cli/public-command-entry.ts +450 -0
- package/src/cli/public-command-errors.ts +581 -0
- package/src/cli/public-command-evidence.ts +717 -0
- package/src/cli/public-command-help.ts +360 -0
- package/src/cli/public-command-registry.ts +541 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +10 -1
- package/src/cli/skills-cli.ts +46 -2
- package/src/cli/stats-cli.ts +11 -1
- package/src/cli.ts +61 -1
- package/src/commands/daemon.ts +65 -69
- package/src/commands/sdk.ts +175 -117
- package/src/commands/setup.ts +2 -0
- package/src/commands/skills.ts +11 -0
- package/src/config/autorouting-tier-map.ts +543 -3
- package/src/config/file-lock.ts +206 -65
- package/src/config/model-discovery-manager.ts +4 -4
- package/src/config/model-profile-activation.ts +417 -66
- package/src/config/model-registry.ts +254 -36
- package/src/config/model-resolver.ts +6 -1
- package/src/config/settings-schema.ts +26 -2
- package/src/coordinator-mcp/policy.ts +10 -5
- package/src/coordinator-mcp/question-gate-codec.ts +17 -0
- package/src/coordinator-mcp/question-state.ts +179 -9
- package/src/coordinator-mcp/server.ts +521 -87
- package/src/coordinator-mcp/session-reaper.ts +6 -4
- package/src/crash/record-loader.ts +6 -2
- package/src/daemon/operator-contract.ts +26 -12
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/eval/js/context-manager.ts +8 -8
- package/src/eval/js/worker-core.ts +9 -4
- package/src/eval/js/worker-protocol.ts +2 -0
- package/src/exec/bash-executor.ts +3 -0
- package/src/extensibility/extensions/runner.ts +52 -4
- package/src/extensibility/extensions/types.ts +10 -0
- package/src/extensibility/gjc-plugins/registry.ts +196 -37
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +46 -8
- package/src/extensibility/runtime-skill-discovery.ts +61 -3
- package/src/extensibility/shared-events.ts +3 -1
- package/src/gjc-runtime/session-state-lock.ts +165 -24
- package/src/gjc-runtime/state-writer.ts +317 -6
- package/src/gjc-runtime/ultragoal-succession.ts +31 -3
- package/src/harness-control-plane/control-endpoint.ts +24 -6
- package/src/harness-control-plane/finalize.ts +312 -25
- package/src/harness-control-plane/owner.ts +323 -89
- package/src/internal-urls/docs-index.generated.ts +23 -20
- package/src/internal-urls/embedded-protocol.ts +83 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/parse.ts +21 -1
- package/src/internal-urls/router.ts +4 -2
- package/src/main.ts +192 -74
- package/src/modes/acp/acp-agent.ts +973 -56
- package/src/modes/acp/startup-provenance.ts +59 -0
- package/src/modes/components/custom-provider-wizard.ts +308 -7
- package/src/modes/components/gjc-bundle-settings.ts +21 -1
- package/src/modes/components/oauth-selector.ts +9 -0
- package/src/modes/components/session-selector.ts +47 -4
- package/src/modes/components/tool-status-header.ts +72 -6
- package/src/modes/components/user-message-selector.ts +57 -15
- package/src/modes/controllers/command-controller.ts +58 -32
- package/src/modes/controllers/extension-ui-controller.ts +33 -7
- package/src/modes/controllers/input-controller.ts +4 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +161 -15
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
- package/src/modes/types.ts +2 -2
- package/src/prompts/agent-fragments/browser-aside.md +1 -0
- package/src/prompts/system/title-system.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/read.md +2 -0
- package/src/runtime/network-prewarm-service.test.ts +36 -1
- package/src/runtime/network-prewarm-service.ts +28 -3
- package/src/runtime/process-lifecycle.ts +22 -11
- package/src/runtime-mcp/config.ts +16 -1
- package/src/runtime-mcp/loader.ts +6 -3
- package/src/runtime-mcp/manager.ts +845 -109
- package/src/runtime-mcp/pool.test.ts +31 -0
- package/src/runtime-mcp/pool.ts +304 -28
- package/src/runtime-mcp/redaction.ts +42 -0
- package/src/runtime-mcp/startup-policy.ts +7 -0
- package/src/runtime-mcp/tool-bridge.ts +71 -0
- package/src/runtime-mcp/transports/http.ts +108 -25
- package/src/sdk/acp/adapter.ts +27 -8
- package/src/sdk/broker/broker.ts +1431 -198
- package/src/sdk/broker/ensure.ts +21 -3
- package/src/sdk/broker/identity.ts +22 -1
- package/src/sdk/broker/lifecycle-ledger.ts +95 -31
- package/src/sdk/broker/lifecycle.ts +690 -141
- package/src/sdk/broker/managed-task-dag.ts +1927 -0
- package/src/sdk/broker/managed-task-verification.ts +642 -0
- package/src/sdk/broker/runtime.ts +1 -1
- package/src/sdk/broker/session-index.ts +97 -4
- package/src/sdk/broker/spawn-authority.ts +175 -14
- package/src/sdk/broker/startup-failure.ts +80 -1
- package/src/sdk/broker/stderr-drainer.ts +59 -0
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/index.ts +707 -81
- package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -3
- package/src/sdk/cli/master-cli.ts +47 -36
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +705 -184
- package/src/sdk/client/client.ts +18 -0
- package/src/sdk/guides/catalog.ts +2 -2
- package/src/sdk/guides/cli.ts +20 -62
- package/src/sdk/guides/verify.ts +14 -0
- package/src/sdk/host/host.ts +49 -5
- package/src/sdk/host/session-runtime.test.ts +184 -2
- package/src/sdk/host/session-runtime.ts +322 -101
- package/src/sdk/host/types.ts +12 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
- package/src/sdk/host/websocket-transport.ts +40 -6
- package/src/sdk/index.ts +11 -0
- package/src/sdk/lifecycle/service.ts +2 -0
- package/src/sdk/prompt-deadline-flush.ts +356 -0
- package/src/sdk/prompt-deadline-manager.ts +133 -1
- package/src/sdk/prompt-tool-boundary.ts +121 -0
- package/src/sdk/protocol/operation-inventory.generated.json +106 -0
- package/src/sdk/router/session-router.ts +9 -1
- package/src/sdk/session.ts +763 -116
- package/src/sdk/startup-capability.ts +1 -1
- package/src/sdk/transport/relay.ts +71 -6
- package/src/sdk/transport/serve-cli.ts +397 -42
- package/src/sdk/transport/socket.ts +12 -11
- package/src/sdk/transport/stdio.ts +7 -3
- package/src/session/agent-session.ts +2472 -407
- package/src/session/fallback-chain-controller.ts +6 -7
- package/src/session/fold-coordinator.ts +24 -5
- package/src/session/internal/managed-session-scope.ts +68 -18
- package/src/session/internal/managed-session-storage.ts +334 -12
- package/src/session/session-manager.ts +382 -44
- package/src/session/session-work-lease.ts +34 -0
- package/src/setup/model-onboarding-guidance.ts +1 -1
- package/src/setup/provider-onboarding.ts +397 -24
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +259 -15
- package/src/task/executor.ts +46 -17
- package/src/task/index.ts +14 -2
- package/src/task/types.ts +2 -0
- package/src/tools/bash-interactive.ts +2 -0
- package/src/tools/bash.ts +118 -78
- package/src/tools/fetch.ts +140 -25
- package/src/tools/job.ts +151 -25
- package/src/tools/output-meta.ts +15 -2
- package/src/tools/path-utils.ts +27 -3
- package/src/tools/read.ts +3 -3
- package/src/tools/steer-fold.ts +107 -0
- package/src/tools/subagent-render.ts +24 -1
- package/src/tools/subagent.ts +27 -1
- package/src/tools/tool-catalog.generated.ts +29 -2
- package/src/tools/tool-errors.ts +4 -0
- package/src/utils/git.ts +53 -3
- package/src/utils/markit.ts +55 -41
- package/src/utils/mupdf-assets.d.ts +4 -0
- package/src/utils/mupdf-embedded.ts +4 -0
- package/src/utils/mupdf.ts +136 -0
- package/src/utils/session-title-generation.ts +29 -0
- package/src/utils/title-generator.ts +46 -2
- package/src/web/scrapers/types.ts +24 -3
- package/vendor/markit-ai/LICENSE +21 -0
- package/vendor/markit-ai/MANIFEST.json +412 -0
- package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/config.js +139 -0
- package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
- package/vendor/markit-ai/dist/commands/convert.js +113 -0
- package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/formats.js +92 -0
- package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/init.js +29 -0
- package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/onboard.js +61 -0
- package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/plugin.js +58 -0
- package/vendor/markit-ai/dist/config.d.ts +26 -0
- package/vendor/markit-ai/dist/config.js +42 -0
- package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/audio.js +104 -0
- package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/csv.js +83 -0
- package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/docx.js +57 -0
- package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/epub.js +128 -0
- package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
- package/vendor/markit-ai/dist/converters/github.js +148 -0
- package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/html.js +30 -0
- package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/image.js +124 -0
- package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
- package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
- package/vendor/markit-ai/dist/converters/iwork.js +391 -0
- package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/json.js +21 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
- package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
- package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
- package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
- package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
- package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
- package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
- package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/pptx.js +289 -0
- package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
- package/vendor/markit-ai/dist/converters/rss.js +138 -0
- package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
- package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
- package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/xml.js +18 -0
- package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/yaml.js +17 -0
- package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/zip.js +57 -0
- package/vendor/markit-ai/dist/index.d.ts +30 -0
- package/vendor/markit-ai/dist/index.js +26 -0
- package/vendor/markit-ai/dist/main.d.ts +2 -0
- package/vendor/markit-ai/dist/main.js +191 -0
- package/vendor/markit-ai/dist/markit.d.ts +45 -0
- package/vendor/markit-ai/dist/markit.js +267 -0
- package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
- package/vendor/markit-ai/dist/plugins/api.js +42 -0
- package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
- package/vendor/markit-ai/dist/plugins/index.js +3 -0
- package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/installer.js +176 -0
- package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
- package/vendor/markit-ai/dist/plugins/loader.js +61 -0
- package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/types.js +1 -0
- package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
- package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
- package/vendor/markit-ai/dist/providers/index.js +56 -0
- package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/openai.js +76 -0
- package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
- package/vendor/markit-ai/dist/providers/types.js +1 -0
- package/vendor/markit-ai/dist/types.d.ts +36 -0
- package/vendor/markit-ai/dist/types.js +1 -0
- package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
- package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
- package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
- package/vendor/markit-ai/dist/utils/output.js +31 -0
- package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
- package/vendor/markit-ai/dist/utils/turndown.js +70 -0
- package/vendor/markit-ai/package.json +74 -0
- package/vendor/markit-ai.patch +49 -0
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
MCPPoolLeaseReleaseError,
|
|
36
36
|
} from "./pool";
|
|
37
37
|
import type { MCPProtocolObservation } from "./protocol";
|
|
38
|
+
import { DEFAULT_MCP_STARTUP_WAIT_MS, MAX_MCP_STARTUP_WAIT_MS, MCP_STARTUP_WAIT_GRACE_MS } from "./startup-policy";
|
|
38
39
|
import type { MCPToolDetails } from "./tool-bridge";
|
|
39
40
|
import { DeferredMCPTool, MCPTool } from "./tool-bridge";
|
|
40
41
|
import type { MCPToolCache } from "./tool-cache";
|
|
@@ -51,7 +52,7 @@ import type {
|
|
|
51
52
|
MCPServerConnection,
|
|
52
53
|
MCPToolDefinition,
|
|
53
54
|
} from "./types";
|
|
54
|
-
import { MCPExpectedFailure, MCPNotificationMethods } from "./types";
|
|
55
|
+
import { MCPExpectedFailure, MCPHttpRequestError, MCPJsonRpcError, MCPNotificationMethods } from "./types";
|
|
55
56
|
|
|
56
57
|
type ToolLoadResult = {
|
|
57
58
|
connection: MCPServerConnection;
|
|
@@ -101,16 +102,23 @@ type RetiredLeaseRelease = {
|
|
|
101
102
|
promise: Promise<void>;
|
|
102
103
|
};
|
|
103
104
|
|
|
104
|
-
const STARTUP_TIMEOUT_MS =
|
|
105
|
-
const STARTUP_TIMEOUT_GRACE_MS =
|
|
105
|
+
const STARTUP_TIMEOUT_MS = DEFAULT_MCP_STARTUP_WAIT_MS;
|
|
106
|
+
const STARTUP_TIMEOUT_GRACE_MS = MCP_STARTUP_WAIT_GRACE_MS;
|
|
106
107
|
/**
|
|
107
108
|
* Default ceiling on how long `discoverAndConnect` waits for a server batch to
|
|
108
109
|
* come up. Deliberately short: a config with a large `timeout` must not be able
|
|
109
110
|
* to hang ordinary startup. ACP lifecycle launches carry their own, larger
|
|
110
111
|
* budget derived from the readiness deadline (see `maxStartupTimeoutMs`).
|
|
111
112
|
*/
|
|
112
|
-
const MAX_STARTUP_TIMEOUT_MS =
|
|
113
|
+
const MAX_STARTUP_TIMEOUT_MS = MAX_MCP_STARTUP_WAIT_MS;
|
|
113
114
|
const DEFAULT_EXACT_CONFIG_STARTUP_TIMEOUT_MS = 30_000;
|
|
115
|
+
const MCP_STARTUP_TIMEOUT_PREFIX = "MCP server connection timed out during startup:";
|
|
116
|
+
|
|
117
|
+
export function isMCPStartupTimeoutError(error: unknown): boolean {
|
|
118
|
+
return typeof error === "string"
|
|
119
|
+
? error.startsWith(MCP_STARTUP_TIMEOUT_PREFIX)
|
|
120
|
+
: error instanceof Error && error.message.startsWith(MCP_STARTUP_TIMEOUT_PREFIX);
|
|
121
|
+
}
|
|
114
122
|
|
|
115
123
|
export function resolveStartupTimeoutMs(configs: MCPServerConfig[], maxStartupTimeoutMs?: number): number {
|
|
116
124
|
const ceiling =
|
|
@@ -150,6 +158,24 @@ export function withinDeclaredConnectionWindow(config: MCPServerConfig, elapsedM
|
|
|
150
158
|
return elapsedMs < timeout;
|
|
151
159
|
}
|
|
152
160
|
|
|
161
|
+
/** Keep startup logs bounded and free of remote response text. */
|
|
162
|
+
function classifyMCPStartupFailure(error: unknown): string {
|
|
163
|
+
if (error instanceof MCPExpectedFailure) {
|
|
164
|
+
return error.cause === undefined ? "expected-mcp-failure" : classifyMCPStartupFailure(error.cause);
|
|
165
|
+
}
|
|
166
|
+
if (error instanceof MCPHttpRequestError) {
|
|
167
|
+
return Number.isInteger(error.status) && error.status >= 100 && error.status <= 599
|
|
168
|
+
? `http-status:${error.status}`
|
|
169
|
+
: "http-request-error";
|
|
170
|
+
}
|
|
171
|
+
if (error instanceof MCPJsonRpcError) {
|
|
172
|
+
return Number.isInteger(error.code) && Math.abs(error.code) <= 1_000_000
|
|
173
|
+
? `json-rpc-code:${error.code}`
|
|
174
|
+
: "json-rpc-error";
|
|
175
|
+
}
|
|
176
|
+
return "transport-error";
|
|
177
|
+
}
|
|
178
|
+
|
|
153
179
|
function trackPromise<T>(promise: Promise<T>): TrackedPromise<T> {
|
|
154
180
|
const tracked: TrackedPromise<T> = { promise, status: "pending" };
|
|
155
181
|
promise.then(
|
|
@@ -260,6 +286,22 @@ export interface MCPLoadResult {
|
|
|
260
286
|
exaApiKeys: string[];
|
|
261
287
|
}
|
|
262
288
|
|
|
289
|
+
export type MCPToolCatalogPublication = "unpublished" | "published" | "fenced";
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Atomic view of the manager's fenced tool catalog and its publication state.
|
|
293
|
+
*
|
|
294
|
+
* `unpublished` is reserved for the initial deferred window where no manager
|
|
295
|
+
* catalog has been published and no exact control is fencing it. An empty
|
|
296
|
+
* `published` or `fenced` catalog is authoritative and must not be replaced
|
|
297
|
+
* with an in-flight discovery result.
|
|
298
|
+
*/
|
|
299
|
+
export interface MCPToolCatalogSnapshot {
|
|
300
|
+
tools: CustomTool<TSchema, MCPToolDetails>[];
|
|
301
|
+
publication: MCPToolCatalogPublication;
|
|
302
|
+
generation: number;
|
|
303
|
+
}
|
|
304
|
+
|
|
263
305
|
/** Options for discovering and connecting to MCP servers */
|
|
264
306
|
export interface MCPDiscoverOptions {
|
|
265
307
|
/** Whether to load project-level config (default: true) */
|
|
@@ -288,10 +330,10 @@ export interface MCPManagerOptions {
|
|
|
288
330
|
/** Restrict this instance to tools from an explicit MCP config. */
|
|
289
331
|
toolsOnly?: boolean;
|
|
290
332
|
/**
|
|
291
|
-
* Ceiling for the startup wait, in milliseconds.
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
333
|
+
* Ceiling for the startup wait, in milliseconds. ACP lifecycle launches and
|
|
334
|
+
* plugin MCP startup use bounded caller-owned budgets; other consumers keep
|
|
335
|
+
* the short default. Non-positive or non-finite values are ignored and the
|
|
336
|
+
* default applies.
|
|
295
337
|
*/
|
|
296
338
|
maxStartupTimeoutMs?: number;
|
|
297
339
|
/** Connection pool used for every physical MCP open/close. */
|
|
@@ -300,12 +342,49 @@ export interface MCPManagerOptions {
|
|
|
300
342
|
sessionId?: string;
|
|
301
343
|
/** Idle retention for shared pool entries. */
|
|
302
344
|
sharedPoolIdleMs?: number;
|
|
345
|
+
/** Limit tool-cache reads and writes to this manager's conventional servers. */
|
|
346
|
+
toolCacheServerNames?: ReadonlySet<string>;
|
|
303
347
|
/** Test seam for deterministic reconnect backoff scheduling. */
|
|
304
348
|
sleep?: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
|
|
305
349
|
/** Test seam for fencing the acquisition-to-registration replacement race. */
|
|
306
350
|
afterLeaseAcquiredForTests?: (name: string, lease: MCPPoolLease) => void | Promise<void>;
|
|
307
351
|
}
|
|
308
352
|
|
|
353
|
+
export type ExactMcpServerControlStatus =
|
|
354
|
+
| "suspended"
|
|
355
|
+
| "already-suspended"
|
|
356
|
+
| "resumed"
|
|
357
|
+
| "not-suspended"
|
|
358
|
+
| "reconnected"
|
|
359
|
+
| "unavailable"
|
|
360
|
+
| "unknown-server";
|
|
361
|
+
|
|
362
|
+
export interface ExactMcpServerControlResult {
|
|
363
|
+
readonly name: string;
|
|
364
|
+
readonly status: ExactMcpServerControlStatus;
|
|
365
|
+
readonly toolCount: number;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* A staged catalog the caller can validate against before it is published.
|
|
370
|
+
* `commit` is the single publication point; `abort` releases anything the
|
|
371
|
+
* preparation opened and leaves the manager on its predecessor.
|
|
372
|
+
*/
|
|
373
|
+
export interface PreparedExactMcpCatalog {
|
|
374
|
+
readonly tools: CustomTool<TSchema, MCPToolDetails>[];
|
|
375
|
+
canCommit(): boolean;
|
|
376
|
+
commit(): Promise<void>;
|
|
377
|
+
abort(): Promise<void>;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export interface PreparedExactMcpServerControl extends PreparedExactMcpCatalog {
|
|
381
|
+
readonly result: ExactMcpServerControlResult;
|
|
382
|
+
/** Validate a resumed replacement before an atomic multi-server publication. */
|
|
383
|
+
precommit?(): void;
|
|
384
|
+
/** Publish after every replacement in the catalog has passed precommit. */
|
|
385
|
+
publish?(): void;
|
|
386
|
+
}
|
|
387
|
+
|
|
309
388
|
/**
|
|
310
389
|
* MCP Server Manager.
|
|
311
390
|
*
|
|
@@ -342,6 +421,8 @@ export class MCPManager {
|
|
|
342
421
|
readonly #retiredLeaseReleases = new Set<RetiredLeaseRelease>();
|
|
343
422
|
#nextScopedOperationId = 1;
|
|
344
423
|
#tools: CustomTool<TSchema, MCPToolDetails>[] = [];
|
|
424
|
+
#toolCatalogGeneration = 0;
|
|
425
|
+
#toolCatalogPublished = false;
|
|
345
426
|
#pendingConnections = new Map<string, Promise<MCPServerConnection>>();
|
|
346
427
|
#pendingConnectionControllers = new Map<string, AbortController>();
|
|
347
428
|
#pendingToolLoads = new Map<string, Promise<ToolLoadResult>>();
|
|
@@ -351,7 +432,8 @@ export class MCPManager {
|
|
|
351
432
|
#authStorage: AuthStorage | null = null;
|
|
352
433
|
#inputRequestHandler: MCPInputRequestHandler | null = null;
|
|
353
434
|
#onNotification?: (serverName: string, method: string, params: unknown) => void;
|
|
354
|
-
#onToolsChanged?: (tools: CustomTool<TSchema, MCPToolDetails
|
|
435
|
+
#onToolsChanged?: (tools: readonly Readonly<CustomTool<TSchema, MCPToolDetails>>[]) => void;
|
|
436
|
+
readonly #toolChangeListeners = new Set<(tools: readonly Readonly<CustomTool<TSchema, MCPToolDetails>>[]) => void>();
|
|
355
437
|
#onResourcesChanged?: (serverName: string, uri: string) => void;
|
|
356
438
|
#onPromptsChanged?: (serverName: string) => void;
|
|
357
439
|
#notificationsEnabled = false;
|
|
@@ -365,18 +447,33 @@ export class MCPManager {
|
|
|
365
447
|
#reconnectBackoffs = new Map<string, AbortController>();
|
|
366
448
|
/** Preserved configs for reconnection after connection loss. */
|
|
367
449
|
#serverConfigs = new Map<string, MCPServerConfig>();
|
|
450
|
+
// Exact MCP replacements retain cleanup ownership and cancel in-flight candidates.
|
|
451
|
+
/** Session-local operator suppression; never persisted to MCP config. */
|
|
452
|
+
#suppressedServers = new Set<string>();
|
|
453
|
+
/** Prepare-phase fence that prevents a concurrent close/rebind publication. */
|
|
454
|
+
#pendingExactSuspensions = new Set<string>();
|
|
455
|
+
/** Single per-server candidate axis shared against automatic reconnect. */
|
|
456
|
+
#pendingExactControlServers = new Set<string>();
|
|
368
457
|
/** Monotonic epoch incremented on disconnectAll to invalidate stale reconnections. */
|
|
369
458
|
#epoch = 0;
|
|
370
459
|
#scopedLifecycle: "open" | "disconnecting" | "reconnecting" = "open";
|
|
371
460
|
#scopedLifecycleEpoch = 0;
|
|
372
461
|
readonly #toolsOnly: boolean;
|
|
462
|
+
#toolCacheServerNames: Set<string> | undefined;
|
|
373
463
|
#toolsOnlyConfigLoaded = false;
|
|
464
|
+
#connectionSetMutationBlocked = false;
|
|
374
465
|
#connectionSetSealed = false;
|
|
375
466
|
|
|
376
467
|
#serverError(message: string): string {
|
|
377
468
|
return this.#toolsOnly ? "MCP server unavailable" : message;
|
|
378
469
|
}
|
|
379
470
|
|
|
471
|
+
#shouldCacheServerTools(name: string): boolean {
|
|
472
|
+
return (
|
|
473
|
+
this.toolCache !== null && (this.#toolCacheServerNames === undefined || this.#toolCacheServerNames.has(name))
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
380
477
|
#removePendingAcquireCleanup(name: string, error: MCPPoolAcquireAbortError): void {
|
|
381
478
|
const pending = this.#pendingAcquireCleanups.get(name);
|
|
382
479
|
pending?.delete(error);
|
|
@@ -480,13 +577,27 @@ export class MCPManager {
|
|
|
480
577
|
return this.#scopedLifecycleEpoch;
|
|
481
578
|
}
|
|
482
579
|
#assertConnectionSetMutable(): void {
|
|
483
|
-
if (this.#
|
|
580
|
+
if (this.#connectionSetMutationBlocked) {
|
|
581
|
+
throw new Error(
|
|
582
|
+
this.#connectionSetSealed ? "MCP manager connection set is sealed" : "MCP manager connection set is frozen",
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** Block config mutations and reloads while keeping existing servers reconnectable. */
|
|
588
|
+
freezeConnectionSet(): void {
|
|
589
|
+
this.#connectionSetMutationBlocked = true;
|
|
484
590
|
}
|
|
485
591
|
|
|
486
592
|
sealConnectionSet(): void {
|
|
593
|
+
this.freezeConnectionSet();
|
|
487
594
|
this.#connectionSetSealed = true;
|
|
488
595
|
}
|
|
489
596
|
|
|
597
|
+
isConnectionSetMutationBlocked(): boolean {
|
|
598
|
+
return this.#connectionSetMutationBlocked;
|
|
599
|
+
}
|
|
600
|
+
|
|
490
601
|
isConnectionSetSealed(): boolean {
|
|
491
602
|
return this.#connectionSetSealed;
|
|
492
603
|
}
|
|
@@ -539,7 +650,11 @@ export class MCPManager {
|
|
|
539
650
|
}
|
|
540
651
|
|
|
541
652
|
#scheduleSharedRebind(name: string, lease: MCPPoolLease): void {
|
|
542
|
-
if (this.#
|
|
653
|
+
if (this.#pendingExactSuspensions.has(name) || this.#pendingExactControlServers.has(name)) {
|
|
654
|
+
this.#queueDeferredSharedRebind(name, lease, this.#epoch);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (this.#scopedLifecycle === "disconnecting" || this.#suppressedServers.has(name)) return;
|
|
543
658
|
const active = this.#activeSharedRebinds.get(name);
|
|
544
659
|
if (active) {
|
|
545
660
|
if (active.lease !== lease) this.#queueDeferredSharedRebind(name, lease, this.#epoch);
|
|
@@ -562,6 +677,15 @@ export class MCPManager {
|
|
|
562
677
|
#drainDeferredSharedRebinds(): void {
|
|
563
678
|
if (this.#scopedLifecycle !== "open" || this.#deferredSharedRebinds.size === 0) return;
|
|
564
679
|
for (const [name, pending] of this.#deferredSharedRebinds.entries()) {
|
|
680
|
+
// A suspended or in-flight control still owns this server. Keep the newest
|
|
681
|
+
// replacement queued so the manager rebinds after suppression clears instead
|
|
682
|
+
// of staying bound to a retired pool generation.
|
|
683
|
+
if (
|
|
684
|
+
this.#suppressedServers.has(name) ||
|
|
685
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
686
|
+
this.#pendingExactControlServers.has(name)
|
|
687
|
+
)
|
|
688
|
+
continue;
|
|
565
689
|
if (pending.managerEpoch !== this.#epoch) {
|
|
566
690
|
this.#deferredSharedRebinds.delete(name);
|
|
567
691
|
continue;
|
|
@@ -614,7 +738,14 @@ export class MCPManager {
|
|
|
614
738
|
this.#handleServerNotification(name, event.method, event.params);
|
|
615
739
|
return;
|
|
616
740
|
}
|
|
617
|
-
if (
|
|
741
|
+
if (
|
|
742
|
+
event.type !== "close" ||
|
|
743
|
+
this.#connectionSetSealed ||
|
|
744
|
+
this.#suppressedServers.has(name) ||
|
|
745
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
746
|
+
this.#pendingExactControlServers.has(name)
|
|
747
|
+
)
|
|
748
|
+
return;
|
|
618
749
|
void this.reconnectServer(name);
|
|
619
750
|
}),
|
|
620
751
|
);
|
|
@@ -622,7 +753,12 @@ export class MCPManager {
|
|
|
622
753
|
|
|
623
754
|
async #rebindAfterSharedRestart(name: string, lease: MCPPoolLease): Promise<void> {
|
|
624
755
|
const managerEpoch = this.#epoch;
|
|
625
|
-
if (
|
|
756
|
+
if (
|
|
757
|
+
this.#scopedLifecycle === "disconnecting" ||
|
|
758
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
759
|
+
this.#pendingExactControlServers.has(name)
|
|
760
|
+
)
|
|
761
|
+
return;
|
|
626
762
|
if (this.#scopedLifecycle === "reconnecting") {
|
|
627
763
|
this.#queueDeferredSharedRebind(name, lease, managerEpoch);
|
|
628
764
|
return;
|
|
@@ -858,6 +994,7 @@ export class MCPManager {
|
|
|
858
994
|
options: MCPManagerOptions = {},
|
|
859
995
|
) {
|
|
860
996
|
this.#toolsOnly = options.toolsOnly === true;
|
|
997
|
+
this.#toolCacheServerNames = options.toolCacheServerNames ? new Set(options.toolCacheServerNames) : undefined;
|
|
861
998
|
this.#maxStartupTimeoutMs = options.maxStartupTimeoutMs;
|
|
862
999
|
this.#sleep = options.sleep ?? delay;
|
|
863
1000
|
this.#afterLeaseAcquiredForTests = options.afterLeaseAcquiredForTests;
|
|
@@ -879,13 +1016,46 @@ export class MCPManager {
|
|
|
879
1016
|
}
|
|
880
1017
|
|
|
881
1018
|
/**
|
|
882
|
-
* Set a callback to
|
|
1019
|
+
* Set a callback to receive a read-only catalog snapshot when server tools change.
|
|
883
1020
|
*/
|
|
884
|
-
setOnToolsChanged(handler: (tools: CustomTool<TSchema, MCPToolDetails
|
|
1021
|
+
setOnToolsChanged(handler: (tools: readonly Readonly<CustomTool<TSchema, MCPToolDetails>>[]) => void): void {
|
|
885
1022
|
if (this.#toolsOnly) return;
|
|
886
1023
|
this.#onToolsChanged = handler;
|
|
887
1024
|
}
|
|
888
1025
|
|
|
1026
|
+
/** Subscribe to readonly catalog snapshots without replacing the manager owner's callback. */
|
|
1027
|
+
subscribeToToolsChanged(
|
|
1028
|
+
handler: (tools: readonly Readonly<CustomTool<TSchema, MCPToolDetails>>[]) => void,
|
|
1029
|
+
): () => void {
|
|
1030
|
+
if (this.#toolsOnly) return () => {};
|
|
1031
|
+
this.#toolChangeListeners.add(handler);
|
|
1032
|
+
return () => {
|
|
1033
|
+
this.#toolChangeListeners.delete(handler);
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
#notifyToolsChanged(): void {
|
|
1038
|
+
const listeners = [...this.#toolChangeListeners];
|
|
1039
|
+
// Keep live MCPTool instances for reconnect behavior, but never expose the
|
|
1040
|
+
// manager-owned array; callback types make their tool descriptors readonly.
|
|
1041
|
+
const snapshot = this.#onToolsChanged || listeners.length > 0 ? Object.freeze([...this.#tools]) : undefined;
|
|
1042
|
+
if (snapshot && this.#onToolsChanged) {
|
|
1043
|
+
try {
|
|
1044
|
+
this.#onToolsChanged(snapshot);
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
logger.warn("MCP tool catalog owner callback failed", { error: classifyMCPStartupFailure(error) });
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (!snapshot) return;
|
|
1050
|
+
for (const listener of listeners) {
|
|
1051
|
+
try {
|
|
1052
|
+
listener(snapshot);
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
logger.warn("MCP tool catalog listener failed", { error: classifyMCPStartupFailure(error) });
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
889
1059
|
/**
|
|
890
1060
|
* Set a callback to fire when any server's resources change.
|
|
891
1061
|
*/
|
|
@@ -1014,6 +1184,11 @@ export class MCPManager {
|
|
|
1014
1184
|
nativeOnly: options?.nativeOnly,
|
|
1015
1185
|
configPath: options?.configPath,
|
|
1016
1186
|
});
|
|
1187
|
+
if (this.#toolCacheServerNames !== undefined) {
|
|
1188
|
+
this.#toolCacheServerNames = new Set(
|
|
1189
|
+
Object.keys(configs).filter(name => sources[name]?.provider !== "gjc-plugins"),
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1017
1192
|
const result = await this.#connectServers(configs, sources, options?.onConnecting);
|
|
1018
1193
|
if (configurationWarning) result.errors.set("$config", "MCP configuration unavailable");
|
|
1019
1194
|
result.exaApiKeys = exaApiKeys;
|
|
@@ -1067,6 +1242,13 @@ export class MCPManager {
|
|
|
1067
1242
|
"MCP connection cleanup pending";
|
|
1068
1243
|
errors.set(name, this.#serverError(message));
|
|
1069
1244
|
reportedErrors.add(name);
|
|
1245
|
+
if (!this.#toolsOnly) {
|
|
1246
|
+
logger.warn("Skipping MCP autoload registration", {
|
|
1247
|
+
path: `mcp:${name}`,
|
|
1248
|
+
serverName: name,
|
|
1249
|
+
reason: message,
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1070
1252
|
continue;
|
|
1071
1253
|
}
|
|
1072
1254
|
if (sources[name]) {
|
|
@@ -1080,11 +1262,14 @@ export class MCPManager {
|
|
|
1080
1262
|
// Skip if already connected.
|
|
1081
1263
|
if (this.#connections.has(name)) {
|
|
1082
1264
|
connectedServers.add(name);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1265
|
+
if (this.#isExactToolPublicationAllowed(name)) {
|
|
1266
|
+
allTools.push(
|
|
1267
|
+
...this.#tools.filter(
|
|
1268
|
+
tool =>
|
|
1269
|
+
(tool instanceof MCPTool || tool instanceof DeferredMCPTool) && tool.mcpServerName === name,
|
|
1270
|
+
),
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1088
1273
|
continue;
|
|
1089
1274
|
}
|
|
1090
1275
|
|
|
@@ -1093,14 +1278,29 @@ export class MCPManager {
|
|
|
1093
1278
|
this.#pendingToolLoads.has(name) ||
|
|
1094
1279
|
this.#pendingReconnections.has(name)
|
|
1095
1280
|
) {
|
|
1281
|
+
if (!this.#toolsOnly) {
|
|
1282
|
+
logger.warn("Skipping MCP autoload registration", {
|
|
1283
|
+
path: `mcp:${name}`,
|
|
1284
|
+
serverName: name,
|
|
1285
|
+
reason: "connection already pending",
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1096
1288
|
continue;
|
|
1097
1289
|
}
|
|
1098
1290
|
|
|
1099
1291
|
// Validate config
|
|
1100
1292
|
const validationErrors = validateServerConfig(name, config);
|
|
1101
1293
|
if (validationErrors.length > 0) {
|
|
1102
|
-
|
|
1294
|
+
const reason = validationErrors.join("; ");
|
|
1295
|
+
errors.set(name, this.#serverError(reason));
|
|
1103
1296
|
reportedErrors.add(name);
|
|
1297
|
+
if (!this.#toolsOnly) {
|
|
1298
|
+
logger.warn("Skipping MCP autoload registration", {
|
|
1299
|
+
path: `mcp:${name}`,
|
|
1300
|
+
serverName: name,
|
|
1301
|
+
reason,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1104
1304
|
continue;
|
|
1105
1305
|
}
|
|
1106
1306
|
|
|
@@ -1237,22 +1437,31 @@ export class MCPManager {
|
|
|
1237
1437
|
return;
|
|
1238
1438
|
this.#pendingToolLoads.delete(name);
|
|
1239
1439
|
const reconnect = () => this.reconnectServer(name);
|
|
1240
|
-
const customTools = MCPTool.fromTools(
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1440
|
+
const customTools = MCPTool.fromTools(
|
|
1441
|
+
this.#connectionForLease(connection),
|
|
1442
|
+
serverTools,
|
|
1443
|
+
reconnect,
|
|
1444
|
+
this.#exactToolOptions(name, config.sharing === "shared"),
|
|
1445
|
+
);
|
|
1244
1446
|
this.#replaceServerTools(name, customTools);
|
|
1245
|
-
if (!this.#toolsOnly) this.#
|
|
1246
|
-
if (!this.#toolsOnly
|
|
1447
|
+
if (!this.#toolsOnly) this.#notifyToolsChanged();
|
|
1448
|
+
if (!this.#toolsOnly && this.#shouldCacheServerTools(name))
|
|
1449
|
+
void this.toolCache?.set(name, config, serverTools);
|
|
1247
1450
|
if (!this.#toolsOnly) await this.#loadServerResourcesAndPrompts(name, connection);
|
|
1248
1451
|
})
|
|
1249
1452
|
.catch(error => {
|
|
1250
1453
|
this.#retainConnectionCleanupFailure(name, error);
|
|
1251
1454
|
if (this.#pendingToolLoads.get(name) !== toolsPromise) return;
|
|
1252
1455
|
this.#pendingToolLoads.delete(name);
|
|
1253
|
-
if (!allowBackgroundLogging || reportedErrors.has(name)
|
|
1456
|
+
if (!allowBackgroundLogging || this.#toolsOnly || reportedErrors.has(name)) return;
|
|
1254
1457
|
const message = error instanceof Error ? error.message : String(error);
|
|
1255
|
-
|
|
1458
|
+
errors.set(name, this.#serverError(message));
|
|
1459
|
+
reportedErrors.add(name);
|
|
1460
|
+
logger.error("MCP tool load failed", {
|
|
1461
|
+
path: `mcp:${name}`,
|
|
1462
|
+
serverName: name,
|
|
1463
|
+
error: classifyMCPStartupFailure(error),
|
|
1464
|
+
});
|
|
1256
1465
|
});
|
|
1257
1466
|
}
|
|
1258
1467
|
|
|
@@ -1308,6 +1517,7 @@ export class MCPManager {
|
|
|
1308
1517
|
if (this.toolCache && !this.#toolsOnly) {
|
|
1309
1518
|
await Promise.all(
|
|
1310
1519
|
pendingTasks.map(async task => {
|
|
1520
|
+
if (!this.#shouldCacheServerTools(task.name)) return;
|
|
1311
1521
|
const cached = await this.toolCache?.get(task.name, task.config);
|
|
1312
1522
|
if (cached) {
|
|
1313
1523
|
cachedTools.set(task.name, cached);
|
|
@@ -1316,41 +1526,55 @@ export class MCPManager {
|
|
|
1316
1526
|
);
|
|
1317
1527
|
}
|
|
1318
1528
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1529
|
+
// Cached tools are only a fallback snapshot; they do not exempt the live
|
|
1530
|
+
// connection from the startup cleanup contract. The startup wait elapsing
|
|
1531
|
+
// means "stop blocking session start", not "this server failed". A server
|
|
1532
|
+
// whose operator declared a `timeout` (`gjc mcp add --timeout`) asked to
|
|
1533
|
+
// wait that long for it, so while it is still inside that window it keeps
|
|
1534
|
+
// connecting in the background under `connectToServer`'s own timeout, and
|
|
1535
|
+
// the background tool load adopts it. Only a server that declared no
|
|
1536
|
+
// window, or already spent it, is torn down and reported. Exact-config
|
|
1537
|
+
// (`toolsOnly`) startup still fails fast: it builds a catalog once, so a
|
|
1538
|
+
// missing server is an error.
|
|
1539
|
+
// Abort and disconnect in the background: a misbehaving stdio/MCP transport can
|
|
1540
|
+
// ignore AbortSignal and keep startup blocked indefinitely, but it must not remain
|
|
1541
|
+
// registered if it eventually connects.
|
|
1542
|
+
const startupElapsedMs = Date.now() - startupStartedAt;
|
|
1543
|
+
for (const task of pendingTasks) {
|
|
1544
|
+
if (task.tracked.status !== "pending") continue;
|
|
1545
|
+
if (!this.#toolsOnly && withinDeclaredConnectionWindow(task.config, startupElapsedMs)) {
|
|
1546
|
+
logger.warn("MCP server still connecting after the startup wait", {
|
|
1547
|
+
path: `mcp:${task.name}`,
|
|
1548
|
+
serverName: task.name,
|
|
1549
|
+
startupWaitMs: startupTimeoutMs,
|
|
1550
|
+
declaredTimeoutMs: task.config.timeout,
|
|
1551
|
+
});
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
const message = `MCP server connection timed out during startup: ${task.name}`;
|
|
1555
|
+
if (!this.#toolsOnly) {
|
|
1556
|
+
logger.warn("MCP server connection timed out during startup", {
|
|
1557
|
+
path: `mcp:${task.name}`,
|
|
1558
|
+
serverName: task.name,
|
|
1559
|
+
startupWaitMs: startupTimeoutMs,
|
|
1560
|
+
declaredTimeoutMs: task.config.timeout,
|
|
1561
|
+
remediation: "Set a per-server timeout with `gjc mcp add --timeout <ms>` for slower servers.",
|
|
1349
1562
|
});
|
|
1350
1563
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1564
|
+
errors.set(task.name, this.#serverError(message));
|
|
1565
|
+
reportedErrors.add(task.name);
|
|
1566
|
+
task.connectionAbort.abort(new Error(message));
|
|
1567
|
+
if (this.#pendingConnections.has(task.name)) this.#pendingConnections.delete(task.name);
|
|
1568
|
+
if (this.#pendingToolLoads.get(task.name) === task.toolsPromise)
|
|
1569
|
+
this.#pendingToolLoads.delete(task.name);
|
|
1570
|
+
this.#pendingConnectionControllers.delete(task.name);
|
|
1571
|
+
void this.#disconnectServer(task.name, {
|
|
1572
|
+
preserveConfig: this.#toolsOnly || cachedTools.has(task.name),
|
|
1573
|
+
preserveSource: cachedTools.has(task.name),
|
|
1574
|
+
preserveTools: cachedTools.has(task.name),
|
|
1575
|
+
}).catch(error => {
|
|
1576
|
+
this.#logLeaseReleaseFailure(task.name, undefined, error);
|
|
1577
|
+
});
|
|
1354
1578
|
}
|
|
1355
1579
|
}
|
|
1356
1580
|
|
|
@@ -1368,26 +1592,39 @@ export class MCPManager {
|
|
|
1368
1592
|
continue;
|
|
1369
1593
|
}
|
|
1370
1594
|
connectedServers.add(name);
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1595
|
+
if (this.#isExactToolPublicationAllowed(name)) {
|
|
1596
|
+
const reconnect = () => this.reconnectServer(name);
|
|
1597
|
+
try {
|
|
1598
|
+
allTools.push(
|
|
1599
|
+
...MCPTool.fromTools(
|
|
1600
|
+
this.#connectionForLease(connection),
|
|
1601
|
+
serverTools,
|
|
1602
|
+
reconnect,
|
|
1603
|
+
this.#exactToolOptions(name, task.config.sharing === "shared"),
|
|
1604
|
+
),
|
|
1605
|
+
);
|
|
1606
|
+
} catch (error) {
|
|
1607
|
+
await this.#cleanupConnectionTasks(connectionTasks);
|
|
1608
|
+
throw error;
|
|
1609
|
+
}
|
|
1382
1610
|
}
|
|
1383
1611
|
} else if (task.tracked.status === "rejected") {
|
|
1384
1612
|
const reason = task.tracked.reason;
|
|
1385
1613
|
this.#retainConnectionCleanupFailure(name, reason);
|
|
1386
1614
|
const message = reason instanceof Error ? reason.message : String(reason);
|
|
1615
|
+
if (!(this.#toolsOnly && reason instanceof MCPExpectedFailure)) {
|
|
1616
|
+
logger.warn("MCP server connection failed during startup", {
|
|
1617
|
+
path: `mcp:${name}`,
|
|
1618
|
+
serverName: name,
|
|
1619
|
+
error: classifyMCPStartupFailure(reason),
|
|
1620
|
+
remediation:
|
|
1621
|
+
"Check the server command and set --timeout <ms> when startup is expected to be slow.",
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1387
1624
|
errors.set(name, this.#serverError(message));
|
|
1388
1625
|
reportedErrors.add(name);
|
|
1389
1626
|
if (this.#toolsOnly && reason instanceof MCPExpectedFailure) {
|
|
1390
|
-
await this.#disconnectServer(name).catch(error => {
|
|
1627
|
+
await this.#disconnectServer(name, { preserveConfig: true }).catch(error => {
|
|
1391
1628
|
this.#logLeaseReleaseFailure(name, this.#connections.get(name), error);
|
|
1392
1629
|
});
|
|
1393
1630
|
}
|
|
@@ -1396,7 +1633,7 @@ export class MCPManager {
|
|
|
1396
1633
|
}
|
|
1397
1634
|
} else {
|
|
1398
1635
|
const cached = cachedTools.get(name);
|
|
1399
|
-
if (cached) {
|
|
1636
|
+
if (cached && this.#isExactToolPublicationAllowed(name)) {
|
|
1400
1637
|
const source = this.#sources.get(name);
|
|
1401
1638
|
const reconnect = () => this.reconnectServer(name);
|
|
1402
1639
|
try {
|
|
@@ -1407,10 +1644,7 @@ export class MCPManager {
|
|
|
1407
1644
|
() => this.#waitForConnection(name).then(connection => this.#connectionForLease(connection)),
|
|
1408
1645
|
source,
|
|
1409
1646
|
reconnect,
|
|
1410
|
-
|
|
1411
|
-
noReplay: task.config.sharing === "shared",
|
|
1412
|
-
inputHandler: () => this.#inputRequestHandler ?? undefined,
|
|
1413
|
-
},
|
|
1647
|
+
this.#exactToolOptions(name, task.config.sharing === "shared"),
|
|
1414
1648
|
),
|
|
1415
1649
|
);
|
|
1416
1650
|
} catch (error) {
|
|
@@ -1431,7 +1665,10 @@ export class MCPManager {
|
|
|
1431
1665
|
}
|
|
1432
1666
|
|
|
1433
1667
|
// Update cached tools
|
|
1434
|
-
if (shouldPublishToolSnapshot)
|
|
1668
|
+
if (shouldPublishToolSnapshot) {
|
|
1669
|
+
this.#publishToolCatalog(allTools);
|
|
1670
|
+
this.#notifyToolsChanged();
|
|
1671
|
+
}
|
|
1435
1672
|
allowBackgroundLogging = true;
|
|
1436
1673
|
|
|
1437
1674
|
return {
|
|
@@ -1443,13 +1680,25 @@ export class MCPManager {
|
|
|
1443
1680
|
}
|
|
1444
1681
|
|
|
1445
1682
|
#replaceServerTools(name: string, tools: CustomTool<TSchema, MCPToolDetails>[]): void {
|
|
1446
|
-
|
|
1683
|
+
if (!this.#isExactToolPublicationAllowed(name)) return;
|
|
1684
|
+
const publishedTools = this.#tools.filter(
|
|
1447
1685
|
tool => !((tool instanceof MCPTool || tool instanceof DeferredMCPTool) && tool.mcpServerName === name),
|
|
1448
1686
|
);
|
|
1449
|
-
this.#
|
|
1687
|
+
if (!this.#suppressedServers.has(name)) publishedTools.push(...tools);
|
|
1450
1688
|
// Stable sort by name so reconnect order does not perturb the array.
|
|
1451
1689
|
// See `sortMCPToolsByName` for the cache-stability rationale.
|
|
1452
|
-
sortMCPToolsByName(
|
|
1690
|
+
sortMCPToolsByName(publishedTools);
|
|
1691
|
+
this.#publishToolCatalog(publishedTools);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
#exactToolOptions(
|
|
1695
|
+
name: string,
|
|
1696
|
+
noReplay: boolean,
|
|
1697
|
+
): { noReplay: boolean; isAvailable?: () => boolean; inputHandler: () => MCPInputRequestHandler | undefined } {
|
|
1698
|
+
const inputHandler = () => this.#inputRequestHandler ?? undefined;
|
|
1699
|
+
return this.#toolsOnly
|
|
1700
|
+
? { noReplay, isAvailable: () => !this.#suppressedServers.has(name), inputHandler }
|
|
1701
|
+
: { noReplay, inputHandler };
|
|
1453
1702
|
}
|
|
1454
1703
|
|
|
1455
1704
|
#triggerNotificationRefresh(serverName: string, kind: "tools" | "resources" | "prompts"): void {
|
|
@@ -1525,7 +1774,463 @@ export class MCPManager {
|
|
|
1525
1774
|
* Get all loaded tools.
|
|
1526
1775
|
*/
|
|
1527
1776
|
getTools(): CustomTool<TSchema, MCPToolDetails>[] {
|
|
1528
|
-
return
|
|
1777
|
+
return this.#tools.filter(tool => !tool.mcpServerName || this.#isExactToolPublicationAllowed(tool.mcpServerName));
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
/**
|
|
1781
|
+
* Read the fenced catalog and publication state atomically.
|
|
1782
|
+
*
|
|
1783
|
+
* An empty catalog is still authoritative after publication or while an
|
|
1784
|
+
* exact control is in flight. Deferred startup may fall back to its discovery
|
|
1785
|
+
* result only for the explicit `unpublished` state.
|
|
1786
|
+
*/
|
|
1787
|
+
getToolCatalogSnapshot(): MCPToolCatalogSnapshot {
|
|
1788
|
+
const exactControlFence =
|
|
1789
|
+
this.#toolsOnly &&
|
|
1790
|
+
(this.#suppressedServers.size > 0 ||
|
|
1791
|
+
this.#pendingExactSuspensions.size > 0 ||
|
|
1792
|
+
this.#pendingExactControlServers.size > 0);
|
|
1793
|
+
return {
|
|
1794
|
+
tools: this.getTools(),
|
|
1795
|
+
publication: exactControlFence ? "fenced" : this.#toolCatalogPublished ? "published" : "unpublished",
|
|
1796
|
+
generation: this.#toolCatalogGeneration,
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
#publishToolCatalog(tools: CustomTool<TSchema, MCPToolDetails>[]): void {
|
|
1801
|
+
this.#tools = tools;
|
|
1802
|
+
this.#toolCatalogGeneration++;
|
|
1803
|
+
this.#toolCatalogPublished = true;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
#isExactToolPublicationAllowed(name: string): boolean {
|
|
1807
|
+
return (
|
|
1808
|
+
!this.#toolsOnly ||
|
|
1809
|
+
(!this.#suppressedServers.has(name) &&
|
|
1810
|
+
!this.#pendingExactSuspensions.has(name) &&
|
|
1811
|
+
!this.#pendingExactControlServers.has(name))
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
isExactServerSuppressed(name: string): boolean {
|
|
1816
|
+
this.#assertExactServerControl();
|
|
1817
|
+
return this.#suppressedServers.has(name);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
#exactServerToolCount(name: string): number {
|
|
1821
|
+
return this.#tools.reduce((count, tool) => (tool.mcpServerName === name ? count + 1 : count), 0);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
#assertExactServerControl(): void {
|
|
1825
|
+
if (!this.#toolsOnly) throw new Error("Exact MCP server controls require a tools-only manager");
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
/**
|
|
1829
|
+
* Prepare one exact-config control without publishing manager state. Session
|
|
1830
|
+
* registry and prompt validation run against `tools`; commit is the single
|
|
1831
|
+
* publication point and abort releases only an unpublished candidate.
|
|
1832
|
+
*/
|
|
1833
|
+
async prepareExactServerControl(
|
|
1834
|
+
action: "suspend" | "resume" | "reconnect",
|
|
1835
|
+
name: string,
|
|
1836
|
+
): Promise<PreparedExactMcpServerControl> {
|
|
1837
|
+
this.#assertExactServerControl();
|
|
1838
|
+
if (!this.#serverConfigs.has(name) && !this.#connections.has(name)) {
|
|
1839
|
+
return this.#settledExactControl({ name, status: "unknown-server", toolCount: 0 });
|
|
1840
|
+
}
|
|
1841
|
+
if (action === "suspend") {
|
|
1842
|
+
if (this.#suppressedServers.has(name)) {
|
|
1843
|
+
return this.#settledExactControl({ name, status: "already-suspended", toolCount: 0 });
|
|
1844
|
+
}
|
|
1845
|
+
this.#pendingExactSuspensions.add(name);
|
|
1846
|
+
await this.#activeSharedRebinds.get(name)?.promise;
|
|
1847
|
+
const tools = this.#tools.filter(
|
|
1848
|
+
tool =>
|
|
1849
|
+
tool.mcpServerName !== name && (!tool.mcpServerName || !this.#suppressedServers.has(tool.mcpServerName)),
|
|
1850
|
+
);
|
|
1851
|
+
return {
|
|
1852
|
+
result: { name, status: "suspended", toolCount: 0 },
|
|
1853
|
+
tools,
|
|
1854
|
+
canCommit: () => true,
|
|
1855
|
+
commit: async () => {
|
|
1856
|
+
this.#suppressedServers.add(name);
|
|
1857
|
+
this.#pendingExactSuspensions.delete(name);
|
|
1858
|
+
this.#reconnectBackoffs.get(name)?.abort(new Error(`MCP server suspended: ${name}`));
|
|
1859
|
+
for (const tool of this.#tools) {
|
|
1860
|
+
if (tool.mcpServerName === name) (tool as MCPTool | DeferredMCPTool).invalidateForSessionControl();
|
|
1861
|
+
}
|
|
1862
|
+
await this.#activeSharedRebinds.get(name)?.promise;
|
|
1863
|
+
},
|
|
1864
|
+
abort: async () => {
|
|
1865
|
+
this.#pendingExactSuspensions.delete(name);
|
|
1866
|
+
},
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
if (action === "resume" && !this.#suppressedServers.has(name)) {
|
|
1870
|
+
return this.#settledExactControl({
|
|
1871
|
+
name,
|
|
1872
|
+
status: "not-suspended",
|
|
1873
|
+
toolCount: this.#exactServerToolCount(name),
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
if (action === "reconnect" && this.#suppressedServers.has(name)) {
|
|
1877
|
+
return this.#settledExactControl({ name, status: "unavailable", toolCount: 0 });
|
|
1878
|
+
}
|
|
1879
|
+
const automaticReconnect = this.#pendingReconnections.get(name);
|
|
1880
|
+
if (automaticReconnect) {
|
|
1881
|
+
this.#pendingExactControlServers.add(name);
|
|
1882
|
+
let connection: MCPServerConnection | null;
|
|
1883
|
+
try {
|
|
1884
|
+
connection = await automaticReconnect;
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
this.#pendingExactControlServers.delete(name);
|
|
1887
|
+
this.#drainDeferredSharedRebinds();
|
|
1888
|
+
throw error;
|
|
1889
|
+
}
|
|
1890
|
+
if (!connection) {
|
|
1891
|
+
this.#pendingExactControlServers.delete(name);
|
|
1892
|
+
this.#drainDeferredSharedRebinds();
|
|
1893
|
+
return this.#settledExactControl({ name, status: "unavailable", toolCount: 0 });
|
|
1894
|
+
}
|
|
1895
|
+
let candidateTools: CustomTool<TSchema, MCPToolDetails>[];
|
|
1896
|
+
try {
|
|
1897
|
+
candidateTools = await this.#buildExactServerTools(name, connection);
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
this.#pendingExactControlServers.delete(name);
|
|
1900
|
+
this.#drainDeferredSharedRebinds();
|
|
1901
|
+
throw error;
|
|
1902
|
+
}
|
|
1903
|
+
return this.#preparedCatalogCommit(
|
|
1904
|
+
name,
|
|
1905
|
+
action === "resume" ? "resumed" : "reconnected",
|
|
1906
|
+
candidateTools,
|
|
1907
|
+
undefined,
|
|
1908
|
+
connection,
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1911
|
+
this.#pendingExactControlServers.add(name);
|
|
1912
|
+
const existing = this.#connections.get(name);
|
|
1913
|
+
if (action === "resume" && existing?.transport.connected) {
|
|
1914
|
+
try {
|
|
1915
|
+
const candidateTools = await this.#buildExactServerTools(name, existing);
|
|
1916
|
+
return this.#preparedCatalogCommit(name, "resumed", candidateTools, undefined, existing);
|
|
1917
|
+
} catch {
|
|
1918
|
+
// A mapped transport can close while suppressed; recover with an
|
|
1919
|
+
// unpublished physical candidate instead of exposing stale state.
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
try {
|
|
1923
|
+
const candidate = await this.#prepareExactReplacement(name);
|
|
1924
|
+
return this.#preparedCatalogCommit(
|
|
1925
|
+
name,
|
|
1926
|
+
action === "resume" ? "resumed" : "reconnected",
|
|
1927
|
+
candidate.tools,
|
|
1928
|
+
candidate,
|
|
1929
|
+
undefined,
|
|
1930
|
+
);
|
|
1931
|
+
} catch (error) {
|
|
1932
|
+
const cleanupFailure = this.#retainConnectionCleanupFailure(name, error);
|
|
1933
|
+
if (cleanupFailure) {
|
|
1934
|
+
logger.error("MCP exact replacement cleanup failed", {
|
|
1935
|
+
path: `mcp:${name}`,
|
|
1936
|
+
error: cleanupFailure,
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
this.#pendingExactControlServers.delete(name);
|
|
1940
|
+
this.#drainDeferredSharedRebinds();
|
|
1941
|
+
return this.#settledExactControl({ name, status: "unavailable", toolCount: 0 });
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
/** Clear all session-local suppression before this owner adopts a new identity. */
|
|
1946
|
+
async prepareExactServerControlReset(): Promise<PreparedExactMcpCatalog> {
|
|
1947
|
+
this.#assertExactServerControl();
|
|
1948
|
+
const prepared: PreparedExactMcpServerControl[] = [];
|
|
1949
|
+
for (const name of [...this.#suppressedServers]) {
|
|
1950
|
+
try {
|
|
1951
|
+
const candidate = await this.prepareExactServerControl("resume", name);
|
|
1952
|
+
if (candidate.result.status !== "resumed") {
|
|
1953
|
+
logger.warn("MCP server did not resume during identity reset", {
|
|
1954
|
+
path: `mcp:${name}`,
|
|
1955
|
+
status: candidate.result.status,
|
|
1956
|
+
});
|
|
1957
|
+
await candidate.abort();
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
prepared.push(candidate);
|
|
1961
|
+
} catch (error) {
|
|
1962
|
+
// A suspended server's reachability must not block identity adoption.
|
|
1963
|
+
logger.warn("MCP server resume failed during identity reset", { path: `mcp:${name}`, error });
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
const resumedNames = new Set(prepared.map(candidate => candidate.result.name));
|
|
1967
|
+
const carriedOver = this.#tools.filter(
|
|
1968
|
+
tool =>
|
|
1969
|
+
!tool.mcpServerName ||
|
|
1970
|
+
(!this.#suppressedServers.has(tool.mcpServerName) && !resumedNames.has(tool.mcpServerName)),
|
|
1971
|
+
);
|
|
1972
|
+
const resumedTools = prepared.flatMap(candidate =>
|
|
1973
|
+
candidate.tools.filter(tool => tool.mcpServerName === candidate.result.name),
|
|
1974
|
+
);
|
|
1975
|
+
const tools = [...carriedOver, ...resumedTools];
|
|
1976
|
+
sortMCPToolsByName(tools);
|
|
1977
|
+
return {
|
|
1978
|
+
tools,
|
|
1979
|
+
canCommit: () => prepared.every(candidate => candidate.canCommit()),
|
|
1980
|
+
commit: async () => {
|
|
1981
|
+
try {
|
|
1982
|
+
for (const candidate of prepared) {
|
|
1983
|
+
if (!candidate.precommit)
|
|
1984
|
+
throw new Error("Exact MCP reset candidate does not support atomic publication");
|
|
1985
|
+
candidate.precommit();
|
|
1986
|
+
}
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
await Promise.allSettled(prepared.map(candidate => candidate.abort()));
|
|
1989
|
+
throw error;
|
|
1990
|
+
}
|
|
1991
|
+
for (const candidate of prepared) {
|
|
1992
|
+
if (!candidate.publish) throw new Error("Exact MCP reset candidate does not support atomic publication");
|
|
1993
|
+
candidate.publish();
|
|
1994
|
+
}
|
|
1995
|
+
this.#publishToolCatalog(tools);
|
|
1996
|
+
this.#suppressedServers.clear();
|
|
1997
|
+
this.#pendingExactSuspensions.clear();
|
|
1998
|
+
this.#drainDeferredSharedRebinds();
|
|
1999
|
+
},
|
|
2000
|
+
abort: async () => {
|
|
2001
|
+
await Promise.allSettled(prepared.map(candidate => candidate.abort()));
|
|
2002
|
+
},
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
#settledExactControl(result: ExactMcpServerControlResult): PreparedExactMcpServerControl {
|
|
2007
|
+
return { result, tools: this.getTools(), canCommit: () => true, commit: async () => {}, abort: async () => {} };
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
async #buildExactServerTools(
|
|
2011
|
+
name: string,
|
|
2012
|
+
connection: MCPServerConnection,
|
|
2013
|
+
): Promise<CustomTool<TSchema, MCPToolDetails>[]> {
|
|
2014
|
+
const config = this.#serverConfigs.get(name) ?? connection.config;
|
|
2015
|
+
const serverTools = await listTools(this.#connectionForLease(connection));
|
|
2016
|
+
const reconnect = () => this.reconnectServer(name);
|
|
2017
|
+
return MCPTool.fromTools(
|
|
2018
|
+
this.#connectionForLease(connection),
|
|
2019
|
+
serverTools,
|
|
2020
|
+
reconnect,
|
|
2021
|
+
this.#exactToolOptions(name, config.sharing === "shared"),
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
async #preparedCatalogCommit(
|
|
2026
|
+
name: string,
|
|
2027
|
+
status: "resumed" | "reconnected",
|
|
2028
|
+
serverTools: CustomTool<TSchema, MCPToolDetails>[],
|
|
2029
|
+
candidate:
|
|
2030
|
+
| {
|
|
2031
|
+
connection: MCPServerConnection;
|
|
2032
|
+
lease: MCPPoolLease;
|
|
2033
|
+
canCommit(): boolean;
|
|
2034
|
+
publish(): void;
|
|
2035
|
+
abort(): Promise<void>;
|
|
2036
|
+
}
|
|
2037
|
+
| undefined,
|
|
2038
|
+
expectedConnection: MCPServerConnection | undefined,
|
|
2039
|
+
): Promise<PreparedExactMcpServerControl> {
|
|
2040
|
+
const tools = [
|
|
2041
|
+
...this.#tools.filter(
|
|
2042
|
+
tool =>
|
|
2043
|
+
tool.mcpServerName !== name && (!tool.mcpServerName || !this.#suppressedServers.has(tool.mcpServerName)),
|
|
2044
|
+
),
|
|
2045
|
+
...serverTools,
|
|
2046
|
+
];
|
|
2047
|
+
sortMCPToolsByName(tools);
|
|
2048
|
+
if (this.#toolsOnly && new Set(tools.map(tool => tool.name)).size !== tools.length) {
|
|
2049
|
+
let abortError: unknown;
|
|
2050
|
+
try {
|
|
2051
|
+
await candidate?.abort();
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
abortError = error;
|
|
2054
|
+
}
|
|
2055
|
+
this.#pendingExactControlServers.delete(name);
|
|
2056
|
+
this.#drainDeferredSharedRebinds();
|
|
2057
|
+
if (abortError !== undefined) throw abortError;
|
|
2058
|
+
return this.#settledExactControl({ name, status: "unavailable", toolCount: 0 });
|
|
2059
|
+
}
|
|
2060
|
+
let settled = false;
|
|
2061
|
+
const precommit = (): void => {
|
|
2062
|
+
if (settled) return;
|
|
2063
|
+
if (
|
|
2064
|
+
candidate
|
|
2065
|
+
? !candidate.canCommit()
|
|
2066
|
+
: expectedConnection &&
|
|
2067
|
+
(this.#connections.get(name) !== expectedConnection || !expectedConnection.transport.connected)
|
|
2068
|
+
) {
|
|
2069
|
+
throw new Error(`MCP exact control predecessor changed before commit: ${name}`);
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
const publish = (): void => {
|
|
2073
|
+
if (settled) return;
|
|
2074
|
+
candidate?.publish();
|
|
2075
|
+
settled = true;
|
|
2076
|
+
if (candidate) {
|
|
2077
|
+
this.#leaseByConnection.set(candidate.connection, candidate.lease);
|
|
2078
|
+
this.#connections.set(name, candidate.connection);
|
|
2079
|
+
this.#registerLease(name, candidate.connection);
|
|
2080
|
+
}
|
|
2081
|
+
for (const tool of this.#tools) {
|
|
2082
|
+
if (tool.mcpServerName === name) (tool as MCPTool | DeferredMCPTool).invalidateForSessionControl();
|
|
2083
|
+
}
|
|
2084
|
+
const publishedTools = [
|
|
2085
|
+
...this.#tools.filter(
|
|
2086
|
+
tool =>
|
|
2087
|
+
tool.mcpServerName !== name &&
|
|
2088
|
+
(!tool.mcpServerName || !this.#suppressedServers.has(tool.mcpServerName)),
|
|
2089
|
+
),
|
|
2090
|
+
...serverTools,
|
|
2091
|
+
];
|
|
2092
|
+
sortMCPToolsByName(publishedTools);
|
|
2093
|
+
this.#publishToolCatalog(publishedTools);
|
|
2094
|
+
this.#suppressedServers.delete(name);
|
|
2095
|
+
this.#pendingExactControlServers.delete(name);
|
|
2096
|
+
this.#drainDeferredSharedRebinds();
|
|
2097
|
+
};
|
|
2098
|
+
return {
|
|
2099
|
+
result: { name, status, toolCount: serverTools.length },
|
|
2100
|
+
tools,
|
|
2101
|
+
canCommit: () =>
|
|
2102
|
+
candidate
|
|
2103
|
+
? candidate.canCommit()
|
|
2104
|
+
: !expectedConnection ||
|
|
2105
|
+
(this.#connections.get(name) === expectedConnection && expectedConnection.transport.connected),
|
|
2106
|
+
precommit,
|
|
2107
|
+
publish,
|
|
2108
|
+
commit: async () => {
|
|
2109
|
+
if (settled) return;
|
|
2110
|
+
try {
|
|
2111
|
+
precommit();
|
|
2112
|
+
publish();
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
let abortError: unknown;
|
|
2115
|
+
try {
|
|
2116
|
+
await candidate?.abort();
|
|
2117
|
+
} catch (cleanupError) {
|
|
2118
|
+
abortError = cleanupError;
|
|
2119
|
+
}
|
|
2120
|
+
this.#pendingExactControlServers.delete(name);
|
|
2121
|
+
this.#drainDeferredSharedRebinds();
|
|
2122
|
+
if (abortError !== undefined) {
|
|
2123
|
+
throw new AggregateError([error, abortError], `MCP exact control commit and cleanup failed: ${name}`);
|
|
2124
|
+
}
|
|
2125
|
+
throw error;
|
|
2126
|
+
}
|
|
2127
|
+
},
|
|
2128
|
+
abort: async () => {
|
|
2129
|
+
if (settled) return;
|
|
2130
|
+
let abortError: unknown;
|
|
2131
|
+
try {
|
|
2132
|
+
await candidate?.abort();
|
|
2133
|
+
} catch (cleanupError) {
|
|
2134
|
+
abortError = cleanupError;
|
|
2135
|
+
}
|
|
2136
|
+
settled = true;
|
|
2137
|
+
this.#pendingExactControlServers.delete(name);
|
|
2138
|
+
this.#drainDeferredSharedRebinds();
|
|
2139
|
+
if (abortError !== undefined) throw abortError;
|
|
2140
|
+
},
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
async #prepareExactReplacement(name: string): Promise<{
|
|
2145
|
+
connection: MCPServerConnection;
|
|
2146
|
+
lease: MCPPoolLease;
|
|
2147
|
+
tools: CustomTool<TSchema, MCPToolDetails>[];
|
|
2148
|
+
canCommit(): boolean;
|
|
2149
|
+
publish(): void;
|
|
2150
|
+
abort(): Promise<void>;
|
|
2151
|
+
}> {
|
|
2152
|
+
const config = this.#serverConfigs.get(name);
|
|
2153
|
+
if (!config) throw new Error(`MCP server unavailable: ${name}`);
|
|
2154
|
+
await this.#closePendingConnectionCleanup(name);
|
|
2155
|
+
const resolved = await this.#resolveAuthConfig(config);
|
|
2156
|
+
const abort = new AbortController();
|
|
2157
|
+
this.#pendingConnectionControllers.set(name, abort);
|
|
2158
|
+
let prepared:
|
|
2159
|
+
| {
|
|
2160
|
+
lease: MCPPoolLease;
|
|
2161
|
+
canCommit(): boolean;
|
|
2162
|
+
publish(): void;
|
|
2163
|
+
abort(): Promise<void>;
|
|
2164
|
+
}
|
|
2165
|
+
| undefined;
|
|
2166
|
+
const abortPrepared = async (): Promise<void> => {
|
|
2167
|
+
if (!prepared) return;
|
|
2168
|
+
try {
|
|
2169
|
+
await prepared.abort();
|
|
2170
|
+
} catch (error) {
|
|
2171
|
+
const cleanupFailure = this.#retainConnectionCleanupFailure(name, error);
|
|
2172
|
+
if (cleanupFailure) {
|
|
2173
|
+
logger.error("MCP exact replacement abort cleanup failed", {
|
|
2174
|
+
path: `mcp:${name}`,
|
|
2175
|
+
error: cleanupFailure,
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
throw error;
|
|
2179
|
+
}
|
|
2180
|
+
};
|
|
2181
|
+
try {
|
|
2182
|
+
prepared = await this.#pool.prepareReplacement(name, resolved, {
|
|
2183
|
+
keyConfig: config,
|
|
2184
|
+
sharingMode:
|
|
2185
|
+
config.sharing === "shared" &&
|
|
2186
|
+
(resolved.type === "http" || resolved.type === "sse" || resolved.type === "stdio")
|
|
2187
|
+
? "shared"
|
|
2188
|
+
: "per-session",
|
|
2189
|
+
sessionId: config.sharing === "shared" ? undefined : this.#sessionId,
|
|
2190
|
+
signal: abort.signal,
|
|
2191
|
+
advertiseRoots: false,
|
|
2192
|
+
effectiveCwd: resolved.type === "stdio" ? (resolved.cwd ?? this.cwd) : this.cwd,
|
|
2193
|
+
capabilityProfile: "tools-only",
|
|
2194
|
+
effectiveHeaders: resolved.type === "http" || resolved.type === "sse" ? resolved.headers : undefined,
|
|
2195
|
+
onRequest: (method, params) => this.#handleServerRequest(method, params),
|
|
2196
|
+
onCleanupPending: error => this.#retainPendingAcquireCleanup(name, error),
|
|
2197
|
+
});
|
|
2198
|
+
const connection = prepared.lease.connection;
|
|
2199
|
+
connection.config = config;
|
|
2200
|
+
const source = this.#sources.get(name) ?? this.#connections.get(name)?._source;
|
|
2201
|
+
if (source) connection._source = source;
|
|
2202
|
+
const definitions = await listTools(prepared.lease.connectionForLease());
|
|
2203
|
+
const reconnect = () => this.reconnectServer(name);
|
|
2204
|
+
const tools = MCPTool.fromTools(
|
|
2205
|
+
prepared.lease.connectionForLease(),
|
|
2206
|
+
definitions,
|
|
2207
|
+
reconnect,
|
|
2208
|
+
this.#exactToolOptions(name, config.sharing === "shared"),
|
|
2209
|
+
);
|
|
2210
|
+
return {
|
|
2211
|
+
connection,
|
|
2212
|
+
lease: prepared.lease,
|
|
2213
|
+
tools,
|
|
2214
|
+
canCommit: prepared.canCommit,
|
|
2215
|
+
publish: prepared.publish,
|
|
2216
|
+
abort: abortPrepared,
|
|
2217
|
+
};
|
|
2218
|
+
} catch (error) {
|
|
2219
|
+
this.#retainConnectionCleanupFailure(name, error);
|
|
2220
|
+
try {
|
|
2221
|
+
await abortPrepared();
|
|
2222
|
+
} catch (abortError) {
|
|
2223
|
+
throw new AggregateError(
|
|
2224
|
+
[error, abortError],
|
|
2225
|
+
`MCP exact replacement preparation and cleanup failed: ${name}`,
|
|
2226
|
+
);
|
|
2227
|
+
}
|
|
2228
|
+
throw error;
|
|
2229
|
+
} finally {
|
|
2230
|
+
if (this.#pendingConnectionControllers.get(name) === abort) {
|
|
2231
|
+
this.#pendingConnectionControllers.delete(name);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
1529
2234
|
}
|
|
1530
2235
|
|
|
1531
2236
|
/**
|
|
@@ -1776,7 +2481,10 @@ export class MCPManager {
|
|
|
1776
2481
|
await this.#disconnectServer(name);
|
|
1777
2482
|
}
|
|
1778
2483
|
|
|
1779
|
-
async #disconnectServer(
|
|
2484
|
+
async #disconnectServer(
|
|
2485
|
+
name: string,
|
|
2486
|
+
options: { preserveConfig?: boolean; preserveSource?: boolean; preserveTools?: boolean } = {},
|
|
2487
|
+
): Promise<void> {
|
|
1780
2488
|
const nextEpoch = (this.#disconnectEpochs.get(name) ?? 0) + 1;
|
|
1781
2489
|
this.#disconnectEpochs.set(name, nextEpoch);
|
|
1782
2490
|
this.#pendingConnectionControllers.get(name)?.abort(new Error(`MCP server disconnected: ${name}`));
|
|
@@ -1786,8 +2494,8 @@ export class MCPManager {
|
|
|
1786
2494
|
this.#pendingConnections.delete(name);
|
|
1787
2495
|
this.#pendingToolLoads.delete(name);
|
|
1788
2496
|
this.#pendingReconnections.delete(name);
|
|
1789
|
-
this.#sources.delete(name);
|
|
1790
|
-
this.#serverConfigs.delete(name);
|
|
2497
|
+
if (!options.preserveSource) this.#sources.delete(name);
|
|
2498
|
+
if (!options.preserveConfig) this.#serverConfigs.delete(name);
|
|
1791
2499
|
this.#pendingResourceRefresh.delete(name);
|
|
1792
2500
|
const connection = this.#connections.get(name);
|
|
1793
2501
|
|
|
@@ -1816,14 +2524,21 @@ export class MCPManager {
|
|
|
1816
2524
|
if (this.#connections.get(name) === connection) this.#connections.delete(name);
|
|
1817
2525
|
}
|
|
1818
2526
|
|
|
1819
|
-
//
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2527
|
+
// Keep a published cache fallback available while its expired live connection closes.
|
|
2528
|
+
if (!options.preserveTools) {
|
|
2529
|
+
const hadTools = this.#tools.some(
|
|
2530
|
+
tool => (tool instanceof MCPTool || tool instanceof DeferredMCPTool) && tool.mcpServerName === name,
|
|
2531
|
+
);
|
|
2532
|
+
const remainingTools = this.#tools.filter(
|
|
2533
|
+
tool => !((tool instanceof MCPTool || tool instanceof DeferredMCPTool) && tool.mcpServerName === name),
|
|
2534
|
+
);
|
|
2535
|
+
if (hadTools) {
|
|
2536
|
+
this.#publishToolCatalog(remainingTools);
|
|
2537
|
+
this.#notifyToolsChanged();
|
|
2538
|
+
} else {
|
|
2539
|
+
this.#tools = remainingTools;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
1827
2542
|
|
|
1828
2543
|
// Notify prompt consumers so stale commands are cleared
|
|
1829
2544
|
if (connection?.prompts?.length) this.#onPromptsChanged?.(name);
|
|
@@ -1926,8 +2641,13 @@ export class MCPManager {
|
|
|
1926
2641
|
this.#pendingResourceRefresh.clear();
|
|
1927
2642
|
this.#sources.clear();
|
|
1928
2643
|
this.#serverConfigs.clear();
|
|
2644
|
+
this.#suppressedServers.clear();
|
|
2645
|
+
this.#pendingExactSuspensions.clear();
|
|
2646
|
+
this.#pendingExactControlServers.clear();
|
|
1929
2647
|
this.#connections.clear();
|
|
1930
|
-
this.#tools
|
|
2648
|
+
const hadTools = this.#tools.length > 0;
|
|
2649
|
+
this.#publishToolCatalog([]);
|
|
2650
|
+
if (hadTools) this.#notifyToolsChanged();
|
|
1931
2651
|
this.#subscribedResources.clear();
|
|
1932
2652
|
const releaseFailures = [
|
|
1933
2653
|
...scopedReleaseFailures,
|
|
@@ -1966,6 +2686,8 @@ export class MCPManager {
|
|
|
1966
2686
|
async reconnectServer(name: string): Promise<MCPServerConnection | null> {
|
|
1967
2687
|
if (this.#connectionSetSealed) return null;
|
|
1968
2688
|
if (this.#scopedLifecycle === "disconnecting") return null;
|
|
2689
|
+
if (this.#pendingExactControlServers.has(name)) return this.#connections.get(name) ?? null;
|
|
2690
|
+
if (this.#suppressedServers.has(name)) return null;
|
|
1969
2691
|
const pending = this.#pendingReconnections.get(name);
|
|
1970
2692
|
if (pending) return pending;
|
|
1971
2693
|
|
|
@@ -2163,6 +2885,9 @@ export class MCPManager {
|
|
|
2163
2885
|
// while we were connecting (e.g. /mcp reload called disconnectAll).
|
|
2164
2886
|
if (
|
|
2165
2887
|
!this.#serverConfigs.has(name) ||
|
|
2888
|
+
this.#suppressedServers.has(name) ||
|
|
2889
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
2890
|
+
this.#pendingExactControlServers.has(name) ||
|
|
2166
2891
|
this.#epoch !== globalEpoch ||
|
|
2167
2892
|
(this.#disconnectEpochs.get(name) ?? 0) !== disconnectEpoch ||
|
|
2168
2893
|
(lifecycleEpoch !== undefined &&
|
|
@@ -2193,19 +2918,28 @@ export class MCPManager {
|
|
|
2193
2918
|
}
|
|
2194
2919
|
try {
|
|
2195
2920
|
const serverTools = await listTools(this.#connectionForLease(connection));
|
|
2196
|
-
|
|
2921
|
+
// An exact control can claim this server while tools load; publishing here
|
|
2922
|
+
// would install a catalog the control never validated.
|
|
2923
|
+
if (
|
|
2924
|
+
!this.#isCurrentConnection(name, config, globalEpoch, disconnectEpoch, connection) ||
|
|
2925
|
+
this.#suppressedServers.has(name) ||
|
|
2926
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
2927
|
+
this.#pendingExactControlServers.has(name)
|
|
2928
|
+
) {
|
|
2197
2929
|
const disconnectError = new Error(`Server "${name}" was disconnected during tool loading`);
|
|
2198
2930
|
await this.#releaseLeasePreservingPrimary(name, connection);
|
|
2199
2931
|
throw disconnectError;
|
|
2200
2932
|
}
|
|
2201
2933
|
const reconnect = () => this.reconnectServer(name);
|
|
2202
|
-
const customTools = MCPTool.fromTools(
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2934
|
+
const customTools = MCPTool.fromTools(
|
|
2935
|
+
this.#connectionForLease(connection),
|
|
2936
|
+
serverTools,
|
|
2937
|
+
reconnect,
|
|
2938
|
+
this.#exactToolOptions(name, config.sharing === "shared"),
|
|
2939
|
+
);
|
|
2940
|
+
if (this.#shouldCacheServerTools(name)) void this.toolCache?.set(name, config, serverTools);
|
|
2207
2941
|
this.#replaceServerTools(name, customTools);
|
|
2208
|
-
this.#
|
|
2942
|
+
this.#notifyToolsChanged();
|
|
2209
2943
|
void this.#loadServerResourcesAndPrompts(name, connection);
|
|
2210
2944
|
return connection;
|
|
2211
2945
|
} catch (error) {
|
|
@@ -2267,15 +3001,17 @@ export class MCPManager {
|
|
|
2267
3001
|
const serverTools = await listTools(facade);
|
|
2268
3002
|
if (!this.#isCurrentConnection(name, connection.config, globalEpoch, disconnectEpoch, connection)) return;
|
|
2269
3003
|
const reconnect = () => this.reconnectServer(name);
|
|
2270
|
-
const customTools = MCPTool.fromTools(
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
3004
|
+
const customTools = MCPTool.fromTools(
|
|
3005
|
+
facade,
|
|
3006
|
+
serverTools,
|
|
3007
|
+
reconnect,
|
|
3008
|
+
this.#exactToolOptions(name, connection.config.sharing === "shared"),
|
|
3009
|
+
);
|
|
3010
|
+
if (this.#shouldCacheServerTools(name)) void this.toolCache?.set(name, connection.config, serverTools);
|
|
2275
3011
|
|
|
2276
3012
|
// Replace tools from this server
|
|
2277
3013
|
this.#replaceServerTools(name, customTools);
|
|
2278
|
-
this.#
|
|
3014
|
+
this.#notifyToolsChanged();
|
|
2279
3015
|
}
|
|
2280
3016
|
|
|
2281
3017
|
/**
|