@machina.ai/cell-cli-core 1.20.2-rc1 → 1.22.5-rc1
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/agents/delegate-to-agent-tool.d.ts +19 -0
- package/dist/src/agents/delegate-to-agent-tool.js +111 -0
- package/dist/src/agents/delegate-to-agent-tool.js.map +1 -0
- package/dist/src/agents/delegate-to-agent-tool.test.d.ts +6 -0
- package/dist/src/agents/delegate-to-agent-tool.test.js +133 -0
- package/dist/src/agents/delegate-to-agent-tool.test.js.map +1 -0
- package/dist/src/agents/executor.js +1 -1
- package/dist/src/agents/executor.js.map +1 -1
- package/dist/src/agents/executor.test.js.map +1 -1
- package/dist/src/agents/registry.d.ts +15 -0
- package/dist/src/agents/registry.js +58 -2
- package/dist/src/agents/registry.js.map +1 -1
- package/dist/src/agents/registry.test.js +61 -0
- package/dist/src/agents/registry.test.js.map +1 -1
- package/dist/src/availability/errorClassification.d.ts +7 -0
- package/dist/src/availability/errorClassification.js +20 -0
- package/dist/src/availability/errorClassification.js.map +1 -0
- package/dist/src/availability/modelAvailabilityService.d.ts +1 -0
- package/dist/src/availability/modelAvailabilityService.js +3 -0
- package/dist/src/availability/modelAvailabilityService.js.map +1 -1
- package/dist/src/availability/modelPolicy.d.ts +8 -1
- package/dist/src/availability/policyCatalog.d.ts +1 -0
- package/dist/src/availability/policyCatalog.js +6 -7
- package/dist/src/availability/policyCatalog.js.map +1 -1
- package/dist/src/availability/policyCatalog.test.js +2 -2
- package/dist/src/availability/policyCatalog.test.js.map +1 -1
- package/dist/src/availability/policyHelpers.d.ts +33 -3
- package/dist/src/availability/policyHelpers.js +113 -13
- package/dist/src/availability/policyHelpers.js.map +1 -1
- package/dist/src/availability/policyHelpers.test.js +133 -13
- package/dist/src/availability/policyHelpers.test.js.map +1 -1
- package/dist/src/availability/testUtils.d.ts +10 -0
- package/dist/src/availability/testUtils.js +22 -0
- package/dist/src/availability/testUtils.js.map +1 -0
- package/dist/src/code_assist/experiments/client_metadata.js +2 -1
- package/dist/src/code_assist/experiments/client_metadata.js.map +1 -1
- package/dist/src/code_assist/experiments/client_metadata.test.js +7 -10
- package/dist/src/code_assist/experiments/client_metadata.test.js.map +1 -1
- package/dist/src/code_assist/oauth2.d.ts +2 -0
- package/dist/src/code_assist/oauth2.js +38 -12
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +113 -6
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/commands/init.d.ts +7 -0
- package/dist/src/commands/init.js +53 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/init.test.d.ts +6 -0
- package/dist/src/commands/init.test.js +25 -0
- package/dist/src/commands/init.test.js.map +1 -0
- package/dist/src/commands/restore.d.ts +9 -0
- package/dist/src/commands/restore.js +46 -0
- package/dist/src/commands/restore.js.map +1 -0
- package/dist/src/commands/restore.test.d.ts +6 -0
- package/dist/src/commands/restore.test.js +137 -0
- package/dist/src/commands/restore.test.js.map +1 -0
- package/dist/src/commands/types.d.ts +41 -0
- package/dist/src/commands/types.js +7 -0
- package/dist/src/commands/types.js.map +1 -0
- package/dist/src/config/config.d.ts +29 -3
- package/dist/src/config/config.js +146 -29
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +203 -9
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/defaultModelConfigs.js +21 -0
- package/dist/src/config/defaultModelConfigs.js.map +1 -1
- package/dist/src/config/models.d.ts +33 -11
- package/dist/src/config/models.js +82 -24
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/config/models.test.js +70 -76
- package/dist/src/config/models.test.js.map +1 -1
- package/dist/src/confirmation-bus/message-bus.js +1 -0
- package/dist/src/confirmation-bus/message-bus.js.map +1 -1
- package/dist/src/confirmation-bus/types.d.ts +4 -0
- package/dist/src/core/baseLlmClient.d.ts +3 -1
- package/dist/src/core/baseLlmClient.js +40 -3
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/baseLlmClient.test.js +184 -7
- package/dist/src/core/baseLlmClient.test.js.map +1 -1
- package/dist/src/core/client.js +47 -13
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +133 -6
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.js +5 -3
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +29 -22
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +1 -1
- package/dist/src/core/coreToolScheduler.js +76 -34
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +135 -37
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.js +60 -29
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +236 -188
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/geminiChat_network_retry.test.d.ts +6 -0
- package/dist/src/core/geminiChat_network_retry.test.js +198 -0
- package/dist/src/core/geminiChat_network_retry.test.js.map +1 -0
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +4 -5
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/prompts.js +13 -11
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +19 -8
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/sessionHookTriggers.d.ts +28 -0
- package/dist/src/core/sessionHookTriggers.js +68 -0
- package/dist/src/core/sessionHookTriggers.js.map +1 -0
- package/dist/src/core/turn.d.ts +1 -0
- package/dist/src/core/turn.js +1 -1
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/fallback/handler.js +56 -115
- package/dist/src/fallback/handler.js.map +1 -1
- package/dist/src/fallback/handler.test.js +124 -278
- package/dist/src/fallback/handler.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/hooks/hookEventHandler.js +59 -1
- package/dist/src/hooks/hookEventHandler.js.map +1 -1
- package/dist/src/hooks/hookEventHandler.test.js +8 -1
- package/dist/src/hooks/hookEventHandler.test.js.map +1 -1
- package/dist/src/hooks/hookRegistry.d.ts +0 -7
- package/dist/src/hooks/hookRegistry.js +8 -21
- package/dist/src/hooks/hookRegistry.js.map +1 -1
- package/dist/src/hooks/hookRegistry.test.js +2 -7
- package/dist/src/hooks/hookRegistry.test.js.map +1 -1
- package/dist/src/hooks/hookRunner.js +12 -2
- package/dist/src/hooks/hookRunner.js.map +1 -1
- package/dist/src/hooks/hookRunner.test.js +1 -1
- package/dist/src/hooks/hookRunner.test.js.map +1 -1
- package/dist/src/hooks/hookSystem.d.ts +0 -8
- package/dist/src/hooks/hookSystem.js +0 -18
- package/dist/src/hooks/hookSystem.js.map +1 -1
- package/dist/src/hooks/hookSystem.test.js +123 -18
- package/dist/src/hooks/hookSystem.test.js.map +1 -1
- package/dist/src/hooks/index.d.ts +3 -1
- package/dist/src/hooks/index.js +3 -0
- package/dist/src/hooks/index.js.map +1 -1
- package/dist/src/hooks/types.d.ts +1 -2
- package/dist/src/hooks/types.js +0 -1
- package/dist/src/hooks/types.js.map +1 -1
- package/dist/src/ide/detect-ide.test.js +32 -1
- package/dist/src/ide/detect-ide.test.js.map +1 -1
- package/dist/src/ide/ide-client.js +2 -0
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-installer.test.js +1 -1
- package/dist/src/ide/ide-installer.test.js.map +1 -1
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +8 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/output/json-formatter.d.ts +2 -2
- package/dist/src/output/json-formatter.js +6 -3
- package/dist/src/output/json-formatter.js.map +1 -1
- package/dist/src/output/json-formatter.test.js +37 -9
- package/dist/src/output/json-formatter.test.js.map +1 -1
- package/dist/src/output/stream-json-formatter.js +6 -0
- package/dist/src/output/stream-json-formatter.js.map +1 -1
- package/dist/src/output/stream-json-formatter.test.js +98 -100
- package/dist/src/output/stream-json-formatter.test.js.map +1 -1
- package/dist/src/output/types.d.ts +3 -0
- package/dist/src/output/types.js.map +1 -1
- package/dist/src/policy/config.js +97 -11
- package/dist/src/policy/config.js.map +1 -1
- package/dist/src/policy/persistence.test.d.ts +6 -0
- package/dist/src/policy/persistence.test.js +154 -0
- package/dist/src/policy/persistence.test.js.map +1 -0
- package/dist/src/policy/policies/agent.toml +31 -0
- package/dist/src/policy/policy-engine.d.ts +10 -1
- package/dist/src/policy/policy-engine.js +79 -5
- package/dist/src/policy/policy-engine.js.map +1 -1
- package/dist/src/policy/policy-engine.test.js +26 -2
- package/dist/src/policy/policy-engine.test.js.map +1 -1
- package/dist/src/policy/policy-updater.test.d.ts +6 -0
- package/dist/src/policy/policy-updater.test.js +116 -0
- package/dist/src/policy/policy-updater.test.js.map +1 -0
- package/dist/src/policy/shell-safety.test.d.ts +6 -0
- package/dist/src/policy/shell-safety.test.js +75 -0
- package/dist/src/policy/shell-safety.test.js.map +1 -0
- package/dist/src/policy/toml-loader.d.ts +11 -5
- package/dist/src/policy/toml-loader.js +38 -23
- package/dist/src/policy/toml-loader.js.map +1 -1
- package/dist/src/policy/toml-loader.test.js +28 -7
- package/dist/src/policy/toml-loader.test.js.map +1 -1
- package/dist/src/policy/types.d.ts +15 -0
- package/dist/src/resources/resource-registry.d.ts +30 -0
- package/dist/src/resources/resource-registry.js +57 -0
- package/dist/src/resources/resource-registry.js.map +1 -0
- package/dist/src/resources/resource-registry.test.d.ts +6 -0
- package/dist/src/resources/resource-registry.test.js +54 -0
- package/dist/src/resources/resource-registry.test.js.map +1 -0
- package/dist/src/routing/modelRouterService.js +0 -15
- package/dist/src/routing/modelRouterService.js.map +1 -1
- package/dist/src/routing/modelRouterService.test.js +0 -62
- package/dist/src/routing/modelRouterService.test.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.js +10 -21
- package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.test.js +2 -1
- package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/fallbackStrategy.js +23 -12
- package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
- package/dist/src/routing/strategies/fallbackStrategy.test.js +69 -39
- package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/overrideStrategy.js +4 -3
- package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
- package/dist/src/safety/checker-runner.js +17 -6
- package/dist/src/safety/checker-runner.js.map +1 -1
- package/dist/src/services/chatCompressionService.js +15 -1
- package/dist/src/services/chatCompressionService.js.map +1 -1
- package/dist/src/services/chatCompressionService.test.js +2 -0
- package/dist/src/services/chatCompressionService.test.js.map +1 -1
- package/dist/src/services/chatRecordingService.d.ts +14 -0
- package/dist/src/services/chatRecordingService.js +37 -0
- package/dist/src/services/chatRecordingService.js.map +1 -1
- package/dist/src/services/fileSystemService.d.ts +0 -9
- package/dist/src/services/fileSystemService.js +0 -11
- package/dist/src/services/fileSystemService.js.map +1 -1
- package/dist/src/services/gitService.js +5 -0
- package/dist/src/services/gitService.js.map +1 -1
- package/dist/src/services/gitService.test.js +28 -0
- package/dist/src/services/gitService.test.js.map +1 -1
- package/dist/src/services/loopDetectionService.js +2 -2
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/modelConfig.golden.test.js +32 -0
- package/dist/src/services/modelConfig.golden.test.js.map +1 -1
- package/dist/src/services/modelConfigService.d.ts +3 -0
- package/dist/src/services/modelConfigService.js +3 -2
- package/dist/src/services/modelConfigService.js.map +1 -1
- package/dist/src/services/modelConfigService.test.js +110 -0
- package/dist/src/services/modelConfigService.test.js.map +1 -1
- package/dist/src/services/modelConfigServiceTestUtils.d.ts +10 -0
- package/dist/src/services/modelConfigServiceTestUtils.js +17 -0
- package/dist/src/services/modelConfigServiceTestUtils.js.map +1 -0
- package/dist/src/services/sessionSummaryService.d.ts +28 -0
- package/dist/src/services/sessionSummaryService.js +131 -0
- package/dist/src/services/sessionSummaryService.js.map +1 -0
- package/dist/src/services/sessionSummaryService.test.d.ts +6 -0
- package/dist/src/services/sessionSummaryService.test.js +785 -0
- package/dist/src/services/sessionSummaryService.test.js.map +1 -0
- package/dist/src/services/sessionSummaryUtils.d.ts +16 -0
- package/dist/src/services/sessionSummaryUtils.js +129 -0
- package/dist/src/services/sessionSummaryUtils.js.map +1 -0
- package/dist/src/services/sessionSummaryUtils.test.d.ts +6 -0
- package/dist/src/services/sessionSummaryUtils.test.js +137 -0
- package/dist/src/services/sessionSummaryUtils.test.js.map +1 -0
- package/dist/src/services/shellExecutionService.js +28 -22
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +46 -4
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/services/test-data/resolved-aliases-retry.golden.json +238 -0
- package/dist/src/services/test-data/resolved-aliases.golden.json +16 -0
- package/dist/src/telemetry/activity-detector.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +28 -5
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +67 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +1 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +3 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/config.js +2 -0
- package/dist/src/telemetry/config.js.map +1 -1
- package/dist/src/telemetry/config.test.js +25 -0
- package/dist/src/telemetry/config.test.js.map +1 -1
- package/dist/src/telemetry/gcp-exporters.d.ts +4 -3
- package/dist/src/telemetry/gcp-exporters.js +8 -4
- package/dist/src/telemetry/gcp-exporters.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +1 -1
- package/dist/src/telemetry/index.js +1 -1
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +2 -1
- package/dist/src/telemetry/loggers.js +345 -335
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +20 -5
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/sdk.d.ts +9 -2
- package/dist/src/telemetry/sdk.js +143 -17
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/sdk.test.js +130 -28
- package/dist/src/telemetry/sdk.test.js.map +1 -1
- package/dist/src/telemetry/startupProfiler.js +26 -3
- package/dist/src/telemetry/startupProfiler.js.map +1 -1
- package/dist/src/telemetry/startupProfiler.test.js +49 -7
- package/dist/src/telemetry/startupProfiler.test.js.map +1 -1
- package/dist/src/telemetry/telemetry.test.js +10 -3
- package/dist/src/telemetry/telemetry.test.js.map +1 -1
- package/dist/src/telemetry/trace.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +31 -6
- package/dist/src/telemetry/types.js +47 -8
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +1 -0
- package/dist/src/telemetry/uiTelemetry.js +2 -0
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +4 -0
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/test-utils/mock-message-bus.js.map +1 -1
- package/dist/src/tools/confirmation-policy.test.d.ts +6 -0
- package/dist/src/tools/confirmation-policy.test.js +152 -0
- package/dist/src/tools/confirmation-policy.test.js.map +1 -0
- package/dist/src/tools/edit.js +5 -0
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.d.ts +2 -1
- package/dist/src/tools/mcp-client-manager.js +20 -4
- package/dist/src/tools/mcp-client-manager.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.test.js +13 -10
- package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +39 -3
- package/dist/src/tools/mcp-client.js +433 -168
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +648 -28
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/mcp-tool.js +13 -0
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +25 -0
- package/dist/src/tools/mcp-tool.test.js.map +1 -1
- package/dist/src/tools/memoryTool.js +1 -0
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/modifiable-tool.js.map +1 -1
- package/dist/src/tools/modifiable-tool.test.js +22 -13
- package/dist/src/tools/modifiable-tool.test.js.map +1 -1
- package/dist/src/tools/read-file.js +1 -1
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/read-many-files.js +6 -4
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/read-many-files.test.js +1 -1
- package/dist/src/tools/read-many-files.test.js.map +1 -1
- package/dist/src/tools/shell.d.ts +2 -1
- package/dist/src/tools/shell.js +15 -1
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +2 -1
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.js +5 -0
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js.map +1 -1
- package/dist/src/tools/tool-names.d.ts +2 -0
- package/dist/src/tools/tool-names.js +2 -0
- package/dist/src/tools/tool-names.js.map +1 -1
- package/dist/src/tools/tools.d.ts +19 -0
- package/dist/src/tools/tools.js +29 -8
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.js +17 -4
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-fetch.test.js +1 -0
- package/dist/src/tools/web-fetch.test.js.map +1 -1
- package/dist/src/tools/write-file.js +5 -0
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.d.ts +8 -0
- package/dist/src/utils/bfsFileSearch.js +63 -23
- package/dist/src/utils/bfsFileSearch.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.test.js +65 -1
- package/dist/src/utils/bfsFileSearch.test.js.map +1 -1
- package/dist/src/utils/checkpointUtils.d.ts +82 -0
- package/dist/src/utils/checkpointUtils.js +117 -0
- package/dist/src/utils/checkpointUtils.js.map +1 -0
- package/dist/src/utils/checkpointUtils.test.d.ts +6 -0
- package/dist/src/utils/checkpointUtils.test.js +229 -0
- package/dist/src/utils/checkpointUtils.test.js.map +1 -0
- package/dist/src/utils/debugLogger.d.ts +3 -0
- package/dist/src/utils/debugLogger.js +27 -0
- package/dist/src/utils/debugLogger.js.map +1 -1
- package/dist/src/utils/editCorrector.test.js +4 -0
- package/dist/src/utils/editCorrector.test.js.map +1 -1
- package/dist/src/utils/editor.d.ts +9 -1
- package/dist/src/utils/editor.js +23 -14
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/errors.d.ts +8 -0
- package/dist/src/utils/errors.js +39 -2
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/errors.test.d.ts +6 -0
- package/dist/src/utils/errors.test.js +155 -0
- package/dist/src/utils/errors.test.js.map +1 -0
- package/dist/src/utils/extensionLoader.d.ts +2 -2
- package/dist/src/utils/extensionLoader.js +5 -6
- package/dist/src/utils/extensionLoader.js.map +1 -1
- package/dist/src/utils/extensionLoader.test.js +11 -0
- package/dist/src/utils/extensionLoader.test.js.map +1 -1
- package/dist/src/utils/fetch.d.ts +1 -1
- package/dist/src/utils/fetch.js +3 -3
- package/dist/src/utils/fetch.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +15 -0
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/filesearch/crawlCache.js.map +1 -1
- package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
- package/dist/src/utils/flashFallback.test.js +1 -1
- package/dist/src/utils/flashFallback.test.js.map +1 -1
- package/dist/src/utils/googleErrors.js +31 -18
- package/dist/src/utils/googleErrors.js.map +1 -1
- package/dist/src/utils/googleErrors.test.js +10 -2
- package/dist/src/utils/googleErrors.test.js.map +1 -1
- package/dist/src/utils/googleQuotaErrors.d.ts +3 -3
- package/dist/src/utils/googleQuotaErrors.js +32 -6
- package/dist/src/utils/googleQuotaErrors.js.map +1 -1
- package/dist/src/utils/googleQuotaErrors.test.js +94 -2
- package/dist/src/utils/googleQuotaErrors.test.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.test.js +4 -0
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/src/utils/pathCorrector.js +12 -2
- package/dist/src/utils/pathCorrector.js.map +1 -1
- package/dist/src/utils/pathCorrector.test.js +6 -2
- package/dist/src/utils/pathCorrector.test.js.map +1 -1
- package/dist/src/utils/retry.d.ts +11 -0
- package/dist/src/utils/retry.js +54 -13
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +170 -10
- package/dist/src/utils/retry.test.js.map +1 -1
- package/dist/src/utils/shell-permissions.d.ts +52 -0
- package/dist/src/utils/shell-permissions.js +188 -0
- package/dist/src/utils/shell-permissions.js.map +1 -0
- package/dist/src/utils/shell-permissions.test.d.ts +6 -0
- package/dist/src/utils/shell-permissions.test.js +347 -0
- package/dist/src/utils/shell-permissions.test.js.map +1 -0
- package/dist/src/utils/shell-utils.d.ts +10 -47
- package/dist/src/utils/shell-utils.js +1 -182
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +1 -288
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/src/utils/terminalSerializer.test.js +17 -0
- package/dist/src/utils/terminalSerializer.test.js.map +1 -1
- package/dist/src/utils/tool-utils.js.map +1 -1
- package/dist/src/utils/version.d.ts +6 -0
- package/dist/src/utils/version.js +15 -0
- package/dist/src/utils/version.js.map +1 -0
- package/dist/src/utils/version.test.d.ts +6 -0
- package/dist/src/utils/version.test.js +39 -0
- package/dist/src/utils/version.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -7,13 +7,14 @@ import { GoogleAuth } from 'google-auth-library';
|
|
|
7
7
|
import * as ClientLib from '@modelcontextprotocol/sdk/client/index.js';
|
|
8
8
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
9
9
|
import * as SdkClientStdioLib from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
10
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
10
|
+
import { StreamableHTTPClientTransport, StreamableHTTPError, } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
11
11
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
12
12
|
import { AuthProviderType } from '../config/config.js';
|
|
13
13
|
import { GoogleCredentialProvider } from '../mcp/google-auth-provider.js';
|
|
14
14
|
import { MCPOAuthProvider } from '../mcp/oauth-provider.js';
|
|
15
15
|
import { MCPOAuthTokenStorage } from '../mcp/oauth-token-storage.js';
|
|
16
16
|
import { OAuthUtils } from '../mcp/oauth-utils.js';
|
|
17
|
+
import { ToolListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
17
18
|
import { WorkspaceContext } from '../utils/workspaceContext.js';
|
|
18
19
|
import { connectToMcpServer, createTransport, hasNetworkTransport, isEnabled, McpClient, populateMcpServerCommand, } from './mcp-client.js';
|
|
19
20
|
import * as fs from 'node:fs';
|
|
@@ -60,6 +61,7 @@ describe('mcp-client', () => {
|
|
|
60
61
|
getStatus: vi.fn(),
|
|
61
62
|
registerCapabilities: vi.fn(),
|
|
62
63
|
setRequestHandler: vi.fn(),
|
|
64
|
+
setNotificationHandler: vi.fn(),
|
|
63
65
|
getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
|
|
64
66
|
listTools: vi.fn().mockResolvedValue({
|
|
65
67
|
tools: [
|
|
@@ -84,12 +86,20 @@ describe('mcp-client', () => {
|
|
|
84
86
|
sortTools: vi.fn(),
|
|
85
87
|
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
86
88
|
};
|
|
89
|
+
const promptRegistry = {
|
|
90
|
+
registerPrompt: vi.fn(),
|
|
91
|
+
removePromptsByServer: vi.fn(),
|
|
92
|
+
};
|
|
93
|
+
const resourceRegistry = {
|
|
94
|
+
setResourcesForServer: vi.fn(),
|
|
95
|
+
removeResourcesByServer: vi.fn(),
|
|
96
|
+
};
|
|
87
97
|
const client = new McpClient('test-server', {
|
|
88
98
|
command: 'test-command',
|
|
89
|
-
}, mockedToolRegistry,
|
|
99
|
+
}, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
|
|
90
100
|
await client.connect();
|
|
91
101
|
await client.discover({});
|
|
92
|
-
expect(mockedClient.listTools).toHaveBeenCalledWith({});
|
|
102
|
+
expect(mockedClient.listTools).toHaveBeenCalledWith({}, { timeout: 600000 });
|
|
93
103
|
});
|
|
94
104
|
it('should not skip tools even if a parameter is missing a type', async () => {
|
|
95
105
|
const consoleWarnSpy = vi
|
|
@@ -102,6 +112,7 @@ describe('mcp-client', () => {
|
|
|
102
112
|
getStatus: vi.fn(),
|
|
103
113
|
registerCapabilities: vi.fn(),
|
|
104
114
|
setRequestHandler: vi.fn(),
|
|
115
|
+
setNotificationHandler: vi.fn(),
|
|
105
116
|
getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
|
|
106
117
|
listTools: vi.fn().mockResolvedValue({
|
|
107
118
|
tools: [
|
|
@@ -137,9 +148,17 @@ describe('mcp-client', () => {
|
|
|
137
148
|
sortTools: vi.fn(),
|
|
138
149
|
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
139
150
|
};
|
|
151
|
+
const promptRegistry = {
|
|
152
|
+
registerPrompt: vi.fn(),
|
|
153
|
+
removePromptsByServer: vi.fn(),
|
|
154
|
+
};
|
|
155
|
+
const resourceRegistry = {
|
|
156
|
+
setResourcesForServer: vi.fn(),
|
|
157
|
+
removeResourcesByServer: vi.fn(),
|
|
158
|
+
};
|
|
140
159
|
const client = new McpClient('test-server', {
|
|
141
160
|
command: 'test-command',
|
|
142
|
-
}, mockedToolRegistry,
|
|
161
|
+
}, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
|
|
143
162
|
await client.connect();
|
|
144
163
|
await client.discover({});
|
|
145
164
|
expect(mockedToolRegistry.registerTool).toHaveBeenCalledTimes(2);
|
|
@@ -154,6 +173,7 @@ describe('mcp-client', () => {
|
|
|
154
173
|
getStatus: vi.fn(),
|
|
155
174
|
registerCapabilities: vi.fn(),
|
|
156
175
|
setRequestHandler: vi.fn(),
|
|
176
|
+
setNotificationHandler: vi.fn(),
|
|
157
177
|
getServerCapabilities: vi.fn().mockReturnValue({ prompts: {} }),
|
|
158
178
|
listTools: vi.fn().mockResolvedValue({ tools: [] }),
|
|
159
179
|
listPrompts: vi.fn().mockRejectedValue(new Error('Test error')),
|
|
@@ -165,11 +185,19 @@ describe('mcp-client', () => {
|
|
|
165
185
|
registerTool: vi.fn(),
|
|
166
186
|
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
167
187
|
};
|
|
188
|
+
const promptRegistry = {
|
|
189
|
+
registerPrompt: vi.fn(),
|
|
190
|
+
removePromptsByServer: vi.fn(),
|
|
191
|
+
};
|
|
192
|
+
const resourceRegistry = {
|
|
193
|
+
setResourcesForServer: vi.fn(),
|
|
194
|
+
removeResourcesByServer: vi.fn(),
|
|
195
|
+
};
|
|
168
196
|
const client = new McpClient('test-server', {
|
|
169
197
|
command: 'test-command',
|
|
170
|
-
}, mockedToolRegistry,
|
|
198
|
+
}, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
|
|
171
199
|
await client.connect();
|
|
172
|
-
await expect(client.discover({})).rejects.toThrow('No prompts or
|
|
200
|
+
await expect(client.discover({})).rejects.toThrow('No prompts, tools, or resources found on the server.');
|
|
173
201
|
expect(coreEvents.emitFeedback).toHaveBeenCalledWith('error', `Error discovering prompts from test-server: Test error`, expect.any(Error));
|
|
174
202
|
});
|
|
175
203
|
it('should not discover tools if server does not support them', async () => {
|
|
@@ -180,6 +208,7 @@ describe('mcp-client', () => {
|
|
|
180
208
|
getStatus: vi.fn(),
|
|
181
209
|
registerCapabilities: vi.fn(),
|
|
182
210
|
setRequestHandler: vi.fn(),
|
|
211
|
+
setNotificationHandler: vi.fn(),
|
|
183
212
|
getServerCapabilities: vi.fn().mockReturnValue({ prompts: {} }),
|
|
184
213
|
listPrompts: vi.fn().mockResolvedValue({ prompts: [] }),
|
|
185
214
|
request: vi.fn().mockResolvedValue({}),
|
|
@@ -191,11 +220,19 @@ describe('mcp-client', () => {
|
|
|
191
220
|
sortTools: vi.fn(),
|
|
192
221
|
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
193
222
|
};
|
|
223
|
+
const promptRegistry = {
|
|
224
|
+
registerPrompt: vi.fn(),
|
|
225
|
+
removePromptsByServer: vi.fn(),
|
|
226
|
+
};
|
|
227
|
+
const resourceRegistry = {
|
|
228
|
+
setResourcesForServer: vi.fn(),
|
|
229
|
+
removeResourcesByServer: vi.fn(),
|
|
230
|
+
};
|
|
194
231
|
const client = new McpClient('test-server', {
|
|
195
232
|
command: 'test-command',
|
|
196
|
-
}, mockedToolRegistry,
|
|
233
|
+
}, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
|
|
197
234
|
await client.connect();
|
|
198
|
-
await expect(client.discover({})).rejects.toThrow('No prompts or
|
|
235
|
+
await expect(client.discover({})).rejects.toThrow('No prompts, tools, or resources found on the server.');
|
|
199
236
|
});
|
|
200
237
|
it('should discover tools if server supports them', async () => {
|
|
201
238
|
const mockedClient = {
|
|
@@ -205,6 +242,7 @@ describe('mcp-client', () => {
|
|
|
205
242
|
getStatus: vi.fn(),
|
|
206
243
|
registerCapabilities: vi.fn(),
|
|
207
244
|
setRequestHandler: vi.fn(),
|
|
245
|
+
setNotificationHandler: vi.fn(),
|
|
208
246
|
getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
|
|
209
247
|
listTools: vi.fn().mockResolvedValue({
|
|
210
248
|
tools: [
|
|
@@ -225,9 +263,17 @@ describe('mcp-client', () => {
|
|
|
225
263
|
sortTools: vi.fn(),
|
|
226
264
|
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
227
265
|
};
|
|
266
|
+
const promptRegistry = {
|
|
267
|
+
registerPrompt: vi.fn(),
|
|
268
|
+
removePromptsByServer: vi.fn(),
|
|
269
|
+
};
|
|
270
|
+
const resourceRegistry = {
|
|
271
|
+
setResourcesForServer: vi.fn(),
|
|
272
|
+
removeResourcesByServer: vi.fn(),
|
|
273
|
+
};
|
|
228
274
|
const client = new McpClient('test-server', {
|
|
229
275
|
command: 'test-command',
|
|
230
|
-
}, mockedToolRegistry,
|
|
276
|
+
}, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
|
|
231
277
|
await client.connect();
|
|
232
278
|
await client.discover({});
|
|
233
279
|
expect(mockedToolRegistry.registerTool).toHaveBeenCalledOnce();
|
|
@@ -240,6 +286,7 @@ describe('mcp-client', () => {
|
|
|
240
286
|
getStatus: vi.fn(),
|
|
241
287
|
registerCapabilities: vi.fn(),
|
|
242
288
|
setRequestHandler: vi.fn(),
|
|
289
|
+
setNotificationHandler: vi.fn(),
|
|
243
290
|
getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
|
|
244
291
|
listTools: vi.fn().mockResolvedValue({
|
|
245
292
|
tools: [
|
|
@@ -275,9 +322,17 @@ describe('mcp-client', () => {
|
|
|
275
322
|
sortTools: vi.fn(),
|
|
276
323
|
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
277
324
|
};
|
|
325
|
+
const promptRegistry = {
|
|
326
|
+
registerPrompt: vi.fn(),
|
|
327
|
+
removePromptsByServer: vi.fn(),
|
|
328
|
+
};
|
|
329
|
+
const resourceRegistry = {
|
|
330
|
+
setResourcesForServer: vi.fn(),
|
|
331
|
+
removeResourcesByServer: vi.fn(),
|
|
332
|
+
};
|
|
278
333
|
const client = new McpClient('test-server', {
|
|
279
334
|
command: 'test-command',
|
|
280
|
-
}, mockedToolRegistry,
|
|
335
|
+
}, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
|
|
281
336
|
await client.connect();
|
|
282
337
|
await client.discover({});
|
|
283
338
|
expect(mockedToolRegistry.registerTool).toHaveBeenCalledOnce();
|
|
@@ -298,6 +353,126 @@ describe('mcp-client', () => {
|
|
|
298
353
|
},
|
|
299
354
|
});
|
|
300
355
|
});
|
|
356
|
+
it('should discover resources when a server only exposes resources', async () => {
|
|
357
|
+
const mockedClient = {
|
|
358
|
+
connect: vi.fn(),
|
|
359
|
+
discover: vi.fn(),
|
|
360
|
+
disconnect: vi.fn(),
|
|
361
|
+
getStatus: vi.fn(),
|
|
362
|
+
registerCapabilities: vi.fn(),
|
|
363
|
+
setRequestHandler: vi.fn(),
|
|
364
|
+
setNotificationHandler: vi.fn(),
|
|
365
|
+
getServerCapabilities: vi.fn().mockReturnValue({ resources: {} }),
|
|
366
|
+
request: vi.fn().mockImplementation(({ method }) => {
|
|
367
|
+
if (method === 'resources/list') {
|
|
368
|
+
return Promise.resolve({
|
|
369
|
+
resources: [
|
|
370
|
+
{
|
|
371
|
+
uri: 'file:///tmp/resource.txt',
|
|
372
|
+
name: 'resource',
|
|
373
|
+
description: 'Test Resource',
|
|
374
|
+
mimeType: 'text/plain',
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
return Promise.resolve({ prompts: [] });
|
|
380
|
+
}),
|
|
381
|
+
};
|
|
382
|
+
vi.mocked(ClientLib.Client).mockReturnValue(mockedClient);
|
|
383
|
+
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({});
|
|
384
|
+
const mockedToolRegistry = {
|
|
385
|
+
registerTool: vi.fn(),
|
|
386
|
+
sortTools: vi.fn(),
|
|
387
|
+
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
388
|
+
};
|
|
389
|
+
const promptRegistry = {
|
|
390
|
+
registerPrompt: vi.fn(),
|
|
391
|
+
removePromptsByServer: vi.fn(),
|
|
392
|
+
};
|
|
393
|
+
const resourceRegistry = {
|
|
394
|
+
setResourcesForServer: vi.fn(),
|
|
395
|
+
removeResourcesByServer: vi.fn(),
|
|
396
|
+
};
|
|
397
|
+
const client = new McpClient('test-server', {
|
|
398
|
+
command: 'test-command',
|
|
399
|
+
}, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
|
|
400
|
+
await client.connect();
|
|
401
|
+
await client.discover({});
|
|
402
|
+
expect(resourceRegistry.setResourcesForServer).toHaveBeenCalledWith('test-server', [
|
|
403
|
+
expect.objectContaining({
|
|
404
|
+
uri: 'file:///tmp/resource.txt',
|
|
405
|
+
name: 'resource',
|
|
406
|
+
}),
|
|
407
|
+
]);
|
|
408
|
+
});
|
|
409
|
+
it('refreshes registry when resource list change notification is received', async () => {
|
|
410
|
+
let listCallCount = 0;
|
|
411
|
+
let resourceListHandler;
|
|
412
|
+
const mockedClient = {
|
|
413
|
+
connect: vi.fn(),
|
|
414
|
+
discover: vi.fn(),
|
|
415
|
+
disconnect: vi.fn(),
|
|
416
|
+
getStatus: vi.fn(),
|
|
417
|
+
registerCapabilities: vi.fn(),
|
|
418
|
+
setRequestHandler: vi.fn(),
|
|
419
|
+
setNotificationHandler: vi.fn((_, handler) => {
|
|
420
|
+
resourceListHandler = handler;
|
|
421
|
+
}),
|
|
422
|
+
getServerCapabilities: vi
|
|
423
|
+
.fn()
|
|
424
|
+
.mockReturnValue({ resources: { listChanged: true } }),
|
|
425
|
+
request: vi.fn().mockImplementation(({ method }) => {
|
|
426
|
+
if (method === 'resources/list') {
|
|
427
|
+
listCallCount += 1;
|
|
428
|
+
if (listCallCount === 1) {
|
|
429
|
+
return Promise.resolve({
|
|
430
|
+
resources: [
|
|
431
|
+
{
|
|
432
|
+
uri: 'file:///tmp/one.txt',
|
|
433
|
+
},
|
|
434
|
+
],
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return Promise.resolve({
|
|
438
|
+
resources: [
|
|
439
|
+
{
|
|
440
|
+
uri: 'file:///tmp/two.txt',
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return Promise.resolve({ prompts: [] });
|
|
446
|
+
}),
|
|
447
|
+
};
|
|
448
|
+
vi.mocked(ClientLib.Client).mockReturnValue(mockedClient);
|
|
449
|
+
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({});
|
|
450
|
+
const mockedToolRegistry = {
|
|
451
|
+
registerTool: vi.fn(),
|
|
452
|
+
sortTools: vi.fn(),
|
|
453
|
+
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
454
|
+
};
|
|
455
|
+
const promptRegistry = {
|
|
456
|
+
registerPrompt: vi.fn(),
|
|
457
|
+
removePromptsByServer: vi.fn(),
|
|
458
|
+
};
|
|
459
|
+
const resourceRegistry = {
|
|
460
|
+
setResourcesForServer: vi.fn(),
|
|
461
|
+
removeResourcesByServer: vi.fn(),
|
|
462
|
+
};
|
|
463
|
+
const client = new McpClient('test-server', {
|
|
464
|
+
command: 'test-command',
|
|
465
|
+
}, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
|
|
466
|
+
await client.connect();
|
|
467
|
+
await client.discover({});
|
|
468
|
+
expect(mockedClient.setNotificationHandler).toHaveBeenCalledOnce();
|
|
469
|
+
expect(resourceListHandler).toBeDefined();
|
|
470
|
+
await resourceListHandler?.({
|
|
471
|
+
method: 'notifications/resources/list_changed',
|
|
472
|
+
});
|
|
473
|
+
expect(resourceRegistry.setResourcesForServer).toHaveBeenLastCalledWith('test-server', [expect.objectContaining({ uri: 'file:///tmp/two.txt' })]);
|
|
474
|
+
expect(coreEvents.emitFeedback).toHaveBeenCalledWith('info', 'Resources updated for server: test-server');
|
|
475
|
+
});
|
|
301
476
|
it('should remove tools and prompts on disconnect', async () => {
|
|
302
477
|
const mockedClient = {
|
|
303
478
|
connect: vi.fn(),
|
|
@@ -305,6 +480,7 @@ describe('mcp-client', () => {
|
|
|
305
480
|
getStatus: vi.fn(),
|
|
306
481
|
registerCapabilities: vi.fn(),
|
|
307
482
|
setRequestHandler: vi.fn(),
|
|
483
|
+
setNotificationHandler: vi.fn(),
|
|
308
484
|
getServerCapabilities: vi
|
|
309
485
|
.fn()
|
|
310
486
|
.mockReturnValue({ tools: {}, prompts: {} }),
|
|
@@ -336,9 +512,13 @@ describe('mcp-client', () => {
|
|
|
336
512
|
unregisterPrompt: vi.fn(),
|
|
337
513
|
removePromptsByServer: vi.fn(),
|
|
338
514
|
};
|
|
515
|
+
const resourceRegistry = {
|
|
516
|
+
setResourcesForServer: vi.fn(),
|
|
517
|
+
removeResourcesByServer: vi.fn(),
|
|
518
|
+
};
|
|
339
519
|
const client = new McpClient('test-server', {
|
|
340
520
|
command: 'test-command',
|
|
341
|
-
}, mockedToolRegistry, mockedPromptRegistry, workspaceContext, false);
|
|
521
|
+
}, mockedToolRegistry, mockedPromptRegistry, resourceRegistry, workspaceContext, {}, false);
|
|
342
522
|
await client.connect();
|
|
343
523
|
await client.discover({});
|
|
344
524
|
expect(mockedToolRegistry.registerTool).toHaveBeenCalledOnce();
|
|
@@ -347,6 +527,251 @@ describe('mcp-client', () => {
|
|
|
347
527
|
expect(mockedClient.close).toHaveBeenCalledOnce();
|
|
348
528
|
expect(mockedToolRegistry.removeMcpToolsByServer).toHaveBeenCalledOnce();
|
|
349
529
|
expect(mockedPromptRegistry.removePromptsByServer).toHaveBeenCalledOnce();
|
|
530
|
+
expect(resourceRegistry.removeResourcesByServer).toHaveBeenCalledOnce();
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
describe('Dynamic Tool Updates', () => {
|
|
534
|
+
it('should set up notification handler if server supports tool list changes', async () => {
|
|
535
|
+
const mockedClient = {
|
|
536
|
+
connect: vi.fn(),
|
|
537
|
+
getStatus: vi.fn(),
|
|
538
|
+
registerCapabilities: vi.fn(),
|
|
539
|
+
setRequestHandler: vi.fn(),
|
|
540
|
+
// Capability enables the listener
|
|
541
|
+
getServerCapabilities: vi
|
|
542
|
+
.fn()
|
|
543
|
+
.mockReturnValue({ tools: { listChanged: true } }),
|
|
544
|
+
setNotificationHandler: vi.fn(),
|
|
545
|
+
listTools: vi.fn().mockResolvedValue({ tools: [] }),
|
|
546
|
+
listPrompts: vi.fn().mockResolvedValue({ prompts: [] }),
|
|
547
|
+
request: vi.fn().mockResolvedValue({}),
|
|
548
|
+
};
|
|
549
|
+
vi.mocked(ClientLib.Client).mockReturnValue(mockedClient);
|
|
550
|
+
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({});
|
|
551
|
+
const client = new McpClient('test-server', { command: 'test-command' }, {}, {}, {}, workspaceContext, {}, false);
|
|
552
|
+
await client.connect();
|
|
553
|
+
expect(mockedClient.setNotificationHandler).toHaveBeenCalledWith(ToolListChangedNotificationSchema, expect.any(Function));
|
|
554
|
+
});
|
|
555
|
+
it('should NOT set up notification handler if server lacks capability', async () => {
|
|
556
|
+
const mockedClient = {
|
|
557
|
+
connect: vi.fn(),
|
|
558
|
+
getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }), // No listChanged
|
|
559
|
+
setNotificationHandler: vi.fn(),
|
|
560
|
+
request: vi.fn().mockResolvedValue({}),
|
|
561
|
+
registerCapabilities: vi.fn().mockResolvedValue({}),
|
|
562
|
+
setRequestHandler: vi.fn().mockResolvedValue({}),
|
|
563
|
+
};
|
|
564
|
+
vi.mocked(ClientLib.Client).mockReturnValue(mockedClient);
|
|
565
|
+
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({});
|
|
566
|
+
const client = new McpClient('test-server', { command: 'test-command' }, {}, {}, {}, workspaceContext, {}, false);
|
|
567
|
+
await client.connect();
|
|
568
|
+
expect(mockedClient.setNotificationHandler).not.toHaveBeenCalled();
|
|
569
|
+
});
|
|
570
|
+
it('should refresh tools and notify manager when notification is received', async () => {
|
|
571
|
+
// Setup mocks
|
|
572
|
+
const mockedClient = {
|
|
573
|
+
connect: vi.fn(),
|
|
574
|
+
getServerCapabilities: vi
|
|
575
|
+
.fn()
|
|
576
|
+
.mockReturnValue({ tools: { listChanged: true } }),
|
|
577
|
+
setNotificationHandler: vi.fn(),
|
|
578
|
+
listTools: vi.fn().mockResolvedValue({
|
|
579
|
+
tools: [
|
|
580
|
+
{
|
|
581
|
+
name: 'newTool',
|
|
582
|
+
inputSchema: { type: 'object', properties: {} },
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
}),
|
|
586
|
+
listPrompts: vi.fn().mockResolvedValue({ prompts: [] }),
|
|
587
|
+
request: vi.fn().mockResolvedValue({}),
|
|
588
|
+
registerCapabilities: vi.fn().mockResolvedValue({}),
|
|
589
|
+
setRequestHandler: vi.fn().mockResolvedValue({}),
|
|
590
|
+
};
|
|
591
|
+
vi.mocked(ClientLib.Client).mockReturnValue(mockedClient);
|
|
592
|
+
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({});
|
|
593
|
+
const mockedToolRegistry = {
|
|
594
|
+
removeMcpToolsByServer: vi.fn(),
|
|
595
|
+
registerTool: vi.fn(),
|
|
596
|
+
sortTools: vi.fn(),
|
|
597
|
+
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
598
|
+
};
|
|
599
|
+
const onToolsUpdatedSpy = vi.fn().mockResolvedValue(undefined);
|
|
600
|
+
// Initialize client with onToolsUpdated callback
|
|
601
|
+
const client = new McpClient('test-server', { command: 'test-command' }, mockedToolRegistry, {}, {}, workspaceContext, {}, false, onToolsUpdatedSpy);
|
|
602
|
+
// 1. Connect (sets up listener)
|
|
603
|
+
await client.connect();
|
|
604
|
+
// 2. Extract the callback passed to setNotificationHandler
|
|
605
|
+
const notificationCallback = mockedClient.setNotificationHandler.mock.calls[0][1];
|
|
606
|
+
// 3. Trigger the notification manually
|
|
607
|
+
await notificationCallback();
|
|
608
|
+
// 4. Assertions
|
|
609
|
+
// It should clear old tools
|
|
610
|
+
expect(mockedToolRegistry.removeMcpToolsByServer).toHaveBeenCalledWith('test-server');
|
|
611
|
+
// It should fetch new tools (listTools called inside discoverTools)
|
|
612
|
+
expect(mockedClient.listTools).toHaveBeenCalled();
|
|
613
|
+
// It should register the new tool
|
|
614
|
+
expect(mockedToolRegistry.registerTool).toHaveBeenCalled();
|
|
615
|
+
// It should notify the manager
|
|
616
|
+
expect(onToolsUpdatedSpy).toHaveBeenCalled();
|
|
617
|
+
// It should emit feedback event
|
|
618
|
+
expect(coreEvents.emitFeedback).toHaveBeenCalledWith('info', 'Tools updated for server: test-server');
|
|
619
|
+
});
|
|
620
|
+
it('should handle errors during tool refresh gracefully', async () => {
|
|
621
|
+
const mockedClient = {
|
|
622
|
+
connect: vi.fn(),
|
|
623
|
+
getServerCapabilities: vi
|
|
624
|
+
.fn()
|
|
625
|
+
.mockReturnValue({ tools: { listChanged: true } }),
|
|
626
|
+
setNotificationHandler: vi.fn(),
|
|
627
|
+
// Simulate error during discovery
|
|
628
|
+
listTools: vi.fn().mockRejectedValue(new Error('Network blip')),
|
|
629
|
+
request: vi.fn().mockResolvedValue({}),
|
|
630
|
+
registerCapabilities: vi.fn().mockResolvedValue({}),
|
|
631
|
+
setRequestHandler: vi.fn().mockResolvedValue({}),
|
|
632
|
+
};
|
|
633
|
+
vi.mocked(ClientLib.Client).mockReturnValue(mockedClient);
|
|
634
|
+
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({});
|
|
635
|
+
const mockedToolRegistry = {
|
|
636
|
+
removeMcpToolsByServer: vi.fn(),
|
|
637
|
+
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
638
|
+
};
|
|
639
|
+
const client = new McpClient('test-server', { command: 'test-command' }, mockedToolRegistry, {}, {}, workspaceContext, {}, false);
|
|
640
|
+
await client.connect();
|
|
641
|
+
const notificationCallback = mockedClient.setNotificationHandler.mock.calls[0][1];
|
|
642
|
+
// Trigger notification - should fail internally but catch the error
|
|
643
|
+
await notificationCallback();
|
|
644
|
+
// Should try to remove tools
|
|
645
|
+
expect(mockedToolRegistry.removeMcpToolsByServer).toHaveBeenCalled();
|
|
646
|
+
// Should NOT emit success feedback
|
|
647
|
+
expect(coreEvents.emitFeedback).not.toHaveBeenCalledWith('info', expect.stringContaining('Tools updated'));
|
|
648
|
+
});
|
|
649
|
+
it('should handle concurrent updates from multiple servers', async () => {
|
|
650
|
+
const createMockSdkClient = (toolName) => ({
|
|
651
|
+
connect: vi.fn(),
|
|
652
|
+
getServerCapabilities: vi
|
|
653
|
+
.fn()
|
|
654
|
+
.mockReturnValue({ tools: { listChanged: true } }),
|
|
655
|
+
setNotificationHandler: vi.fn(),
|
|
656
|
+
listTools: vi.fn().mockResolvedValue({
|
|
657
|
+
tools: [
|
|
658
|
+
{
|
|
659
|
+
name: toolName,
|
|
660
|
+
inputSchema: { type: 'object', properties: {} },
|
|
661
|
+
},
|
|
662
|
+
],
|
|
663
|
+
}),
|
|
664
|
+
listPrompts: vi.fn().mockResolvedValue({ prompts: [] }),
|
|
665
|
+
request: vi.fn().mockResolvedValue({}),
|
|
666
|
+
registerCapabilities: vi.fn().mockResolvedValue({}),
|
|
667
|
+
setRequestHandler: vi.fn().mockResolvedValue({}),
|
|
668
|
+
});
|
|
669
|
+
const mockClientA = createMockSdkClient('tool-from-A');
|
|
670
|
+
const mockClientB = createMockSdkClient('tool-from-B');
|
|
671
|
+
vi.mocked(ClientLib.Client)
|
|
672
|
+
.mockReturnValueOnce(mockClientA)
|
|
673
|
+
.mockReturnValueOnce(mockClientB);
|
|
674
|
+
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({});
|
|
675
|
+
const mockedToolRegistry = {
|
|
676
|
+
removeMcpToolsByServer: vi.fn(),
|
|
677
|
+
registerTool: vi.fn(),
|
|
678
|
+
sortTools: vi.fn(),
|
|
679
|
+
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
680
|
+
};
|
|
681
|
+
const onToolsUpdatedSpy = vi.fn().mockResolvedValue(undefined);
|
|
682
|
+
const clientA = new McpClient('server-A', { command: 'cmd-a' }, mockedToolRegistry, {}, {}, workspaceContext, {}, false, onToolsUpdatedSpy);
|
|
683
|
+
const clientB = new McpClient('server-B', { command: 'cmd-b' }, mockedToolRegistry, {}, {}, workspaceContext, {}, false, onToolsUpdatedSpy);
|
|
684
|
+
await clientA.connect();
|
|
685
|
+
await clientB.connect();
|
|
686
|
+
const handlerA = mockClientA.setNotificationHandler.mock.calls[0][1];
|
|
687
|
+
const handlerB = mockClientB.setNotificationHandler.mock.calls[0][1];
|
|
688
|
+
// Trigger burst updates simultaneously
|
|
689
|
+
await Promise.all([handlerA(), handlerB()]);
|
|
690
|
+
expect(mockedToolRegistry.removeMcpToolsByServer).toHaveBeenCalledWith('server-A');
|
|
691
|
+
expect(mockedToolRegistry.removeMcpToolsByServer).toHaveBeenCalledWith('server-B');
|
|
692
|
+
// Verify fetching happened on both clients
|
|
693
|
+
expect(mockClientA.listTools).toHaveBeenCalled();
|
|
694
|
+
expect(mockClientB.listTools).toHaveBeenCalled();
|
|
695
|
+
// Verify tools from both servers were registered (2 total calls)
|
|
696
|
+
expect(mockedToolRegistry.registerTool).toHaveBeenCalledTimes(2);
|
|
697
|
+
// Verify the update callback was triggered for both
|
|
698
|
+
expect(onToolsUpdatedSpy).toHaveBeenCalledTimes(2);
|
|
699
|
+
});
|
|
700
|
+
it('should abort discovery and log error if timeout is exceeded during refresh', async () => {
|
|
701
|
+
vi.useFakeTimers();
|
|
702
|
+
const mockedClient = {
|
|
703
|
+
connect: vi.fn(),
|
|
704
|
+
getServerCapabilities: vi
|
|
705
|
+
.fn()
|
|
706
|
+
.mockReturnValue({ tools: { listChanged: true } }),
|
|
707
|
+
setNotificationHandler: vi.fn(),
|
|
708
|
+
// Mock listTools to simulate a long running process that respects the abort signal
|
|
709
|
+
listTools: vi.fn().mockImplementation(async (params, options) => new Promise((resolve, reject) => {
|
|
710
|
+
if (options?.signal?.aborted) {
|
|
711
|
+
return reject(new Error('Operation aborted'));
|
|
712
|
+
}
|
|
713
|
+
options?.signal?.addEventListener('abort', () => {
|
|
714
|
+
reject(new Error('Operation aborted'));
|
|
715
|
+
});
|
|
716
|
+
// Intentionally do not resolve immediately to simulate lag
|
|
717
|
+
})),
|
|
718
|
+
listPrompts: vi.fn().mockResolvedValue({ prompts: [] }),
|
|
719
|
+
request: vi.fn().mockResolvedValue({}),
|
|
720
|
+
registerCapabilities: vi.fn().mockResolvedValue({}),
|
|
721
|
+
setRequestHandler: vi.fn().mockResolvedValue({}),
|
|
722
|
+
};
|
|
723
|
+
vi.mocked(ClientLib.Client).mockReturnValue(mockedClient);
|
|
724
|
+
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({});
|
|
725
|
+
const mockedToolRegistry = {
|
|
726
|
+
removeMcpToolsByServer: vi.fn(),
|
|
727
|
+
registerTool: vi.fn(),
|
|
728
|
+
sortTools: vi.fn(),
|
|
729
|
+
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
730
|
+
};
|
|
731
|
+
const client = new McpClient('test-server',
|
|
732
|
+
// Set a short timeout
|
|
733
|
+
{ command: 'test-command', timeout: 100 }, mockedToolRegistry, {}, {}, workspaceContext, {}, false);
|
|
734
|
+
await client.connect();
|
|
735
|
+
const notificationCallback = mockedClient.setNotificationHandler.mock.calls[0][1];
|
|
736
|
+
const refreshPromise = notificationCallback();
|
|
737
|
+
vi.advanceTimersByTime(150);
|
|
738
|
+
await refreshPromise;
|
|
739
|
+
expect(mockedClient.listTools).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({
|
|
740
|
+
signal: expect.any(AbortSignal),
|
|
741
|
+
}));
|
|
742
|
+
expect(mockedToolRegistry.registerTool).not.toHaveBeenCalled();
|
|
743
|
+
vi.useRealTimers();
|
|
744
|
+
});
|
|
745
|
+
it('should pass abort signal to onToolsUpdated callback', async () => {
|
|
746
|
+
const mockedClient = {
|
|
747
|
+
connect: vi.fn(),
|
|
748
|
+
getServerCapabilities: vi
|
|
749
|
+
.fn()
|
|
750
|
+
.mockReturnValue({ tools: { listChanged: true } }),
|
|
751
|
+
setNotificationHandler: vi.fn(),
|
|
752
|
+
listTools: vi.fn().mockResolvedValue({ tools: [] }),
|
|
753
|
+
listPrompts: vi.fn().mockResolvedValue({ prompts: [] }),
|
|
754
|
+
request: vi.fn().mockResolvedValue({}),
|
|
755
|
+
registerCapabilities: vi.fn().mockResolvedValue({}),
|
|
756
|
+
setRequestHandler: vi.fn().mockResolvedValue({}),
|
|
757
|
+
};
|
|
758
|
+
vi.mocked(ClientLib.Client).mockReturnValue(mockedClient);
|
|
759
|
+
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({});
|
|
760
|
+
const mockedToolRegistry = {
|
|
761
|
+
removeMcpToolsByServer: vi.fn(),
|
|
762
|
+
registerTool: vi.fn(),
|
|
763
|
+
sortTools: vi.fn(),
|
|
764
|
+
getMessageBus: vi.fn().mockReturnValue(undefined),
|
|
765
|
+
};
|
|
766
|
+
const onToolsUpdatedSpy = vi.fn().mockResolvedValue(undefined);
|
|
767
|
+
const client = new McpClient('test-server', { command: 'test-command' }, mockedToolRegistry, {}, {}, workspaceContext, {}, false, onToolsUpdatedSpy);
|
|
768
|
+
await client.connect();
|
|
769
|
+
const notificationCallback = mockedClient.setNotificationHandler.mock.calls[0][1];
|
|
770
|
+
await notificationCallback();
|
|
771
|
+
expect(onToolsUpdatedSpy).toHaveBeenCalledWith(expect.any(AbortSignal));
|
|
772
|
+
// Verify the signal passed was not aborted (happy path)
|
|
773
|
+
const signal = onToolsUpdatedSpy.mock.calls[0][0];
|
|
774
|
+
expect(signal.aborted).toBe(false);
|
|
350
775
|
});
|
|
351
776
|
});
|
|
352
777
|
describe('appendMcpServerCommand', () => {
|
|
@@ -375,19 +800,23 @@ describe('mcp-client', () => {
|
|
|
375
800
|
httpUrl: 'http://test-server',
|
|
376
801
|
}, false);
|
|
377
802
|
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
378
|
-
expect(transport).
|
|
803
|
+
expect(transport).toMatchObject({
|
|
804
|
+
_url: new URL('http://test-server'),
|
|
805
|
+
_requestInit: { headers: {} },
|
|
806
|
+
});
|
|
379
807
|
});
|
|
380
808
|
it('with headers', async () => {
|
|
381
|
-
// We need this to be an any type because we dig into its private state.
|
|
382
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
383
809
|
const transport = await createTransport('test-server', {
|
|
384
810
|
httpUrl: 'http://test-server',
|
|
385
811
|
headers: { Authorization: 'derp' },
|
|
386
812
|
}, false);
|
|
387
813
|
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
388
|
-
expect(transport).
|
|
389
|
-
|
|
390
|
-
|
|
814
|
+
expect(transport).toMatchObject({
|
|
815
|
+
_url: new URL('http://test-server'),
|
|
816
|
+
_requestInit: {
|
|
817
|
+
headers: { Authorization: 'derp' },
|
|
818
|
+
},
|
|
819
|
+
});
|
|
391
820
|
});
|
|
392
821
|
});
|
|
393
822
|
describe('should connect via url', () => {
|
|
@@ -395,20 +824,96 @@ describe('mcp-client', () => {
|
|
|
395
824
|
const transport = await createTransport('test-server', {
|
|
396
825
|
url: 'http://test-server',
|
|
397
826
|
}, false);
|
|
398
|
-
expect(transport).toBeInstanceOf(
|
|
399
|
-
expect(transport).
|
|
827
|
+
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
828
|
+
expect(transport).toMatchObject({
|
|
829
|
+
_url: new URL('http://test-server'),
|
|
830
|
+
_requestInit: { headers: {} },
|
|
831
|
+
});
|
|
400
832
|
});
|
|
401
833
|
it('with headers', async () => {
|
|
402
|
-
// We need this to be an any type because we dig into its private state.
|
|
403
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
404
834
|
const transport = await createTransport('test-server', {
|
|
405
835
|
url: 'http://test-server',
|
|
406
836
|
headers: { Authorization: 'derp' },
|
|
407
837
|
}, false);
|
|
838
|
+
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
839
|
+
expect(transport).toMatchObject({
|
|
840
|
+
_url: new URL('http://test-server'),
|
|
841
|
+
_requestInit: {
|
|
842
|
+
headers: { Authorization: 'derp' },
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
it('with type="http" creates StreamableHTTPClientTransport', async () => {
|
|
847
|
+
const transport = await createTransport('test-server', {
|
|
848
|
+
url: 'http://test-server',
|
|
849
|
+
type: 'http',
|
|
850
|
+
}, false);
|
|
851
|
+
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
852
|
+
expect(transport).toMatchObject({
|
|
853
|
+
_url: new URL('http://test-server'),
|
|
854
|
+
_requestInit: { headers: {} },
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
it('with type="sse" creates SSEClientTransport', async () => {
|
|
858
|
+
const transport = await createTransport('test-server', {
|
|
859
|
+
url: 'http://test-server',
|
|
860
|
+
type: 'sse',
|
|
861
|
+
}, false);
|
|
408
862
|
expect(transport).toBeInstanceOf(SSEClientTransport);
|
|
409
|
-
expect(transport).
|
|
410
|
-
|
|
411
|
-
|
|
863
|
+
expect(transport).toMatchObject({
|
|
864
|
+
_url: new URL('http://test-server'),
|
|
865
|
+
_requestInit: { headers: {} },
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
it('without type defaults to StreamableHTTPClientTransport', async () => {
|
|
869
|
+
const transport = await createTransport('test-server', {
|
|
870
|
+
url: 'http://test-server',
|
|
871
|
+
}, false);
|
|
872
|
+
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
873
|
+
expect(transport).toMatchObject({
|
|
874
|
+
_url: new URL('http://test-server'),
|
|
875
|
+
_requestInit: { headers: {} },
|
|
876
|
+
});
|
|
877
|
+
});
|
|
878
|
+
it('with type="http" and headers applies headers correctly', async () => {
|
|
879
|
+
const transport = await createTransport('test-server', {
|
|
880
|
+
url: 'http://test-server',
|
|
881
|
+
type: 'http',
|
|
882
|
+
headers: { Authorization: 'Bearer token' },
|
|
883
|
+
}, false);
|
|
884
|
+
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
885
|
+
expect(transport).toMatchObject({
|
|
886
|
+
_url: new URL('http://test-server'),
|
|
887
|
+
_requestInit: {
|
|
888
|
+
headers: { Authorization: 'Bearer token' },
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
it('with type="sse" and headers applies headers correctly', async () => {
|
|
893
|
+
const transport = await createTransport('test-server', {
|
|
894
|
+
url: 'http://test-server',
|
|
895
|
+
type: 'sse',
|
|
896
|
+
headers: { 'X-API-Key': 'key123' },
|
|
897
|
+
}, false);
|
|
898
|
+
expect(transport).toBeInstanceOf(SSEClientTransport);
|
|
899
|
+
expect(transport).toMatchObject({
|
|
900
|
+
_url: new URL('http://test-server'),
|
|
901
|
+
_requestInit: {
|
|
902
|
+
headers: { 'X-API-Key': 'key123' },
|
|
903
|
+
},
|
|
904
|
+
});
|
|
905
|
+
});
|
|
906
|
+
it('httpUrl takes priority over url when both are present', async () => {
|
|
907
|
+
const transport = await createTransport('test-server', {
|
|
908
|
+
httpUrl: 'http://test-server-http',
|
|
909
|
+
url: 'http://test-server-url',
|
|
910
|
+
}, false);
|
|
911
|
+
// httpUrl should take priority and create HTTP transport
|
|
912
|
+
expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
|
|
913
|
+
expect(transport).toMatchObject({
|
|
914
|
+
_url: new URL('http://test-server-http'),
|
|
915
|
+
_requestInit: { headers: {} },
|
|
916
|
+
});
|
|
412
917
|
});
|
|
413
918
|
});
|
|
414
919
|
it('should connect via command', async () => {
|
|
@@ -498,6 +1003,7 @@ describe('mcp-client', () => {
|
|
|
498
1003
|
it('should use GoogleCredentialProvider with SSE transport', async () => {
|
|
499
1004
|
const transport = await createTransport('test-server', {
|
|
500
1005
|
url: 'http://test.googleapis.com',
|
|
1006
|
+
type: 'sse',
|
|
501
1007
|
authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
|
|
502
1008
|
oauth: {
|
|
503
1009
|
scopes: ['scope1'],
|
|
@@ -620,7 +1126,7 @@ describe('connectToMcpServer with OAuth', () => {
|
|
|
620
1126
|
const authUrl = 'http://auth.example.com/auth';
|
|
621
1127
|
const tokenUrl = 'http://auth.example.com/token';
|
|
622
1128
|
const wwwAuthHeader = `Bearer realm="test", resource_metadata="http://test-server.com/.well-known/oauth-protected-resource"`;
|
|
623
|
-
vi.mocked(mockedClient.connect).mockRejectedValueOnce(new
|
|
1129
|
+
vi.mocked(mockedClient.connect).mockRejectedValueOnce(new StreamableHTTPError(401, `Unauthorized\nwww-authenticate: ${wwwAuthHeader}`));
|
|
624
1130
|
vi.mocked(OAuthUtils.discoverOAuthConfig).mockResolvedValue({
|
|
625
1131
|
authorizationUrl: authUrl,
|
|
626
1132
|
tokenUrl,
|
|
@@ -633,7 +1139,7 @@ describe('connectToMcpServer with OAuth', () => {
|
|
|
633
1139
|
capturedTransport = transport;
|
|
634
1140
|
return Promise.resolve();
|
|
635
1141
|
});
|
|
636
|
-
const client = await connectToMcpServer('test-server', { httpUrl: serverUrl }, false, workspaceContext);
|
|
1142
|
+
const client = await connectToMcpServer('test-server', { httpUrl: serverUrl, oauth: { enabled: true } }, false, workspaceContext);
|
|
637
1143
|
expect(client).toBe(mockedClient);
|
|
638
1144
|
expect(mockedClient.connect).toHaveBeenCalledTimes(2);
|
|
639
1145
|
expect(mockAuthProvider.authenticate).toHaveBeenCalledOnce();
|
|
@@ -644,7 +1150,7 @@ describe('connectToMcpServer with OAuth', () => {
|
|
|
644
1150
|
const serverUrl = 'http://test-server.com';
|
|
645
1151
|
const authUrl = 'http://auth.example.com/auth';
|
|
646
1152
|
const tokenUrl = 'http://auth.example.com/token';
|
|
647
|
-
vi.mocked(mockedClient.connect).mockRejectedValueOnce(new
|
|
1153
|
+
vi.mocked(mockedClient.connect).mockRejectedValueOnce(new StreamableHTTPError(401, 'Unauthorized'));
|
|
648
1154
|
vi.mocked(OAuthUtils.discoverOAuthConfig).mockResolvedValue({
|
|
649
1155
|
authorizationUrl: authUrl,
|
|
650
1156
|
tokenUrl,
|
|
@@ -658,7 +1164,7 @@ describe('connectToMcpServer with OAuth', () => {
|
|
|
658
1164
|
capturedTransport = transport;
|
|
659
1165
|
return Promise.resolve();
|
|
660
1166
|
});
|
|
661
|
-
const client = await connectToMcpServer('test-server', { httpUrl: serverUrl }, false, workspaceContext);
|
|
1167
|
+
const client = await connectToMcpServer('test-server', { httpUrl: serverUrl, oauth: { enabled: true } }, false, workspaceContext);
|
|
662
1168
|
expect(client).toBe(mockedClient);
|
|
663
1169
|
expect(mockedClient.connect).toHaveBeenCalledTimes(2);
|
|
664
1170
|
expect(mockAuthProvider.authenticate).toHaveBeenCalledOnce();
|
|
@@ -667,4 +1173,118 @@ describe('connectToMcpServer with OAuth', () => {
|
|
|
667
1173
|
expect(authHeader).toBe('Bearer test-access-token-from-discovery');
|
|
668
1174
|
});
|
|
669
1175
|
});
|
|
1176
|
+
describe('connectToMcpServer - HTTP→SSE fallback', () => {
|
|
1177
|
+
let mockedClient;
|
|
1178
|
+
let workspaceContext;
|
|
1179
|
+
let testWorkspace;
|
|
1180
|
+
beforeEach(() => {
|
|
1181
|
+
mockedClient = {
|
|
1182
|
+
connect: vi.fn(),
|
|
1183
|
+
close: vi.fn(),
|
|
1184
|
+
registerCapabilities: vi.fn(),
|
|
1185
|
+
setRequestHandler: vi.fn(),
|
|
1186
|
+
onclose: vi.fn(),
|
|
1187
|
+
notification: vi.fn(),
|
|
1188
|
+
};
|
|
1189
|
+
vi.mocked(ClientLib.Client).mockImplementation(() => mockedClient);
|
|
1190
|
+
testWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-agent-test-'));
|
|
1191
|
+
workspaceContext = new WorkspaceContext(testWorkspace);
|
|
1192
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
1193
|
+
vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
1194
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
1195
|
+
});
|
|
1196
|
+
afterEach(() => {
|
|
1197
|
+
vi.clearAllMocks();
|
|
1198
|
+
});
|
|
1199
|
+
it('should NOT trigger fallback when type="http" is explicit', async () => {
|
|
1200
|
+
vi.mocked(mockedClient.connect).mockRejectedValueOnce(new Error('Connection failed'));
|
|
1201
|
+
await expect(connectToMcpServer('test-server', { url: 'http://test-server', type: 'http' }, false, workspaceContext)).rejects.toThrow('Connection failed');
|
|
1202
|
+
// Should only try once (no fallback)
|
|
1203
|
+
expect(mockedClient.connect).toHaveBeenCalledTimes(1);
|
|
1204
|
+
});
|
|
1205
|
+
it('should NOT trigger fallback when type="sse" is explicit', async () => {
|
|
1206
|
+
vi.mocked(mockedClient.connect).mockRejectedValueOnce(new Error('Connection failed'));
|
|
1207
|
+
await expect(connectToMcpServer('test-server', { url: 'http://test-server', type: 'sse' }, false, workspaceContext)).rejects.toThrow('Connection failed');
|
|
1208
|
+
// Should only try once (no fallback)
|
|
1209
|
+
expect(mockedClient.connect).toHaveBeenCalledTimes(1);
|
|
1210
|
+
});
|
|
1211
|
+
it('should trigger fallback when url provided without type and HTTP fails', async () => {
|
|
1212
|
+
vi.mocked(mockedClient.connect)
|
|
1213
|
+
.mockRejectedValueOnce(new StreamableHTTPError(500, 'Server error'))
|
|
1214
|
+
.mockResolvedValueOnce(undefined);
|
|
1215
|
+
const client = await connectToMcpServer('test-server', { url: 'http://test-server' }, false, workspaceContext);
|
|
1216
|
+
expect(client).toBe(mockedClient);
|
|
1217
|
+
// First HTTP attempt fails, second SSE attempt succeeds
|
|
1218
|
+
expect(mockedClient.connect).toHaveBeenCalledTimes(2);
|
|
1219
|
+
});
|
|
1220
|
+
it('should throw original HTTP error when both HTTP and SSE fail (non-401)', async () => {
|
|
1221
|
+
const httpError = new StreamableHTTPError(500, 'Server error');
|
|
1222
|
+
const sseError = new Error('SSE connection failed');
|
|
1223
|
+
vi.mocked(mockedClient.connect)
|
|
1224
|
+
.mockRejectedValueOnce(httpError)
|
|
1225
|
+
.mockRejectedValueOnce(sseError);
|
|
1226
|
+
await expect(connectToMcpServer('test-server', { url: 'http://test-server' }, false, workspaceContext)).rejects.toThrow('Server error');
|
|
1227
|
+
expect(mockedClient.connect).toHaveBeenCalledTimes(2);
|
|
1228
|
+
});
|
|
1229
|
+
it('should handle HTTP 404 followed by SSE success', async () => {
|
|
1230
|
+
vi.mocked(mockedClient.connect)
|
|
1231
|
+
.mockRejectedValueOnce(new StreamableHTTPError(404, 'Not Found'))
|
|
1232
|
+
.mockResolvedValueOnce(undefined);
|
|
1233
|
+
const client = await connectToMcpServer('test-server', { url: 'http://test-server' }, false, workspaceContext);
|
|
1234
|
+
expect(client).toBe(mockedClient);
|
|
1235
|
+
expect(mockedClient.connect).toHaveBeenCalledTimes(2);
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
1238
|
+
describe('connectToMcpServer - OAuth with transport fallback', () => {
|
|
1239
|
+
let mockedClient;
|
|
1240
|
+
let workspaceContext;
|
|
1241
|
+
let testWorkspace;
|
|
1242
|
+
let mockAuthProvider;
|
|
1243
|
+
let mockTokenStorage;
|
|
1244
|
+
beforeEach(() => {
|
|
1245
|
+
mockedClient = {
|
|
1246
|
+
connect: vi.fn(),
|
|
1247
|
+
close: vi.fn(),
|
|
1248
|
+
registerCapabilities: vi.fn(),
|
|
1249
|
+
setRequestHandler: vi.fn(),
|
|
1250
|
+
onclose: vi.fn(),
|
|
1251
|
+
notification: vi.fn(),
|
|
1252
|
+
};
|
|
1253
|
+
vi.mocked(ClientLib.Client).mockImplementation(() => mockedClient);
|
|
1254
|
+
testWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-agent-test-'));
|
|
1255
|
+
workspaceContext = new WorkspaceContext(testWorkspace);
|
|
1256
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
1257
|
+
vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
1258
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
1259
|
+
mockTokenStorage = {
|
|
1260
|
+
getCredentials: vi.fn().mockResolvedValue({ clientId: 'test-client' }),
|
|
1261
|
+
};
|
|
1262
|
+
vi.mocked(MCPOAuthTokenStorage).mockReturnValue(mockTokenStorage);
|
|
1263
|
+
mockAuthProvider = {
|
|
1264
|
+
authenticate: vi.fn().mockResolvedValue(undefined),
|
|
1265
|
+
getValidToken: vi.fn().mockResolvedValue('test-access-token'),
|
|
1266
|
+
tokenStorage: mockTokenStorage,
|
|
1267
|
+
};
|
|
1268
|
+
vi.mocked(MCPOAuthProvider).mockReturnValue(mockAuthProvider);
|
|
1269
|
+
vi.mocked(OAuthUtils.discoverOAuthConfig).mockResolvedValue({
|
|
1270
|
+
authorizationUrl: 'http://auth.example.com/auth',
|
|
1271
|
+
tokenUrl: 'http://auth.example.com/token',
|
|
1272
|
+
scopes: ['test-scope'],
|
|
1273
|
+
});
|
|
1274
|
+
});
|
|
1275
|
+
afterEach(() => {
|
|
1276
|
+
vi.clearAllMocks();
|
|
1277
|
+
});
|
|
1278
|
+
it('should handle HTTP 404 → SSE 401 → OAuth → SSE+OAuth succeeds', async () => {
|
|
1279
|
+
// Tests that OAuth flow works when SSE (not HTTP) requires auth
|
|
1280
|
+
vi.mocked(mockedClient.connect)
|
|
1281
|
+
.mockRejectedValueOnce(new StreamableHTTPError(404, 'Not Found'))
|
|
1282
|
+
.mockRejectedValueOnce(new StreamableHTTPError(401, 'Unauthorized'))
|
|
1283
|
+
.mockResolvedValueOnce(undefined);
|
|
1284
|
+
const client = await connectToMcpServer('test-server', { url: 'http://test-server', oauth: { enabled: true } }, false, workspaceContext);
|
|
1285
|
+
expect(client).toBe(mockedClient);
|
|
1286
|
+
expect(mockedClient.connect).toHaveBeenCalledTimes(3);
|
|
1287
|
+
expect(mockAuthProvider.authenticate).toHaveBeenCalledOnce();
|
|
1288
|
+
});
|
|
1289
|
+
});
|
|
670
1290
|
//# sourceMappingURL=mcp-client.test.js.map
|