@gajae-code/coding-agent 0.17.1 → 0.17.4
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 +266 -0
- package/dist/types/async/job-manager.d.ts +7 -6
- 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 +24 -0
- package/dist/types/config/file-lock.d.ts +10 -1
- package/dist/types/config/model-profile-activation.d.ts +63 -4
- package/dist/types/config/model-registry.d.ts +20 -2
- package/dist/types/config/settings-schema.d.ts +11 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
- package/dist/types/coordinator-mcp/server.d.ts +3 -0
- package/dist/types/daemon/operator-contract.d.ts +5 -1
- package/dist/types/eval/js/worker-protocol.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/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/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 +22 -0
- package/dist/types/modes/acp/acp-agent.d.ts +94 -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/thinking-selector.d.ts +2 -1
- 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/manager.d.ts +56 -0
- 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/acp/adapter.d.ts +27 -0
- package/dist/types/sdk/acp/final-text.d.ts +8 -0
- package/dist/types/sdk/broker/broker.d.ts +13 -2
- package/dist/types/sdk/broker/ensure.d.ts +46 -1
- package/dist/types/sdk/broker/identity.d.ts +3 -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/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 +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 +3 -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 +83 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +38 -1
- package/dist/types/sdk/prompt-failure.d.ts +30 -1
- package/dist/types/sdk/prompt-status.d.ts +8 -0
- package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
- package/dist/types/sdk/router/replay-retention-gap-cache.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- 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 +73 -2
- package/dist/types/sdk/transport/stdio.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +168 -21
- 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/thinking.d.ts +3 -1
- package/dist/types/tools/bash.d.ts +37 -11
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/steer-fold.d.ts +31 -0
- package/dist/types/utils/git.d.ts +28 -2
- package/dist/types/utils/mupdf-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf-wasm-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 +60 -9
- 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 +535 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +9 -1
- package/src/cli/skills-cli.ts +46 -2
- package/src/cli.ts +61 -1
- package/src/commands/daemon.ts +65 -69
- package/src/commands/sdk.ts +232 -100
- package/src/commands/setup.ts +2 -0
- package/src/commands/skills.ts +11 -0
- package/src/config/autorouting-tier-map.ts +16 -0
- package/src/config/file-lock.ts +199 -68
- package/src/config/model-profile-activation.ts +406 -53
- package/src/config/model-registry.ts +204 -25
- 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 +175 -8
- package/src/coordinator-mcp/server.ts +528 -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/debug/report-bundle.ts +114 -24
- 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/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/runtime-skill-discovery.ts +61 -3
- package/src/extensibility/shared-events.ts +3 -1
- package/src/gjc-runtime/managed-owner-admission.ts +67 -50
- package/src/gjc-runtime/session-state-lock.ts +165 -24
- package/src/gjc-runtime/ultragoal-succession.ts +31 -3
- package/src/internal-urls/docs-index.generated.ts +21 -19
- 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 +123 -55
- package/src/modes/acp/acp-agent.ts +593 -47
- package/src/modes/acp/acp-event-mapper.ts +9 -0
- package/src/modes/acp/startup-provenance.ts +59 -0
- package/src/modes/components/custom-provider-wizard.ts +308 -7
- package/src/modes/components/model-selector.ts +31 -15
- package/src/modes/components/oauth-selector.ts +9 -0
- package/src/modes/components/session-selector.ts +47 -4
- package/src/modes/components/status-line/segments.ts +6 -1
- package/src/modes/components/thinking-selector.ts +7 -3
- 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/selector-controller.ts +177 -18
- 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/manager.ts +689 -55
- 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 +90 -9
- package/src/sdk/acp/final-text.ts +19 -2
- package/src/sdk/broker/broker.ts +378 -52
- package/src/sdk/broker/ensure.ts +97 -19
- package/src/sdk/broker/identity.ts +9 -1
- package/src/sdk/broker/lifecycle-ledger.ts +95 -31
- package/src/sdk/broker/lifecycle.ts +690 -141
- package/src/sdk/broker/runtime.ts +1 -1
- package/src/sdk/broker/session-index.ts +97 -4
- package/src/sdk/broker/spawn-authority.ts +35 -7
- package/src/sdk/broker/stderr-drainer.ts +59 -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 +721 -84
- package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -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 +687 -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 +636 -9
- package/src/sdk/host/session-runtime.ts +499 -118
- 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/prompt-deadline-flush.ts +334 -0
- package/src/sdk/prompt-deadline-manager.ts +136 -4
- package/src/sdk/prompt-failure.ts +43 -1
- package/src/sdk/prompt-status.ts +8 -0
- package/src/sdk/prompt-tool-boundary.ts +121 -0
- package/src/sdk/protocol/operation-inventory.generated.json +106 -0
- package/src/sdk/router/replay-retention-gap-cache.ts +27 -0
- package/src/sdk/router/session-router.ts +31 -4
- package/src/sdk/session.ts +194 -16
- package/src/sdk/startup-capability.ts +1 -1
- package/src/sdk/transport/relay.ts +71 -6
- package/src/sdk/transport/serve-cli.ts +494 -49
- package/src/sdk/transport/socket.ts +12 -11
- package/src/sdk/transport/stdio.ts +7 -3
- package/src/session/agent-session.ts +2437 -400
- 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 +58 -18
- package/src/session/internal/managed-session-storage.ts +304 -11
- package/src/session/session-manager.ts +382 -44
- package/src/session/session-work-lease.ts +34 -0
- package/src/session-import/codex.ts +12 -3
- package/src/setup/model-onboarding-guidance.ts +1 -1
- package/src/setup/provider-onboarding.ts +397 -24
- package/src/slash-commands/builtin-registry.ts +258 -14
- package/src/task/index.ts +14 -2
- package/src/thinking.ts +21 -2
- package/src/tools/bash.ts +98 -78
- package/src/tools/fetch.ts +119 -20
- 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/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-wasm-embedded.ts +3 -0
- package/src/utils/mupdf-wasm.ts +24 -2
- package/src/utils/mupdf.ts +129 -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";
|
|
@@ -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 =
|
|
@@ -260,6 +268,22 @@ export interface MCPLoadResult {
|
|
|
260
268
|
exaApiKeys: string[];
|
|
261
269
|
}
|
|
262
270
|
|
|
271
|
+
export type MCPToolCatalogPublication = "unpublished" | "published" | "fenced";
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Atomic view of the manager's fenced tool catalog and its publication state.
|
|
275
|
+
*
|
|
276
|
+
* `unpublished` is reserved for the initial deferred window where no manager
|
|
277
|
+
* catalog has been published and no exact control is fencing it. An empty
|
|
278
|
+
* `published` or `fenced` catalog is authoritative and must not be replaced
|
|
279
|
+
* with an in-flight discovery result.
|
|
280
|
+
*/
|
|
281
|
+
export interface MCPToolCatalogSnapshot {
|
|
282
|
+
tools: CustomTool<TSchema, MCPToolDetails>[];
|
|
283
|
+
publication: MCPToolCatalogPublication;
|
|
284
|
+
generation: number;
|
|
285
|
+
}
|
|
286
|
+
|
|
263
287
|
/** Options for discovering and connecting to MCP servers */
|
|
264
288
|
export interface MCPDiscoverOptions {
|
|
265
289
|
/** Whether to load project-level config (default: true) */
|
|
@@ -306,6 +330,41 @@ export interface MCPManagerOptions {
|
|
|
306
330
|
afterLeaseAcquiredForTests?: (name: string, lease: MCPPoolLease) => void | Promise<void>;
|
|
307
331
|
}
|
|
308
332
|
|
|
333
|
+
export type ExactMcpServerControlStatus =
|
|
334
|
+
| "suspended"
|
|
335
|
+
| "already-suspended"
|
|
336
|
+
| "resumed"
|
|
337
|
+
| "not-suspended"
|
|
338
|
+
| "reconnected"
|
|
339
|
+
| "unavailable"
|
|
340
|
+
| "unknown-server";
|
|
341
|
+
|
|
342
|
+
export interface ExactMcpServerControlResult {
|
|
343
|
+
readonly name: string;
|
|
344
|
+
readonly status: ExactMcpServerControlStatus;
|
|
345
|
+
readonly toolCount: number;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* A staged catalog the caller can validate against before it is published.
|
|
350
|
+
* `commit` is the single publication point; `abort` releases anything the
|
|
351
|
+
* preparation opened and leaves the manager on its predecessor.
|
|
352
|
+
*/
|
|
353
|
+
export interface PreparedExactMcpCatalog {
|
|
354
|
+
readonly tools: CustomTool<TSchema, MCPToolDetails>[];
|
|
355
|
+
canCommit(): boolean;
|
|
356
|
+
commit(): Promise<void>;
|
|
357
|
+
abort(): Promise<void>;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export interface PreparedExactMcpServerControl extends PreparedExactMcpCatalog {
|
|
361
|
+
readonly result: ExactMcpServerControlResult;
|
|
362
|
+
/** Validate a resumed replacement before an atomic multi-server publication. */
|
|
363
|
+
precommit?(): void;
|
|
364
|
+
/** Publish after every replacement in the catalog has passed precommit. */
|
|
365
|
+
publish?(): void;
|
|
366
|
+
}
|
|
367
|
+
|
|
309
368
|
/**
|
|
310
369
|
* MCP Server Manager.
|
|
311
370
|
*
|
|
@@ -342,6 +401,8 @@ export class MCPManager {
|
|
|
342
401
|
readonly #retiredLeaseReleases = new Set<RetiredLeaseRelease>();
|
|
343
402
|
#nextScopedOperationId = 1;
|
|
344
403
|
#tools: CustomTool<TSchema, MCPToolDetails>[] = [];
|
|
404
|
+
#toolCatalogGeneration = 0;
|
|
405
|
+
#toolCatalogPublished = false;
|
|
345
406
|
#pendingConnections = new Map<string, Promise<MCPServerConnection>>();
|
|
346
407
|
#pendingConnectionControllers = new Map<string, AbortController>();
|
|
347
408
|
#pendingToolLoads = new Map<string, Promise<ToolLoadResult>>();
|
|
@@ -365,6 +426,13 @@ export class MCPManager {
|
|
|
365
426
|
#reconnectBackoffs = new Map<string, AbortController>();
|
|
366
427
|
/** Preserved configs for reconnection after connection loss. */
|
|
367
428
|
#serverConfigs = new Map<string, MCPServerConfig>();
|
|
429
|
+
// Exact MCP replacements retain cleanup ownership and cancel in-flight candidates.
|
|
430
|
+
/** Session-local operator suppression; never persisted to MCP config. */
|
|
431
|
+
#suppressedServers = new Set<string>();
|
|
432
|
+
/** Prepare-phase fence that prevents a concurrent close/rebind publication. */
|
|
433
|
+
#pendingExactSuspensions = new Set<string>();
|
|
434
|
+
/** Single per-server candidate axis shared against automatic reconnect. */
|
|
435
|
+
#pendingExactControlServers = new Set<string>();
|
|
368
436
|
/** Monotonic epoch incremented on disconnectAll to invalidate stale reconnections. */
|
|
369
437
|
#epoch = 0;
|
|
370
438
|
#scopedLifecycle: "open" | "disconnecting" | "reconnecting" = "open";
|
|
@@ -539,7 +607,11 @@ export class MCPManager {
|
|
|
539
607
|
}
|
|
540
608
|
|
|
541
609
|
#scheduleSharedRebind(name: string, lease: MCPPoolLease): void {
|
|
542
|
-
if (this.#
|
|
610
|
+
if (this.#pendingExactSuspensions.has(name) || this.#pendingExactControlServers.has(name)) {
|
|
611
|
+
this.#queueDeferredSharedRebind(name, lease, this.#epoch);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (this.#scopedLifecycle === "disconnecting" || this.#suppressedServers.has(name)) return;
|
|
543
615
|
const active = this.#activeSharedRebinds.get(name);
|
|
544
616
|
if (active) {
|
|
545
617
|
if (active.lease !== lease) this.#queueDeferredSharedRebind(name, lease, this.#epoch);
|
|
@@ -562,6 +634,15 @@ export class MCPManager {
|
|
|
562
634
|
#drainDeferredSharedRebinds(): void {
|
|
563
635
|
if (this.#scopedLifecycle !== "open" || this.#deferredSharedRebinds.size === 0) return;
|
|
564
636
|
for (const [name, pending] of this.#deferredSharedRebinds.entries()) {
|
|
637
|
+
// A suspended or in-flight control still owns this server. Keep the newest
|
|
638
|
+
// replacement queued so the manager rebinds after suppression clears instead
|
|
639
|
+
// of staying bound to a retired pool generation.
|
|
640
|
+
if (
|
|
641
|
+
this.#suppressedServers.has(name) ||
|
|
642
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
643
|
+
this.#pendingExactControlServers.has(name)
|
|
644
|
+
)
|
|
645
|
+
continue;
|
|
565
646
|
if (pending.managerEpoch !== this.#epoch) {
|
|
566
647
|
this.#deferredSharedRebinds.delete(name);
|
|
567
648
|
continue;
|
|
@@ -614,7 +695,14 @@ export class MCPManager {
|
|
|
614
695
|
this.#handleServerNotification(name, event.method, event.params);
|
|
615
696
|
return;
|
|
616
697
|
}
|
|
617
|
-
if (
|
|
698
|
+
if (
|
|
699
|
+
event.type !== "close" ||
|
|
700
|
+
this.#connectionSetSealed ||
|
|
701
|
+
this.#suppressedServers.has(name) ||
|
|
702
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
703
|
+
this.#pendingExactControlServers.has(name)
|
|
704
|
+
)
|
|
705
|
+
return;
|
|
618
706
|
void this.reconnectServer(name);
|
|
619
707
|
}),
|
|
620
708
|
);
|
|
@@ -622,7 +710,12 @@ export class MCPManager {
|
|
|
622
710
|
|
|
623
711
|
async #rebindAfterSharedRestart(name: string, lease: MCPPoolLease): Promise<void> {
|
|
624
712
|
const managerEpoch = this.#epoch;
|
|
625
|
-
if (
|
|
713
|
+
if (
|
|
714
|
+
this.#scopedLifecycle === "disconnecting" ||
|
|
715
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
716
|
+
this.#pendingExactControlServers.has(name)
|
|
717
|
+
)
|
|
718
|
+
return;
|
|
626
719
|
if (this.#scopedLifecycle === "reconnecting") {
|
|
627
720
|
this.#queueDeferredSharedRebind(name, lease, managerEpoch);
|
|
628
721
|
return;
|
|
@@ -1067,6 +1160,13 @@ export class MCPManager {
|
|
|
1067
1160
|
"MCP connection cleanup pending";
|
|
1068
1161
|
errors.set(name, this.#serverError(message));
|
|
1069
1162
|
reportedErrors.add(name);
|
|
1163
|
+
if (!this.#toolsOnly) {
|
|
1164
|
+
logger.warn("Skipping MCP autoload registration", {
|
|
1165
|
+
path: `mcp:${name}`,
|
|
1166
|
+
serverName: name,
|
|
1167
|
+
reason: message,
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1070
1170
|
continue;
|
|
1071
1171
|
}
|
|
1072
1172
|
if (sources[name]) {
|
|
@@ -1080,11 +1180,14 @@ export class MCPManager {
|
|
|
1080
1180
|
// Skip if already connected.
|
|
1081
1181
|
if (this.#connections.has(name)) {
|
|
1082
1182
|
connectedServers.add(name);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1183
|
+
if (this.#isExactToolPublicationAllowed(name)) {
|
|
1184
|
+
allTools.push(
|
|
1185
|
+
...this.#tools.filter(
|
|
1186
|
+
tool =>
|
|
1187
|
+
(tool instanceof MCPTool || tool instanceof DeferredMCPTool) && tool.mcpServerName === name,
|
|
1188
|
+
),
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1088
1191
|
continue;
|
|
1089
1192
|
}
|
|
1090
1193
|
|
|
@@ -1093,14 +1196,29 @@ export class MCPManager {
|
|
|
1093
1196
|
this.#pendingToolLoads.has(name) ||
|
|
1094
1197
|
this.#pendingReconnections.has(name)
|
|
1095
1198
|
) {
|
|
1199
|
+
if (!this.#toolsOnly) {
|
|
1200
|
+
logger.warn("Skipping MCP autoload registration", {
|
|
1201
|
+
path: `mcp:${name}`,
|
|
1202
|
+
serverName: name,
|
|
1203
|
+
reason: "connection already pending",
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1096
1206
|
continue;
|
|
1097
1207
|
}
|
|
1098
1208
|
|
|
1099
1209
|
// Validate config
|
|
1100
1210
|
const validationErrors = validateServerConfig(name, config);
|
|
1101
1211
|
if (validationErrors.length > 0) {
|
|
1102
|
-
|
|
1212
|
+
const reason = validationErrors.join("; ");
|
|
1213
|
+
errors.set(name, this.#serverError(reason));
|
|
1103
1214
|
reportedErrors.add(name);
|
|
1215
|
+
if (!this.#toolsOnly) {
|
|
1216
|
+
logger.warn("Skipping MCP autoload registration", {
|
|
1217
|
+
path: `mcp:${name}`,
|
|
1218
|
+
serverName: name,
|
|
1219
|
+
reason,
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1104
1222
|
continue;
|
|
1105
1223
|
}
|
|
1106
1224
|
|
|
@@ -1237,10 +1355,12 @@ export class MCPManager {
|
|
|
1237
1355
|
return;
|
|
1238
1356
|
this.#pendingToolLoads.delete(name);
|
|
1239
1357
|
const reconnect = () => this.reconnectServer(name);
|
|
1240
|
-
const customTools = MCPTool.fromTools(
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1358
|
+
const customTools = MCPTool.fromTools(
|
|
1359
|
+
this.#connectionForLease(connection),
|
|
1360
|
+
serverTools,
|
|
1361
|
+
reconnect,
|
|
1362
|
+
this.#exactToolOptions(name, config.sharing === "shared"),
|
|
1363
|
+
);
|
|
1244
1364
|
this.#replaceServerTools(name, customTools);
|
|
1245
1365
|
if (!this.#toolsOnly) this.#onToolsChanged?.(this.#tools);
|
|
1246
1366
|
if (!this.#toolsOnly) void this.toolCache?.set(name, config, serverTools);
|
|
@@ -1250,9 +1370,11 @@ export class MCPManager {
|
|
|
1250
1370
|
this.#retainConnectionCleanupFailure(name, error);
|
|
1251
1371
|
if (this.#pendingToolLoads.get(name) !== toolsPromise) return;
|
|
1252
1372
|
this.#pendingToolLoads.delete(name);
|
|
1253
|
-
if (!allowBackgroundLogging || reportedErrors.has(name)
|
|
1373
|
+
if (!allowBackgroundLogging || this.#toolsOnly || reportedErrors.has(name)) return;
|
|
1254
1374
|
const message = error instanceof Error ? error.message : String(error);
|
|
1255
|
-
|
|
1375
|
+
errors.set(name, this.#serverError(message));
|
|
1376
|
+
reportedErrors.add(name);
|
|
1377
|
+
logger.error("MCP tool load failed", { path: `mcp:${name}`, serverName: name, error: message });
|
|
1256
1378
|
});
|
|
1257
1379
|
}
|
|
1258
1380
|
|
|
@@ -1331,12 +1453,22 @@ export class MCPManager {
|
|
|
1331
1453
|
if (!this.#toolsOnly && withinDeclaredConnectionWindow(task.config, startupElapsedMs)) {
|
|
1332
1454
|
logger.warn("MCP server still connecting after the startup wait", {
|
|
1333
1455
|
path: `mcp:${task.name}`,
|
|
1456
|
+
serverName: task.name,
|
|
1334
1457
|
startupWaitMs: startupTimeoutMs,
|
|
1335
1458
|
declaredTimeoutMs: task.config.timeout,
|
|
1336
1459
|
});
|
|
1337
1460
|
continue;
|
|
1338
1461
|
}
|
|
1339
1462
|
const message = `MCP server connection timed out during startup: ${task.name}`;
|
|
1463
|
+
if (!this.#toolsOnly) {
|
|
1464
|
+
logger.warn("MCP server connection timed out during startup", {
|
|
1465
|
+
path: `mcp:${task.name}`,
|
|
1466
|
+
serverName: task.name,
|
|
1467
|
+
startupWaitMs: startupTimeoutMs,
|
|
1468
|
+
declaredTimeoutMs: task.config.timeout,
|
|
1469
|
+
remediation: "Set a per-server timeout with `gjc mcp add --timeout <ms>` for slower servers.",
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1340
1472
|
errors.set(task.name, this.#serverError(message));
|
|
1341
1473
|
reportedErrors.add(task.name);
|
|
1342
1474
|
task.connectionAbort.abort(new Error(message));
|
|
@@ -1344,7 +1476,7 @@ export class MCPManager {
|
|
|
1344
1476
|
if (this.#pendingToolLoads.get(task.name) === task.toolsPromise)
|
|
1345
1477
|
this.#pendingToolLoads.delete(task.name);
|
|
1346
1478
|
this.#pendingConnectionControllers.delete(task.name);
|
|
1347
|
-
void this.#disconnectServer(task.name).catch(error => {
|
|
1479
|
+
void this.#disconnectServer(task.name, { preserveConfig: this.#toolsOnly }).catch(error => {
|
|
1348
1480
|
this.#logLeaseReleaseFailure(task.name, undefined, error);
|
|
1349
1481
|
});
|
|
1350
1482
|
}
|
|
@@ -1368,26 +1500,39 @@ export class MCPManager {
|
|
|
1368
1500
|
continue;
|
|
1369
1501
|
}
|
|
1370
1502
|
connectedServers.add(name);
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1503
|
+
if (this.#isExactToolPublicationAllowed(name)) {
|
|
1504
|
+
const reconnect = () => this.reconnectServer(name);
|
|
1505
|
+
try {
|
|
1506
|
+
allTools.push(
|
|
1507
|
+
...MCPTool.fromTools(
|
|
1508
|
+
this.#connectionForLease(connection),
|
|
1509
|
+
serverTools,
|
|
1510
|
+
reconnect,
|
|
1511
|
+
this.#exactToolOptions(name, task.config.sharing === "shared"),
|
|
1512
|
+
),
|
|
1513
|
+
);
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
await this.#cleanupConnectionTasks(connectionTasks);
|
|
1516
|
+
throw error;
|
|
1517
|
+
}
|
|
1382
1518
|
}
|
|
1383
1519
|
} else if (task.tracked.status === "rejected") {
|
|
1384
1520
|
const reason = task.tracked.reason;
|
|
1385
1521
|
this.#retainConnectionCleanupFailure(name, reason);
|
|
1386
1522
|
const message = reason instanceof Error ? reason.message : String(reason);
|
|
1523
|
+
if (!(this.#toolsOnly && reason instanceof MCPExpectedFailure)) {
|
|
1524
|
+
logger.warn("MCP server connection failed during startup", {
|
|
1525
|
+
path: `mcp:${name}`,
|
|
1526
|
+
serverName: name,
|
|
1527
|
+
error: message,
|
|
1528
|
+
remediation:
|
|
1529
|
+
"Check the server command and set --timeout <ms> when startup is expected to be slow.",
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1387
1532
|
errors.set(name, this.#serverError(message));
|
|
1388
1533
|
reportedErrors.add(name);
|
|
1389
1534
|
if (this.#toolsOnly && reason instanceof MCPExpectedFailure) {
|
|
1390
|
-
await this.#disconnectServer(name).catch(error => {
|
|
1535
|
+
await this.#disconnectServer(name, { preserveConfig: true }).catch(error => {
|
|
1391
1536
|
this.#logLeaseReleaseFailure(name, this.#connections.get(name), error);
|
|
1392
1537
|
});
|
|
1393
1538
|
}
|
|
@@ -1396,7 +1541,7 @@ export class MCPManager {
|
|
|
1396
1541
|
}
|
|
1397
1542
|
} else {
|
|
1398
1543
|
const cached = cachedTools.get(name);
|
|
1399
|
-
if (cached) {
|
|
1544
|
+
if (cached && this.#isExactToolPublicationAllowed(name)) {
|
|
1400
1545
|
const source = this.#sources.get(name);
|
|
1401
1546
|
const reconnect = () => this.reconnectServer(name);
|
|
1402
1547
|
try {
|
|
@@ -1407,10 +1552,7 @@ export class MCPManager {
|
|
|
1407
1552
|
() => this.#waitForConnection(name).then(connection => this.#connectionForLease(connection)),
|
|
1408
1553
|
source,
|
|
1409
1554
|
reconnect,
|
|
1410
|
-
|
|
1411
|
-
noReplay: task.config.sharing === "shared",
|
|
1412
|
-
inputHandler: () => this.#inputRequestHandler ?? undefined,
|
|
1413
|
-
},
|
|
1555
|
+
this.#exactToolOptions(name, task.config.sharing === "shared"),
|
|
1414
1556
|
),
|
|
1415
1557
|
);
|
|
1416
1558
|
} catch (error) {
|
|
@@ -1431,7 +1573,7 @@ export class MCPManager {
|
|
|
1431
1573
|
}
|
|
1432
1574
|
|
|
1433
1575
|
// Update cached tools
|
|
1434
|
-
if (shouldPublishToolSnapshot) this.#
|
|
1576
|
+
if (shouldPublishToolSnapshot) this.#publishToolCatalog(allTools);
|
|
1435
1577
|
allowBackgroundLogging = true;
|
|
1436
1578
|
|
|
1437
1579
|
return {
|
|
@@ -1443,13 +1585,25 @@ export class MCPManager {
|
|
|
1443
1585
|
}
|
|
1444
1586
|
|
|
1445
1587
|
#replaceServerTools(name: string, tools: CustomTool<TSchema, MCPToolDetails>[]): void {
|
|
1446
|
-
|
|
1588
|
+
if (!this.#isExactToolPublicationAllowed(name)) return;
|
|
1589
|
+
const publishedTools = this.#tools.filter(
|
|
1447
1590
|
tool => !((tool instanceof MCPTool || tool instanceof DeferredMCPTool) && tool.mcpServerName === name),
|
|
1448
1591
|
);
|
|
1449
|
-
this.#
|
|
1592
|
+
if (!this.#suppressedServers.has(name)) publishedTools.push(...tools);
|
|
1450
1593
|
// Stable sort by name so reconnect order does not perturb the array.
|
|
1451
1594
|
// See `sortMCPToolsByName` for the cache-stability rationale.
|
|
1452
|
-
sortMCPToolsByName(
|
|
1595
|
+
sortMCPToolsByName(publishedTools);
|
|
1596
|
+
this.#publishToolCatalog(publishedTools);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
#exactToolOptions(
|
|
1600
|
+
name: string,
|
|
1601
|
+
noReplay: boolean,
|
|
1602
|
+
): { noReplay: boolean; isAvailable?: () => boolean; inputHandler: () => MCPInputRequestHandler | undefined } {
|
|
1603
|
+
const inputHandler = () => this.#inputRequestHandler ?? undefined;
|
|
1604
|
+
return this.#toolsOnly
|
|
1605
|
+
? { noReplay, isAvailable: () => !this.#suppressedServers.has(name), inputHandler }
|
|
1606
|
+
: { noReplay, inputHandler };
|
|
1453
1607
|
}
|
|
1454
1608
|
|
|
1455
1609
|
#triggerNotificationRefresh(serverName: string, kind: "tools" | "resources" | "prompts"): void {
|
|
@@ -1525,7 +1679,463 @@ export class MCPManager {
|
|
|
1525
1679
|
* Get all loaded tools.
|
|
1526
1680
|
*/
|
|
1527
1681
|
getTools(): CustomTool<TSchema, MCPToolDetails>[] {
|
|
1528
|
-
return
|
|
1682
|
+
return this.#tools.filter(tool => !tool.mcpServerName || this.#isExactToolPublicationAllowed(tool.mcpServerName));
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/**
|
|
1686
|
+
* Read the fenced catalog and publication state atomically.
|
|
1687
|
+
*
|
|
1688
|
+
* An empty catalog is still authoritative after publication or while an
|
|
1689
|
+
* exact control is in flight. Deferred startup may fall back to its discovery
|
|
1690
|
+
* result only for the explicit `unpublished` state.
|
|
1691
|
+
*/
|
|
1692
|
+
getToolCatalogSnapshot(): MCPToolCatalogSnapshot {
|
|
1693
|
+
const exactControlFence =
|
|
1694
|
+
this.#toolsOnly &&
|
|
1695
|
+
(this.#suppressedServers.size > 0 ||
|
|
1696
|
+
this.#pendingExactSuspensions.size > 0 ||
|
|
1697
|
+
this.#pendingExactControlServers.size > 0);
|
|
1698
|
+
return {
|
|
1699
|
+
tools: this.getTools(),
|
|
1700
|
+
publication: exactControlFence ? "fenced" : this.#toolCatalogPublished ? "published" : "unpublished",
|
|
1701
|
+
generation: this.#toolCatalogGeneration,
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
#publishToolCatalog(tools: CustomTool<TSchema, MCPToolDetails>[]): void {
|
|
1706
|
+
this.#tools = tools;
|
|
1707
|
+
this.#toolCatalogGeneration++;
|
|
1708
|
+
this.#toolCatalogPublished = true;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
#isExactToolPublicationAllowed(name: string): boolean {
|
|
1712
|
+
return (
|
|
1713
|
+
!this.#toolsOnly ||
|
|
1714
|
+
(!this.#suppressedServers.has(name) &&
|
|
1715
|
+
!this.#pendingExactSuspensions.has(name) &&
|
|
1716
|
+
!this.#pendingExactControlServers.has(name))
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
isExactServerSuppressed(name: string): boolean {
|
|
1721
|
+
this.#assertExactServerControl();
|
|
1722
|
+
return this.#suppressedServers.has(name);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
#exactServerToolCount(name: string): number {
|
|
1726
|
+
return this.#tools.reduce((count, tool) => (tool.mcpServerName === name ? count + 1 : count), 0);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
#assertExactServerControl(): void {
|
|
1730
|
+
if (!this.#toolsOnly) throw new Error("Exact MCP server controls require a tools-only manager");
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
/**
|
|
1734
|
+
* Prepare one exact-config control without publishing manager state. Session
|
|
1735
|
+
* registry and prompt validation run against `tools`; commit is the single
|
|
1736
|
+
* publication point and abort releases only an unpublished candidate.
|
|
1737
|
+
*/
|
|
1738
|
+
async prepareExactServerControl(
|
|
1739
|
+
action: "suspend" | "resume" | "reconnect",
|
|
1740
|
+
name: string,
|
|
1741
|
+
): Promise<PreparedExactMcpServerControl> {
|
|
1742
|
+
this.#assertExactServerControl();
|
|
1743
|
+
if (!this.#serverConfigs.has(name) && !this.#connections.has(name)) {
|
|
1744
|
+
return this.#settledExactControl({ name, status: "unknown-server", toolCount: 0 });
|
|
1745
|
+
}
|
|
1746
|
+
if (action === "suspend") {
|
|
1747
|
+
if (this.#suppressedServers.has(name)) {
|
|
1748
|
+
return this.#settledExactControl({ name, status: "already-suspended", toolCount: 0 });
|
|
1749
|
+
}
|
|
1750
|
+
this.#pendingExactSuspensions.add(name);
|
|
1751
|
+
await this.#activeSharedRebinds.get(name)?.promise;
|
|
1752
|
+
const tools = this.#tools.filter(
|
|
1753
|
+
tool =>
|
|
1754
|
+
tool.mcpServerName !== name && (!tool.mcpServerName || !this.#suppressedServers.has(tool.mcpServerName)),
|
|
1755
|
+
);
|
|
1756
|
+
return {
|
|
1757
|
+
result: { name, status: "suspended", toolCount: 0 },
|
|
1758
|
+
tools,
|
|
1759
|
+
canCommit: () => true,
|
|
1760
|
+
commit: async () => {
|
|
1761
|
+
this.#suppressedServers.add(name);
|
|
1762
|
+
this.#pendingExactSuspensions.delete(name);
|
|
1763
|
+
this.#reconnectBackoffs.get(name)?.abort(new Error(`MCP server suspended: ${name}`));
|
|
1764
|
+
for (const tool of this.#tools) {
|
|
1765
|
+
if (tool.mcpServerName === name) (tool as MCPTool | DeferredMCPTool).invalidateForSessionControl();
|
|
1766
|
+
}
|
|
1767
|
+
await this.#activeSharedRebinds.get(name)?.promise;
|
|
1768
|
+
},
|
|
1769
|
+
abort: async () => {
|
|
1770
|
+
this.#pendingExactSuspensions.delete(name);
|
|
1771
|
+
},
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
if (action === "resume" && !this.#suppressedServers.has(name)) {
|
|
1775
|
+
return this.#settledExactControl({
|
|
1776
|
+
name,
|
|
1777
|
+
status: "not-suspended",
|
|
1778
|
+
toolCount: this.#exactServerToolCount(name),
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
if (action === "reconnect" && this.#suppressedServers.has(name)) {
|
|
1782
|
+
return this.#settledExactControl({ name, status: "unavailable", toolCount: 0 });
|
|
1783
|
+
}
|
|
1784
|
+
const automaticReconnect = this.#pendingReconnections.get(name);
|
|
1785
|
+
if (automaticReconnect) {
|
|
1786
|
+
this.#pendingExactControlServers.add(name);
|
|
1787
|
+
let connection: MCPServerConnection | null;
|
|
1788
|
+
try {
|
|
1789
|
+
connection = await automaticReconnect;
|
|
1790
|
+
} catch (error) {
|
|
1791
|
+
this.#pendingExactControlServers.delete(name);
|
|
1792
|
+
this.#drainDeferredSharedRebinds();
|
|
1793
|
+
throw error;
|
|
1794
|
+
}
|
|
1795
|
+
if (!connection) {
|
|
1796
|
+
this.#pendingExactControlServers.delete(name);
|
|
1797
|
+
this.#drainDeferredSharedRebinds();
|
|
1798
|
+
return this.#settledExactControl({ name, status: "unavailable", toolCount: 0 });
|
|
1799
|
+
}
|
|
1800
|
+
let candidateTools: CustomTool<TSchema, MCPToolDetails>[];
|
|
1801
|
+
try {
|
|
1802
|
+
candidateTools = await this.#buildExactServerTools(name, connection);
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
this.#pendingExactControlServers.delete(name);
|
|
1805
|
+
this.#drainDeferredSharedRebinds();
|
|
1806
|
+
throw error;
|
|
1807
|
+
}
|
|
1808
|
+
return this.#preparedCatalogCommit(
|
|
1809
|
+
name,
|
|
1810
|
+
action === "resume" ? "resumed" : "reconnected",
|
|
1811
|
+
candidateTools,
|
|
1812
|
+
undefined,
|
|
1813
|
+
connection,
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
this.#pendingExactControlServers.add(name);
|
|
1817
|
+
const existing = this.#connections.get(name);
|
|
1818
|
+
if (action === "resume" && existing?.transport.connected) {
|
|
1819
|
+
try {
|
|
1820
|
+
const candidateTools = await this.#buildExactServerTools(name, existing);
|
|
1821
|
+
return this.#preparedCatalogCommit(name, "resumed", candidateTools, undefined, existing);
|
|
1822
|
+
} catch {
|
|
1823
|
+
// A mapped transport can close while suppressed; recover with an
|
|
1824
|
+
// unpublished physical candidate instead of exposing stale state.
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
try {
|
|
1828
|
+
const candidate = await this.#prepareExactReplacement(name);
|
|
1829
|
+
return this.#preparedCatalogCommit(
|
|
1830
|
+
name,
|
|
1831
|
+
action === "resume" ? "resumed" : "reconnected",
|
|
1832
|
+
candidate.tools,
|
|
1833
|
+
candidate,
|
|
1834
|
+
undefined,
|
|
1835
|
+
);
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
const cleanupFailure = this.#retainConnectionCleanupFailure(name, error);
|
|
1838
|
+
if (cleanupFailure) {
|
|
1839
|
+
logger.error("MCP exact replacement cleanup failed", {
|
|
1840
|
+
path: `mcp:${name}`,
|
|
1841
|
+
error: cleanupFailure,
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
this.#pendingExactControlServers.delete(name);
|
|
1845
|
+
this.#drainDeferredSharedRebinds();
|
|
1846
|
+
return this.#settledExactControl({ name, status: "unavailable", toolCount: 0 });
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
/** Clear all session-local suppression before this owner adopts a new identity. */
|
|
1851
|
+
async prepareExactServerControlReset(): Promise<PreparedExactMcpCatalog> {
|
|
1852
|
+
this.#assertExactServerControl();
|
|
1853
|
+
const prepared: PreparedExactMcpServerControl[] = [];
|
|
1854
|
+
for (const name of [...this.#suppressedServers]) {
|
|
1855
|
+
try {
|
|
1856
|
+
const candidate = await this.prepareExactServerControl("resume", name);
|
|
1857
|
+
if (candidate.result.status !== "resumed") {
|
|
1858
|
+
logger.warn("MCP server did not resume during identity reset", {
|
|
1859
|
+
path: `mcp:${name}`,
|
|
1860
|
+
status: candidate.result.status,
|
|
1861
|
+
});
|
|
1862
|
+
await candidate.abort();
|
|
1863
|
+
continue;
|
|
1864
|
+
}
|
|
1865
|
+
prepared.push(candidate);
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
// A suspended server's reachability must not block identity adoption.
|
|
1868
|
+
logger.warn("MCP server resume failed during identity reset", { path: `mcp:${name}`, error });
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
const resumedNames = new Set(prepared.map(candidate => candidate.result.name));
|
|
1872
|
+
const carriedOver = this.#tools.filter(
|
|
1873
|
+
tool =>
|
|
1874
|
+
!tool.mcpServerName ||
|
|
1875
|
+
(!this.#suppressedServers.has(tool.mcpServerName) && !resumedNames.has(tool.mcpServerName)),
|
|
1876
|
+
);
|
|
1877
|
+
const resumedTools = prepared.flatMap(candidate =>
|
|
1878
|
+
candidate.tools.filter(tool => tool.mcpServerName === candidate.result.name),
|
|
1879
|
+
);
|
|
1880
|
+
const tools = [...carriedOver, ...resumedTools];
|
|
1881
|
+
sortMCPToolsByName(tools);
|
|
1882
|
+
return {
|
|
1883
|
+
tools,
|
|
1884
|
+
canCommit: () => prepared.every(candidate => candidate.canCommit()),
|
|
1885
|
+
commit: async () => {
|
|
1886
|
+
try {
|
|
1887
|
+
for (const candidate of prepared) {
|
|
1888
|
+
if (!candidate.precommit)
|
|
1889
|
+
throw new Error("Exact MCP reset candidate does not support atomic publication");
|
|
1890
|
+
candidate.precommit();
|
|
1891
|
+
}
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
await Promise.allSettled(prepared.map(candidate => candidate.abort()));
|
|
1894
|
+
throw error;
|
|
1895
|
+
}
|
|
1896
|
+
for (const candidate of prepared) {
|
|
1897
|
+
if (!candidate.publish) throw new Error("Exact MCP reset candidate does not support atomic publication");
|
|
1898
|
+
candidate.publish();
|
|
1899
|
+
}
|
|
1900
|
+
this.#publishToolCatalog(tools);
|
|
1901
|
+
this.#suppressedServers.clear();
|
|
1902
|
+
this.#pendingExactSuspensions.clear();
|
|
1903
|
+
this.#drainDeferredSharedRebinds();
|
|
1904
|
+
},
|
|
1905
|
+
abort: async () => {
|
|
1906
|
+
await Promise.allSettled(prepared.map(candidate => candidate.abort()));
|
|
1907
|
+
},
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
#settledExactControl(result: ExactMcpServerControlResult): PreparedExactMcpServerControl {
|
|
1912
|
+
return { result, tools: this.getTools(), canCommit: () => true, commit: async () => {}, abort: async () => {} };
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
async #buildExactServerTools(
|
|
1916
|
+
name: string,
|
|
1917
|
+
connection: MCPServerConnection,
|
|
1918
|
+
): Promise<CustomTool<TSchema, MCPToolDetails>[]> {
|
|
1919
|
+
const config = this.#serverConfigs.get(name) ?? connection.config;
|
|
1920
|
+
const serverTools = await listTools(this.#connectionForLease(connection));
|
|
1921
|
+
const reconnect = () => this.reconnectServer(name);
|
|
1922
|
+
return MCPTool.fromTools(
|
|
1923
|
+
this.#connectionForLease(connection),
|
|
1924
|
+
serverTools,
|
|
1925
|
+
reconnect,
|
|
1926
|
+
this.#exactToolOptions(name, config.sharing === "shared"),
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
async #preparedCatalogCommit(
|
|
1931
|
+
name: string,
|
|
1932
|
+
status: "resumed" | "reconnected",
|
|
1933
|
+
serverTools: CustomTool<TSchema, MCPToolDetails>[],
|
|
1934
|
+
candidate:
|
|
1935
|
+
| {
|
|
1936
|
+
connection: MCPServerConnection;
|
|
1937
|
+
lease: MCPPoolLease;
|
|
1938
|
+
canCommit(): boolean;
|
|
1939
|
+
publish(): void;
|
|
1940
|
+
abort(): Promise<void>;
|
|
1941
|
+
}
|
|
1942
|
+
| undefined,
|
|
1943
|
+
expectedConnection: MCPServerConnection | undefined,
|
|
1944
|
+
): Promise<PreparedExactMcpServerControl> {
|
|
1945
|
+
const tools = [
|
|
1946
|
+
...this.#tools.filter(
|
|
1947
|
+
tool =>
|
|
1948
|
+
tool.mcpServerName !== name && (!tool.mcpServerName || !this.#suppressedServers.has(tool.mcpServerName)),
|
|
1949
|
+
),
|
|
1950
|
+
...serverTools,
|
|
1951
|
+
];
|
|
1952
|
+
sortMCPToolsByName(tools);
|
|
1953
|
+
if (this.#toolsOnly && new Set(tools.map(tool => tool.name)).size !== tools.length) {
|
|
1954
|
+
let abortError: unknown;
|
|
1955
|
+
try {
|
|
1956
|
+
await candidate?.abort();
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
abortError = error;
|
|
1959
|
+
}
|
|
1960
|
+
this.#pendingExactControlServers.delete(name);
|
|
1961
|
+
this.#drainDeferredSharedRebinds();
|
|
1962
|
+
if (abortError !== undefined) throw abortError;
|
|
1963
|
+
return this.#settledExactControl({ name, status: "unavailable", toolCount: 0 });
|
|
1964
|
+
}
|
|
1965
|
+
let settled = false;
|
|
1966
|
+
const precommit = (): void => {
|
|
1967
|
+
if (settled) return;
|
|
1968
|
+
if (
|
|
1969
|
+
candidate
|
|
1970
|
+
? !candidate.canCommit()
|
|
1971
|
+
: expectedConnection &&
|
|
1972
|
+
(this.#connections.get(name) !== expectedConnection || !expectedConnection.transport.connected)
|
|
1973
|
+
) {
|
|
1974
|
+
throw new Error(`MCP exact control predecessor changed before commit: ${name}`);
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
const publish = (): void => {
|
|
1978
|
+
if (settled) return;
|
|
1979
|
+
candidate?.publish();
|
|
1980
|
+
settled = true;
|
|
1981
|
+
if (candidate) {
|
|
1982
|
+
this.#leaseByConnection.set(candidate.connection, candidate.lease);
|
|
1983
|
+
this.#connections.set(name, candidate.connection);
|
|
1984
|
+
this.#registerLease(name, candidate.connection);
|
|
1985
|
+
}
|
|
1986
|
+
for (const tool of this.#tools) {
|
|
1987
|
+
if (tool.mcpServerName === name) (tool as MCPTool | DeferredMCPTool).invalidateForSessionControl();
|
|
1988
|
+
}
|
|
1989
|
+
const publishedTools = [
|
|
1990
|
+
...this.#tools.filter(
|
|
1991
|
+
tool =>
|
|
1992
|
+
tool.mcpServerName !== name &&
|
|
1993
|
+
(!tool.mcpServerName || !this.#suppressedServers.has(tool.mcpServerName)),
|
|
1994
|
+
),
|
|
1995
|
+
...serverTools,
|
|
1996
|
+
];
|
|
1997
|
+
sortMCPToolsByName(publishedTools);
|
|
1998
|
+
this.#publishToolCatalog(publishedTools);
|
|
1999
|
+
this.#suppressedServers.delete(name);
|
|
2000
|
+
this.#pendingExactControlServers.delete(name);
|
|
2001
|
+
this.#drainDeferredSharedRebinds();
|
|
2002
|
+
};
|
|
2003
|
+
return {
|
|
2004
|
+
result: { name, status, toolCount: serverTools.length },
|
|
2005
|
+
tools,
|
|
2006
|
+
canCommit: () =>
|
|
2007
|
+
candidate
|
|
2008
|
+
? candidate.canCommit()
|
|
2009
|
+
: !expectedConnection ||
|
|
2010
|
+
(this.#connections.get(name) === expectedConnection && expectedConnection.transport.connected),
|
|
2011
|
+
precommit,
|
|
2012
|
+
publish,
|
|
2013
|
+
commit: async () => {
|
|
2014
|
+
if (settled) return;
|
|
2015
|
+
try {
|
|
2016
|
+
precommit();
|
|
2017
|
+
publish();
|
|
2018
|
+
} catch (error) {
|
|
2019
|
+
let abortError: unknown;
|
|
2020
|
+
try {
|
|
2021
|
+
await candidate?.abort();
|
|
2022
|
+
} catch (cleanupError) {
|
|
2023
|
+
abortError = cleanupError;
|
|
2024
|
+
}
|
|
2025
|
+
this.#pendingExactControlServers.delete(name);
|
|
2026
|
+
this.#drainDeferredSharedRebinds();
|
|
2027
|
+
if (abortError !== undefined) {
|
|
2028
|
+
throw new AggregateError([error, abortError], `MCP exact control commit and cleanup failed: ${name}`);
|
|
2029
|
+
}
|
|
2030
|
+
throw error;
|
|
2031
|
+
}
|
|
2032
|
+
},
|
|
2033
|
+
abort: async () => {
|
|
2034
|
+
if (settled) return;
|
|
2035
|
+
let abortError: unknown;
|
|
2036
|
+
try {
|
|
2037
|
+
await candidate?.abort();
|
|
2038
|
+
} catch (cleanupError) {
|
|
2039
|
+
abortError = cleanupError;
|
|
2040
|
+
}
|
|
2041
|
+
settled = true;
|
|
2042
|
+
this.#pendingExactControlServers.delete(name);
|
|
2043
|
+
this.#drainDeferredSharedRebinds();
|
|
2044
|
+
if (abortError !== undefined) throw abortError;
|
|
2045
|
+
},
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
async #prepareExactReplacement(name: string): Promise<{
|
|
2050
|
+
connection: MCPServerConnection;
|
|
2051
|
+
lease: MCPPoolLease;
|
|
2052
|
+
tools: CustomTool<TSchema, MCPToolDetails>[];
|
|
2053
|
+
canCommit(): boolean;
|
|
2054
|
+
publish(): void;
|
|
2055
|
+
abort(): Promise<void>;
|
|
2056
|
+
}> {
|
|
2057
|
+
const config = this.#serverConfigs.get(name);
|
|
2058
|
+
if (!config) throw new Error(`MCP server unavailable: ${name}`);
|
|
2059
|
+
await this.#closePendingConnectionCleanup(name);
|
|
2060
|
+
const resolved = await this.#resolveAuthConfig(config);
|
|
2061
|
+
const abort = new AbortController();
|
|
2062
|
+
this.#pendingConnectionControllers.set(name, abort);
|
|
2063
|
+
let prepared:
|
|
2064
|
+
| {
|
|
2065
|
+
lease: MCPPoolLease;
|
|
2066
|
+
canCommit(): boolean;
|
|
2067
|
+
publish(): void;
|
|
2068
|
+
abort(): Promise<void>;
|
|
2069
|
+
}
|
|
2070
|
+
| undefined;
|
|
2071
|
+
const abortPrepared = async (): Promise<void> => {
|
|
2072
|
+
if (!prepared) return;
|
|
2073
|
+
try {
|
|
2074
|
+
await prepared.abort();
|
|
2075
|
+
} catch (error) {
|
|
2076
|
+
const cleanupFailure = this.#retainConnectionCleanupFailure(name, error);
|
|
2077
|
+
if (cleanupFailure) {
|
|
2078
|
+
logger.error("MCP exact replacement abort cleanup failed", {
|
|
2079
|
+
path: `mcp:${name}`,
|
|
2080
|
+
error: cleanupFailure,
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
throw error;
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
try {
|
|
2087
|
+
prepared = await this.#pool.prepareReplacement(name, resolved, {
|
|
2088
|
+
keyConfig: config,
|
|
2089
|
+
sharingMode:
|
|
2090
|
+
config.sharing === "shared" &&
|
|
2091
|
+
(resolved.type === "http" || resolved.type === "sse" || resolved.type === "stdio")
|
|
2092
|
+
? "shared"
|
|
2093
|
+
: "per-session",
|
|
2094
|
+
sessionId: config.sharing === "shared" ? undefined : this.#sessionId,
|
|
2095
|
+
signal: abort.signal,
|
|
2096
|
+
advertiseRoots: false,
|
|
2097
|
+
effectiveCwd: resolved.type === "stdio" ? (resolved.cwd ?? this.cwd) : this.cwd,
|
|
2098
|
+
capabilityProfile: "tools-only",
|
|
2099
|
+
effectiveHeaders: resolved.type === "http" || resolved.type === "sse" ? resolved.headers : undefined,
|
|
2100
|
+
onRequest: (method, params) => this.#handleServerRequest(method, params),
|
|
2101
|
+
onCleanupPending: error => this.#retainPendingAcquireCleanup(name, error),
|
|
2102
|
+
});
|
|
2103
|
+
const connection = prepared.lease.connection;
|
|
2104
|
+
connection.config = config;
|
|
2105
|
+
const source = this.#sources.get(name) ?? this.#connections.get(name)?._source;
|
|
2106
|
+
if (source) connection._source = source;
|
|
2107
|
+
const definitions = await listTools(prepared.lease.connectionForLease());
|
|
2108
|
+
const reconnect = () => this.reconnectServer(name);
|
|
2109
|
+
const tools = MCPTool.fromTools(
|
|
2110
|
+
prepared.lease.connectionForLease(),
|
|
2111
|
+
definitions,
|
|
2112
|
+
reconnect,
|
|
2113
|
+
this.#exactToolOptions(name, config.sharing === "shared"),
|
|
2114
|
+
);
|
|
2115
|
+
return {
|
|
2116
|
+
connection,
|
|
2117
|
+
lease: prepared.lease,
|
|
2118
|
+
tools,
|
|
2119
|
+
canCommit: prepared.canCommit,
|
|
2120
|
+
publish: prepared.publish,
|
|
2121
|
+
abort: abortPrepared,
|
|
2122
|
+
};
|
|
2123
|
+
} catch (error) {
|
|
2124
|
+
this.#retainConnectionCleanupFailure(name, error);
|
|
2125
|
+
try {
|
|
2126
|
+
await abortPrepared();
|
|
2127
|
+
} catch (abortError) {
|
|
2128
|
+
throw new AggregateError(
|
|
2129
|
+
[error, abortError],
|
|
2130
|
+
`MCP exact replacement preparation and cleanup failed: ${name}`,
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
throw error;
|
|
2134
|
+
} finally {
|
|
2135
|
+
if (this.#pendingConnectionControllers.get(name) === abort) {
|
|
2136
|
+
this.#pendingConnectionControllers.delete(name);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
1529
2139
|
}
|
|
1530
2140
|
|
|
1531
2141
|
/**
|
|
@@ -1776,7 +2386,7 @@ export class MCPManager {
|
|
|
1776
2386
|
await this.#disconnectServer(name);
|
|
1777
2387
|
}
|
|
1778
2388
|
|
|
1779
|
-
async #disconnectServer(name: string): Promise<void> {
|
|
2389
|
+
async #disconnectServer(name: string, options: { preserveConfig?: boolean } = {}): Promise<void> {
|
|
1780
2390
|
const nextEpoch = (this.#disconnectEpochs.get(name) ?? 0) + 1;
|
|
1781
2391
|
this.#disconnectEpochs.set(name, nextEpoch);
|
|
1782
2392
|
this.#pendingConnectionControllers.get(name)?.abort(new Error(`MCP server disconnected: ${name}`));
|
|
@@ -1787,7 +2397,7 @@ export class MCPManager {
|
|
|
1787
2397
|
this.#pendingToolLoads.delete(name);
|
|
1788
2398
|
this.#pendingReconnections.delete(name);
|
|
1789
2399
|
this.#sources.delete(name);
|
|
1790
|
-
this.#serverConfigs.delete(name);
|
|
2400
|
+
if (!options.preserveConfig) this.#serverConfigs.delete(name);
|
|
1791
2401
|
this.#pendingResourceRefresh.delete(name);
|
|
1792
2402
|
const connection = this.#connections.get(name);
|
|
1793
2403
|
|
|
@@ -1820,10 +2430,15 @@ export class MCPManager {
|
|
|
1820
2430
|
const hadTools = this.#tools.some(
|
|
1821
2431
|
tool => (tool instanceof MCPTool || tool instanceof DeferredMCPTool) && tool.mcpServerName === name,
|
|
1822
2432
|
);
|
|
1823
|
-
|
|
2433
|
+
const remainingTools = this.#tools.filter(
|
|
1824
2434
|
tool => !((tool instanceof MCPTool || tool instanceof DeferredMCPTool) && tool.mcpServerName === name),
|
|
1825
2435
|
);
|
|
1826
|
-
if (hadTools)
|
|
2436
|
+
if (hadTools) {
|
|
2437
|
+
this.#publishToolCatalog(remainingTools);
|
|
2438
|
+
this.#onToolsChanged?.(this.#tools);
|
|
2439
|
+
} else {
|
|
2440
|
+
this.#tools = remainingTools;
|
|
2441
|
+
}
|
|
1827
2442
|
|
|
1828
2443
|
// Notify prompt consumers so stale commands are cleared
|
|
1829
2444
|
if (connection?.prompts?.length) this.#onPromptsChanged?.(name);
|
|
@@ -1926,8 +2541,11 @@ export class MCPManager {
|
|
|
1926
2541
|
this.#pendingResourceRefresh.clear();
|
|
1927
2542
|
this.#sources.clear();
|
|
1928
2543
|
this.#serverConfigs.clear();
|
|
2544
|
+
this.#suppressedServers.clear();
|
|
2545
|
+
this.#pendingExactSuspensions.clear();
|
|
2546
|
+
this.#pendingExactControlServers.clear();
|
|
1929
2547
|
this.#connections.clear();
|
|
1930
|
-
this.#
|
|
2548
|
+
this.#publishToolCatalog([]);
|
|
1931
2549
|
this.#subscribedResources.clear();
|
|
1932
2550
|
const releaseFailures = [
|
|
1933
2551
|
...scopedReleaseFailures,
|
|
@@ -1966,6 +2584,8 @@ export class MCPManager {
|
|
|
1966
2584
|
async reconnectServer(name: string): Promise<MCPServerConnection | null> {
|
|
1967
2585
|
if (this.#connectionSetSealed) return null;
|
|
1968
2586
|
if (this.#scopedLifecycle === "disconnecting") return null;
|
|
2587
|
+
if (this.#pendingExactControlServers.has(name)) return this.#connections.get(name) ?? null;
|
|
2588
|
+
if (this.#suppressedServers.has(name)) return null;
|
|
1969
2589
|
const pending = this.#pendingReconnections.get(name);
|
|
1970
2590
|
if (pending) return pending;
|
|
1971
2591
|
|
|
@@ -2163,6 +2783,9 @@ export class MCPManager {
|
|
|
2163
2783
|
// while we were connecting (e.g. /mcp reload called disconnectAll).
|
|
2164
2784
|
if (
|
|
2165
2785
|
!this.#serverConfigs.has(name) ||
|
|
2786
|
+
this.#suppressedServers.has(name) ||
|
|
2787
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
2788
|
+
this.#pendingExactControlServers.has(name) ||
|
|
2166
2789
|
this.#epoch !== globalEpoch ||
|
|
2167
2790
|
(this.#disconnectEpochs.get(name) ?? 0) !== disconnectEpoch ||
|
|
2168
2791
|
(lifecycleEpoch !== undefined &&
|
|
@@ -2193,16 +2816,25 @@ export class MCPManager {
|
|
|
2193
2816
|
}
|
|
2194
2817
|
try {
|
|
2195
2818
|
const serverTools = await listTools(this.#connectionForLease(connection));
|
|
2196
|
-
|
|
2819
|
+
// An exact control can claim this server while tools load; publishing here
|
|
2820
|
+
// would install a catalog the control never validated.
|
|
2821
|
+
if (
|
|
2822
|
+
!this.#isCurrentConnection(name, config, globalEpoch, disconnectEpoch, connection) ||
|
|
2823
|
+
this.#suppressedServers.has(name) ||
|
|
2824
|
+
this.#pendingExactSuspensions.has(name) ||
|
|
2825
|
+
this.#pendingExactControlServers.has(name)
|
|
2826
|
+
) {
|
|
2197
2827
|
const disconnectError = new Error(`Server "${name}" was disconnected during tool loading`);
|
|
2198
2828
|
await this.#releaseLeasePreservingPrimary(name, connection);
|
|
2199
2829
|
throw disconnectError;
|
|
2200
2830
|
}
|
|
2201
2831
|
const reconnect = () => this.reconnectServer(name);
|
|
2202
|
-
const customTools = MCPTool.fromTools(
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2832
|
+
const customTools = MCPTool.fromTools(
|
|
2833
|
+
this.#connectionForLease(connection),
|
|
2834
|
+
serverTools,
|
|
2835
|
+
reconnect,
|
|
2836
|
+
this.#exactToolOptions(name, config.sharing === "shared"),
|
|
2837
|
+
);
|
|
2206
2838
|
void this.toolCache?.set(name, config, serverTools);
|
|
2207
2839
|
this.#replaceServerTools(name, customTools);
|
|
2208
2840
|
this.#onToolsChanged?.(this.#tools);
|
|
@@ -2267,10 +2899,12 @@ export class MCPManager {
|
|
|
2267
2899
|
const serverTools = await listTools(facade);
|
|
2268
2900
|
if (!this.#isCurrentConnection(name, connection.config, globalEpoch, disconnectEpoch, connection)) return;
|
|
2269
2901
|
const reconnect = () => this.reconnectServer(name);
|
|
2270
|
-
const customTools = MCPTool.fromTools(
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2902
|
+
const customTools = MCPTool.fromTools(
|
|
2903
|
+
facade,
|
|
2904
|
+
serverTools,
|
|
2905
|
+
reconnect,
|
|
2906
|
+
this.#exactToolOptions(name, connection.config.sharing === "shared"),
|
|
2907
|
+
);
|
|
2274
2908
|
void this.toolCache?.set(name, connection.config, serverTools);
|
|
2275
2909
|
|
|
2276
2910
|
// Replace tools from this server
|