@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.
Files changed (436) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/package.json +1 -1
  5. package/dist/src/agents/delegate-to-agent-tool.d.ts +19 -0
  6. package/dist/src/agents/delegate-to-agent-tool.js +111 -0
  7. package/dist/src/agents/delegate-to-agent-tool.js.map +1 -0
  8. package/dist/src/agents/delegate-to-agent-tool.test.d.ts +6 -0
  9. package/dist/src/agents/delegate-to-agent-tool.test.js +133 -0
  10. package/dist/src/agents/delegate-to-agent-tool.test.js.map +1 -0
  11. package/dist/src/agents/executor.js +1 -1
  12. package/dist/src/agents/executor.js.map +1 -1
  13. package/dist/src/agents/executor.test.js.map +1 -1
  14. package/dist/src/agents/registry.d.ts +15 -0
  15. package/dist/src/agents/registry.js +58 -2
  16. package/dist/src/agents/registry.js.map +1 -1
  17. package/dist/src/agents/registry.test.js +61 -0
  18. package/dist/src/agents/registry.test.js.map +1 -1
  19. package/dist/src/availability/errorClassification.d.ts +7 -0
  20. package/dist/src/availability/errorClassification.js +20 -0
  21. package/dist/src/availability/errorClassification.js.map +1 -0
  22. package/dist/src/availability/modelAvailabilityService.d.ts +1 -0
  23. package/dist/src/availability/modelAvailabilityService.js +3 -0
  24. package/dist/src/availability/modelAvailabilityService.js.map +1 -1
  25. package/dist/src/availability/modelPolicy.d.ts +8 -1
  26. package/dist/src/availability/policyCatalog.d.ts +1 -0
  27. package/dist/src/availability/policyCatalog.js +6 -7
  28. package/dist/src/availability/policyCatalog.js.map +1 -1
  29. package/dist/src/availability/policyCatalog.test.js +2 -2
  30. package/dist/src/availability/policyCatalog.test.js.map +1 -1
  31. package/dist/src/availability/policyHelpers.d.ts +33 -3
  32. package/dist/src/availability/policyHelpers.js +113 -13
  33. package/dist/src/availability/policyHelpers.js.map +1 -1
  34. package/dist/src/availability/policyHelpers.test.js +133 -13
  35. package/dist/src/availability/policyHelpers.test.js.map +1 -1
  36. package/dist/src/availability/testUtils.d.ts +10 -0
  37. package/dist/src/availability/testUtils.js +22 -0
  38. package/dist/src/availability/testUtils.js.map +1 -0
  39. package/dist/src/code_assist/experiments/client_metadata.js +2 -1
  40. package/dist/src/code_assist/experiments/client_metadata.js.map +1 -1
  41. package/dist/src/code_assist/experiments/client_metadata.test.js +7 -10
  42. package/dist/src/code_assist/experiments/client_metadata.test.js.map +1 -1
  43. package/dist/src/code_assist/oauth2.d.ts +2 -0
  44. package/dist/src/code_assist/oauth2.js +38 -12
  45. package/dist/src/code_assist/oauth2.js.map +1 -1
  46. package/dist/src/code_assist/oauth2.test.js +113 -6
  47. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  48. package/dist/src/commands/init.d.ts +7 -0
  49. package/dist/src/commands/init.js +53 -0
  50. package/dist/src/commands/init.js.map +1 -0
  51. package/dist/src/commands/init.test.d.ts +6 -0
  52. package/dist/src/commands/init.test.js +25 -0
  53. package/dist/src/commands/init.test.js.map +1 -0
  54. package/dist/src/commands/restore.d.ts +9 -0
  55. package/dist/src/commands/restore.js +46 -0
  56. package/dist/src/commands/restore.js.map +1 -0
  57. package/dist/src/commands/restore.test.d.ts +6 -0
  58. package/dist/src/commands/restore.test.js +137 -0
  59. package/dist/src/commands/restore.test.js.map +1 -0
  60. package/dist/src/commands/types.d.ts +41 -0
  61. package/dist/src/commands/types.js +7 -0
  62. package/dist/src/commands/types.js.map +1 -0
  63. package/dist/src/config/config.d.ts +29 -3
  64. package/dist/src/config/config.js +146 -29
  65. package/dist/src/config/config.js.map +1 -1
  66. package/dist/src/config/config.test.js +203 -9
  67. package/dist/src/config/config.test.js.map +1 -1
  68. package/dist/src/config/defaultModelConfigs.js +21 -0
  69. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  70. package/dist/src/config/models.d.ts +33 -11
  71. package/dist/src/config/models.js +82 -24
  72. package/dist/src/config/models.js.map +1 -1
  73. package/dist/src/config/models.test.js +70 -76
  74. package/dist/src/config/models.test.js.map +1 -1
  75. package/dist/src/confirmation-bus/message-bus.js +1 -0
  76. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  77. package/dist/src/confirmation-bus/types.d.ts +4 -0
  78. package/dist/src/core/baseLlmClient.d.ts +3 -1
  79. package/dist/src/core/baseLlmClient.js +40 -3
  80. package/dist/src/core/baseLlmClient.js.map +1 -1
  81. package/dist/src/core/baseLlmClient.test.js +184 -7
  82. package/dist/src/core/baseLlmClient.test.js.map +1 -1
  83. package/dist/src/core/client.js +47 -13
  84. package/dist/src/core/client.js.map +1 -1
  85. package/dist/src/core/client.test.js +133 -6
  86. package/dist/src/core/client.test.js.map +1 -1
  87. package/dist/src/core/contentGenerator.js +5 -3
  88. package/dist/src/core/contentGenerator.js.map +1 -1
  89. package/dist/src/core/contentGenerator.test.js +29 -22
  90. package/dist/src/core/contentGenerator.test.js.map +1 -1
  91. package/dist/src/core/coreToolScheduler.d.ts +1 -1
  92. package/dist/src/core/coreToolScheduler.js +76 -34
  93. package/dist/src/core/coreToolScheduler.js.map +1 -1
  94. package/dist/src/core/coreToolScheduler.test.js +135 -37
  95. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  96. package/dist/src/core/geminiChat.js +60 -29
  97. package/dist/src/core/geminiChat.js.map +1 -1
  98. package/dist/src/core/geminiChat.test.js +236 -188
  99. package/dist/src/core/geminiChat.test.js.map +1 -1
  100. package/dist/src/core/geminiChat_network_retry.test.d.ts +6 -0
  101. package/dist/src/core/geminiChat_network_retry.test.js +198 -0
  102. package/dist/src/core/geminiChat_network_retry.test.js.map +1 -0
  103. package/dist/src/core/logger.js.map +1 -1
  104. package/dist/src/core/nonInteractiveToolExecutor.test.js +4 -5
  105. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  106. package/dist/src/core/prompts.js +13 -11
  107. package/dist/src/core/prompts.js.map +1 -1
  108. package/dist/src/core/prompts.test.js +19 -8
  109. package/dist/src/core/prompts.test.js.map +1 -1
  110. package/dist/src/core/sessionHookTriggers.d.ts +28 -0
  111. package/dist/src/core/sessionHookTriggers.js +68 -0
  112. package/dist/src/core/sessionHookTriggers.js.map +1 -0
  113. package/dist/src/core/turn.d.ts +1 -0
  114. package/dist/src/core/turn.js +1 -1
  115. package/dist/src/core/turn.js.map +1 -1
  116. package/dist/src/fallback/handler.js +56 -115
  117. package/dist/src/fallback/handler.js.map +1 -1
  118. package/dist/src/fallback/handler.test.js +124 -278
  119. package/dist/src/fallback/handler.test.js.map +1 -1
  120. package/dist/src/generated/git-commit.d.ts +2 -2
  121. package/dist/src/generated/git-commit.js +2 -2
  122. package/dist/src/hooks/hookEventHandler.js +59 -1
  123. package/dist/src/hooks/hookEventHandler.js.map +1 -1
  124. package/dist/src/hooks/hookEventHandler.test.js +8 -1
  125. package/dist/src/hooks/hookEventHandler.test.js.map +1 -1
  126. package/dist/src/hooks/hookRegistry.d.ts +0 -7
  127. package/dist/src/hooks/hookRegistry.js +8 -21
  128. package/dist/src/hooks/hookRegistry.js.map +1 -1
  129. package/dist/src/hooks/hookRegistry.test.js +2 -7
  130. package/dist/src/hooks/hookRegistry.test.js.map +1 -1
  131. package/dist/src/hooks/hookRunner.js +12 -2
  132. package/dist/src/hooks/hookRunner.js.map +1 -1
  133. package/dist/src/hooks/hookRunner.test.js +1 -1
  134. package/dist/src/hooks/hookRunner.test.js.map +1 -1
  135. package/dist/src/hooks/hookSystem.d.ts +0 -8
  136. package/dist/src/hooks/hookSystem.js +0 -18
  137. package/dist/src/hooks/hookSystem.js.map +1 -1
  138. package/dist/src/hooks/hookSystem.test.js +123 -18
  139. package/dist/src/hooks/hookSystem.test.js.map +1 -1
  140. package/dist/src/hooks/index.d.ts +3 -1
  141. package/dist/src/hooks/index.js +3 -0
  142. package/dist/src/hooks/index.js.map +1 -1
  143. package/dist/src/hooks/types.d.ts +1 -2
  144. package/dist/src/hooks/types.js +0 -1
  145. package/dist/src/hooks/types.js.map +1 -1
  146. package/dist/src/ide/detect-ide.test.js +32 -1
  147. package/dist/src/ide/detect-ide.test.js.map +1 -1
  148. package/dist/src/ide/ide-client.js +2 -0
  149. package/dist/src/ide/ide-client.js.map +1 -1
  150. package/dist/src/ide/ide-installer.test.js +1 -1
  151. package/dist/src/ide/ide-installer.test.js.map +1 -1
  152. package/dist/src/index.d.ts +8 -0
  153. package/dist/src/index.js +8 -0
  154. package/dist/src/index.js.map +1 -1
  155. package/dist/src/mcp/oauth-provider.js.map +1 -1
  156. package/dist/src/output/json-formatter.d.ts +2 -2
  157. package/dist/src/output/json-formatter.js +6 -3
  158. package/dist/src/output/json-formatter.js.map +1 -1
  159. package/dist/src/output/json-formatter.test.js +37 -9
  160. package/dist/src/output/json-formatter.test.js.map +1 -1
  161. package/dist/src/output/stream-json-formatter.js +6 -0
  162. package/dist/src/output/stream-json-formatter.js.map +1 -1
  163. package/dist/src/output/stream-json-formatter.test.js +98 -100
  164. package/dist/src/output/stream-json-formatter.test.js.map +1 -1
  165. package/dist/src/output/types.d.ts +3 -0
  166. package/dist/src/output/types.js.map +1 -1
  167. package/dist/src/policy/config.js +97 -11
  168. package/dist/src/policy/config.js.map +1 -1
  169. package/dist/src/policy/persistence.test.d.ts +6 -0
  170. package/dist/src/policy/persistence.test.js +154 -0
  171. package/dist/src/policy/persistence.test.js.map +1 -0
  172. package/dist/src/policy/policies/agent.toml +31 -0
  173. package/dist/src/policy/policy-engine.d.ts +10 -1
  174. package/dist/src/policy/policy-engine.js +79 -5
  175. package/dist/src/policy/policy-engine.js.map +1 -1
  176. package/dist/src/policy/policy-engine.test.js +26 -2
  177. package/dist/src/policy/policy-engine.test.js.map +1 -1
  178. package/dist/src/policy/policy-updater.test.d.ts +6 -0
  179. package/dist/src/policy/policy-updater.test.js +116 -0
  180. package/dist/src/policy/policy-updater.test.js.map +1 -0
  181. package/dist/src/policy/shell-safety.test.d.ts +6 -0
  182. package/dist/src/policy/shell-safety.test.js +75 -0
  183. package/dist/src/policy/shell-safety.test.js.map +1 -0
  184. package/dist/src/policy/toml-loader.d.ts +11 -5
  185. package/dist/src/policy/toml-loader.js +38 -23
  186. package/dist/src/policy/toml-loader.js.map +1 -1
  187. package/dist/src/policy/toml-loader.test.js +28 -7
  188. package/dist/src/policy/toml-loader.test.js.map +1 -1
  189. package/dist/src/policy/types.d.ts +15 -0
  190. package/dist/src/resources/resource-registry.d.ts +30 -0
  191. package/dist/src/resources/resource-registry.js +57 -0
  192. package/dist/src/resources/resource-registry.js.map +1 -0
  193. package/dist/src/resources/resource-registry.test.d.ts +6 -0
  194. package/dist/src/resources/resource-registry.test.js +54 -0
  195. package/dist/src/resources/resource-registry.test.js.map +1 -0
  196. package/dist/src/routing/modelRouterService.js +0 -15
  197. package/dist/src/routing/modelRouterService.js.map +1 -1
  198. package/dist/src/routing/modelRouterService.test.js +0 -62
  199. package/dist/src/routing/modelRouterService.test.js.map +1 -1
  200. package/dist/src/routing/strategies/classifierStrategy.js +10 -21
  201. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  202. package/dist/src/routing/strategies/classifierStrategy.test.js +2 -1
  203. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  204. package/dist/src/routing/strategies/fallbackStrategy.js +23 -12
  205. package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
  206. package/dist/src/routing/strategies/fallbackStrategy.test.js +69 -39
  207. package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
  208. package/dist/src/routing/strategies/overrideStrategy.js +4 -3
  209. package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
  210. package/dist/src/safety/checker-runner.js +17 -6
  211. package/dist/src/safety/checker-runner.js.map +1 -1
  212. package/dist/src/services/chatCompressionService.js +15 -1
  213. package/dist/src/services/chatCompressionService.js.map +1 -1
  214. package/dist/src/services/chatCompressionService.test.js +2 -0
  215. package/dist/src/services/chatCompressionService.test.js.map +1 -1
  216. package/dist/src/services/chatRecordingService.d.ts +14 -0
  217. package/dist/src/services/chatRecordingService.js +37 -0
  218. package/dist/src/services/chatRecordingService.js.map +1 -1
  219. package/dist/src/services/fileSystemService.d.ts +0 -9
  220. package/dist/src/services/fileSystemService.js +0 -11
  221. package/dist/src/services/fileSystemService.js.map +1 -1
  222. package/dist/src/services/gitService.js +5 -0
  223. package/dist/src/services/gitService.js.map +1 -1
  224. package/dist/src/services/gitService.test.js +28 -0
  225. package/dist/src/services/gitService.test.js.map +1 -1
  226. package/dist/src/services/loopDetectionService.js +2 -2
  227. package/dist/src/services/loopDetectionService.js.map +1 -1
  228. package/dist/src/services/modelConfig.golden.test.js +32 -0
  229. package/dist/src/services/modelConfig.golden.test.js.map +1 -1
  230. package/dist/src/services/modelConfigService.d.ts +3 -0
  231. package/dist/src/services/modelConfigService.js +3 -2
  232. package/dist/src/services/modelConfigService.js.map +1 -1
  233. package/dist/src/services/modelConfigService.test.js +110 -0
  234. package/dist/src/services/modelConfigService.test.js.map +1 -1
  235. package/dist/src/services/modelConfigServiceTestUtils.d.ts +10 -0
  236. package/dist/src/services/modelConfigServiceTestUtils.js +17 -0
  237. package/dist/src/services/modelConfigServiceTestUtils.js.map +1 -0
  238. package/dist/src/services/sessionSummaryService.d.ts +28 -0
  239. package/dist/src/services/sessionSummaryService.js +131 -0
  240. package/dist/src/services/sessionSummaryService.js.map +1 -0
  241. package/dist/src/services/sessionSummaryService.test.d.ts +6 -0
  242. package/dist/src/services/sessionSummaryService.test.js +785 -0
  243. package/dist/src/services/sessionSummaryService.test.js.map +1 -0
  244. package/dist/src/services/sessionSummaryUtils.d.ts +16 -0
  245. package/dist/src/services/sessionSummaryUtils.js +129 -0
  246. package/dist/src/services/sessionSummaryUtils.js.map +1 -0
  247. package/dist/src/services/sessionSummaryUtils.test.d.ts +6 -0
  248. package/dist/src/services/sessionSummaryUtils.test.js +137 -0
  249. package/dist/src/services/sessionSummaryUtils.test.js.map +1 -0
  250. package/dist/src/services/shellExecutionService.js +28 -22
  251. package/dist/src/services/shellExecutionService.js.map +1 -1
  252. package/dist/src/services/shellExecutionService.test.js +46 -4
  253. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  254. package/dist/src/services/test-data/resolved-aliases-retry.golden.json +238 -0
  255. package/dist/src/services/test-data/resolved-aliases.golden.json +16 -0
  256. package/dist/src/telemetry/activity-detector.test.js.map +1 -1
  257. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +1 -0
  258. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +28 -5
  259. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  260. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +67 -1
  261. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  262. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +1 -0
  263. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +3 -1
  264. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  265. package/dist/src/telemetry/config.js +2 -0
  266. package/dist/src/telemetry/config.js.map +1 -1
  267. package/dist/src/telemetry/config.test.js +25 -0
  268. package/dist/src/telemetry/config.test.js.map +1 -1
  269. package/dist/src/telemetry/gcp-exporters.d.ts +4 -3
  270. package/dist/src/telemetry/gcp-exporters.js +8 -4
  271. package/dist/src/telemetry/gcp-exporters.js.map +1 -1
  272. package/dist/src/telemetry/index.d.ts +1 -1
  273. package/dist/src/telemetry/index.js +1 -1
  274. package/dist/src/telemetry/index.js.map +1 -1
  275. package/dist/src/telemetry/loggers.d.ts +2 -1
  276. package/dist/src/telemetry/loggers.js +345 -335
  277. package/dist/src/telemetry/loggers.js.map +1 -1
  278. package/dist/src/telemetry/loggers.test.js +20 -5
  279. package/dist/src/telemetry/loggers.test.js.map +1 -1
  280. package/dist/src/telemetry/metrics.test.js.map +1 -1
  281. package/dist/src/telemetry/sdk.d.ts +9 -2
  282. package/dist/src/telemetry/sdk.js +143 -17
  283. package/dist/src/telemetry/sdk.js.map +1 -1
  284. package/dist/src/telemetry/sdk.test.js +130 -28
  285. package/dist/src/telemetry/sdk.test.js.map +1 -1
  286. package/dist/src/telemetry/startupProfiler.js +26 -3
  287. package/dist/src/telemetry/startupProfiler.js.map +1 -1
  288. package/dist/src/telemetry/startupProfiler.test.js +49 -7
  289. package/dist/src/telemetry/startupProfiler.test.js.map +1 -1
  290. package/dist/src/telemetry/telemetry.test.js +10 -3
  291. package/dist/src/telemetry/telemetry.test.js.map +1 -1
  292. package/dist/src/telemetry/trace.js.map +1 -1
  293. package/dist/src/telemetry/types.d.ts +31 -6
  294. package/dist/src/telemetry/types.js +47 -8
  295. package/dist/src/telemetry/types.js.map +1 -1
  296. package/dist/src/telemetry/uiTelemetry.d.ts +1 -0
  297. package/dist/src/telemetry/uiTelemetry.js +2 -0
  298. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  299. package/dist/src/telemetry/uiTelemetry.test.js +4 -0
  300. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  301. package/dist/src/test-utils/mock-message-bus.js.map +1 -1
  302. package/dist/src/tools/confirmation-policy.test.d.ts +6 -0
  303. package/dist/src/tools/confirmation-policy.test.js +152 -0
  304. package/dist/src/tools/confirmation-policy.test.js.map +1 -0
  305. package/dist/src/tools/edit.js +5 -0
  306. package/dist/src/tools/edit.js.map +1 -1
  307. package/dist/src/tools/edit.test.js.map +1 -1
  308. package/dist/src/tools/grep.js.map +1 -1
  309. package/dist/src/tools/mcp-client-manager.d.ts +2 -1
  310. package/dist/src/tools/mcp-client-manager.js +20 -4
  311. package/dist/src/tools/mcp-client-manager.js.map +1 -1
  312. package/dist/src/tools/mcp-client-manager.test.js +13 -10
  313. package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
  314. package/dist/src/tools/mcp-client.d.ts +39 -3
  315. package/dist/src/tools/mcp-client.js +433 -168
  316. package/dist/src/tools/mcp-client.js.map +1 -1
  317. package/dist/src/tools/mcp-client.test.js +648 -28
  318. package/dist/src/tools/mcp-client.test.js.map +1 -1
  319. package/dist/src/tools/mcp-tool.js +13 -0
  320. package/dist/src/tools/mcp-tool.js.map +1 -1
  321. package/dist/src/tools/mcp-tool.test.js +25 -0
  322. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  323. package/dist/src/tools/memoryTool.js +1 -0
  324. package/dist/src/tools/memoryTool.js.map +1 -1
  325. package/dist/src/tools/modifiable-tool.js.map +1 -1
  326. package/dist/src/tools/modifiable-tool.test.js +22 -13
  327. package/dist/src/tools/modifiable-tool.test.js.map +1 -1
  328. package/dist/src/tools/read-file.js +1 -1
  329. package/dist/src/tools/read-file.js.map +1 -1
  330. package/dist/src/tools/read-file.test.js.map +1 -1
  331. package/dist/src/tools/read-many-files.js +6 -4
  332. package/dist/src/tools/read-many-files.js.map +1 -1
  333. package/dist/src/tools/read-many-files.test.js +1 -1
  334. package/dist/src/tools/read-many-files.test.js.map +1 -1
  335. package/dist/src/tools/shell.d.ts +2 -1
  336. package/dist/src/tools/shell.js +15 -1
  337. package/dist/src/tools/shell.js.map +1 -1
  338. package/dist/src/tools/shell.test.js +2 -1
  339. package/dist/src/tools/shell.test.js.map +1 -1
  340. package/dist/src/tools/smart-edit.js +5 -0
  341. package/dist/src/tools/smart-edit.js.map +1 -1
  342. package/dist/src/tools/smart-edit.test.js.map +1 -1
  343. package/dist/src/tools/tool-names.d.ts +2 -0
  344. package/dist/src/tools/tool-names.js +2 -0
  345. package/dist/src/tools/tool-names.js.map +1 -1
  346. package/dist/src/tools/tools.d.ts +19 -0
  347. package/dist/src/tools/tools.js +29 -8
  348. package/dist/src/tools/tools.js.map +1 -1
  349. package/dist/src/tools/web-fetch.js +17 -4
  350. package/dist/src/tools/web-fetch.js.map +1 -1
  351. package/dist/src/tools/web-fetch.test.js +1 -0
  352. package/dist/src/tools/web-fetch.test.js.map +1 -1
  353. package/dist/src/tools/write-file.js +5 -0
  354. package/dist/src/tools/write-file.js.map +1 -1
  355. package/dist/src/tools/write-file.test.js.map +1 -1
  356. package/dist/src/utils/bfsFileSearch.d.ts +8 -0
  357. package/dist/src/utils/bfsFileSearch.js +63 -23
  358. package/dist/src/utils/bfsFileSearch.js.map +1 -1
  359. package/dist/src/utils/bfsFileSearch.test.js +65 -1
  360. package/dist/src/utils/bfsFileSearch.test.js.map +1 -1
  361. package/dist/src/utils/checkpointUtils.d.ts +82 -0
  362. package/dist/src/utils/checkpointUtils.js +117 -0
  363. package/dist/src/utils/checkpointUtils.js.map +1 -0
  364. package/dist/src/utils/checkpointUtils.test.d.ts +6 -0
  365. package/dist/src/utils/checkpointUtils.test.js +229 -0
  366. package/dist/src/utils/checkpointUtils.test.js.map +1 -0
  367. package/dist/src/utils/debugLogger.d.ts +3 -0
  368. package/dist/src/utils/debugLogger.js +27 -0
  369. package/dist/src/utils/debugLogger.js.map +1 -1
  370. package/dist/src/utils/editCorrector.test.js +4 -0
  371. package/dist/src/utils/editCorrector.test.js.map +1 -1
  372. package/dist/src/utils/editor.d.ts +9 -1
  373. package/dist/src/utils/editor.js +23 -14
  374. package/dist/src/utils/editor.js.map +1 -1
  375. package/dist/src/utils/errors.d.ts +8 -0
  376. package/dist/src/utils/errors.js +39 -2
  377. package/dist/src/utils/errors.js.map +1 -1
  378. package/dist/src/utils/errors.test.d.ts +6 -0
  379. package/dist/src/utils/errors.test.js +155 -0
  380. package/dist/src/utils/errors.test.js.map +1 -0
  381. package/dist/src/utils/extensionLoader.d.ts +2 -2
  382. package/dist/src/utils/extensionLoader.js +5 -6
  383. package/dist/src/utils/extensionLoader.js.map +1 -1
  384. package/dist/src/utils/extensionLoader.test.js +11 -0
  385. package/dist/src/utils/extensionLoader.test.js.map +1 -1
  386. package/dist/src/utils/fetch.d.ts +1 -1
  387. package/dist/src/utils/fetch.js +3 -3
  388. package/dist/src/utils/fetch.js.map +1 -1
  389. package/dist/src/utils/fileUtils.test.js +15 -0
  390. package/dist/src/utils/fileUtils.test.js.map +1 -1
  391. package/dist/src/utils/filesearch/crawlCache.js.map +1 -1
  392. package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
  393. package/dist/src/utils/flashFallback.test.js +1 -1
  394. package/dist/src/utils/flashFallback.test.js.map +1 -1
  395. package/dist/src/utils/googleErrors.js +31 -18
  396. package/dist/src/utils/googleErrors.js.map +1 -1
  397. package/dist/src/utils/googleErrors.test.js +10 -2
  398. package/dist/src/utils/googleErrors.test.js.map +1 -1
  399. package/dist/src/utils/googleQuotaErrors.d.ts +3 -3
  400. package/dist/src/utils/googleQuotaErrors.js +32 -6
  401. package/dist/src/utils/googleQuotaErrors.js.map +1 -1
  402. package/dist/src/utils/googleQuotaErrors.test.js +94 -2
  403. package/dist/src/utils/googleQuotaErrors.test.js.map +1 -1
  404. package/dist/src/utils/nextSpeakerChecker.test.js +4 -0
  405. package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
  406. package/dist/src/utils/pathCorrector.js +12 -2
  407. package/dist/src/utils/pathCorrector.js.map +1 -1
  408. package/dist/src/utils/pathCorrector.test.js +6 -2
  409. package/dist/src/utils/pathCorrector.test.js.map +1 -1
  410. package/dist/src/utils/retry.d.ts +11 -0
  411. package/dist/src/utils/retry.js +54 -13
  412. package/dist/src/utils/retry.js.map +1 -1
  413. package/dist/src/utils/retry.test.js +170 -10
  414. package/dist/src/utils/retry.test.js.map +1 -1
  415. package/dist/src/utils/shell-permissions.d.ts +52 -0
  416. package/dist/src/utils/shell-permissions.js +188 -0
  417. package/dist/src/utils/shell-permissions.js.map +1 -0
  418. package/dist/src/utils/shell-permissions.test.d.ts +6 -0
  419. package/dist/src/utils/shell-permissions.test.js +347 -0
  420. package/dist/src/utils/shell-permissions.test.js.map +1 -0
  421. package/dist/src/utils/shell-utils.d.ts +10 -47
  422. package/dist/src/utils/shell-utils.js +1 -182
  423. package/dist/src/utils/shell-utils.js.map +1 -1
  424. package/dist/src/utils/shell-utils.test.js +1 -288
  425. package/dist/src/utils/shell-utils.test.js.map +1 -1
  426. package/dist/src/utils/terminalSerializer.test.js +17 -0
  427. package/dist/src/utils/terminalSerializer.test.js.map +1 -1
  428. package/dist/src/utils/tool-utils.js.map +1 -1
  429. package/dist/src/utils/version.d.ts +6 -0
  430. package/dist/src/utils/version.js +15 -0
  431. package/dist/src/utils/version.js.map +1 -0
  432. package/dist/src/utils/version.test.d.ts +6 -0
  433. package/dist/src/utils/version.test.js +39 -0
  434. package/dist/src/utils/version.test.js.map +1 -0
  435. package/dist/tsconfig.tsbuildinfo +1 -1
  436. 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, {}, workspaceContext, false);
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, {}, workspaceContext, false);
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, {}, workspaceContext, false);
198
+ }, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
171
199
  await client.connect();
172
- await expect(client.discover({})).rejects.toThrow('No prompts or tools found on the server.');
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, {}, workspaceContext, false);
233
+ }, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
197
234
  await client.connect();
198
- await expect(client.discover({})).rejects.toThrow('No prompts or tools found on the server.');
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, {}, workspaceContext, false);
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, {}, workspaceContext, false);
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).toHaveProperty('_url', new URL('http://test-server/'));
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).toHaveProperty('_url', new URL('http://test-server/'));
389
- const authHeader = transport._requestInit?.headers?.['Authorization'];
390
- expect(authHeader).toBe('derp');
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(SSEClientTransport);
399
- expect(transport).toHaveProperty('_url', new URL('http://test-server/'));
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).toHaveProperty('_url', new URL('http://test-server/'));
410
- const authHeader = transport._requestInit?.headers?.['Authorization'];
411
- expect(authHeader).toBe('derp');
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 Error(`401 Unauthorized\nwww-authenticate: ${wwwAuthHeader}`));
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 Error('401 Unauthorized'));
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