@machina.ai/cell-cli-core 1.19.4-rc3 → 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 (504) 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 +12 -17
  12. package/dist/src/agents/executor.js.map +1 -1
  13. package/dist/src/agents/executor.test.js +8 -9
  14. package/dist/src/agents/executor.test.js.map +1 -1
  15. package/dist/src/agents/registry.d.ts +15 -0
  16. package/dist/src/agents/registry.js +58 -2
  17. package/dist/src/agents/registry.js.map +1 -1
  18. package/dist/src/agents/registry.test.js +61 -0
  19. package/dist/src/agents/registry.test.js.map +1 -1
  20. package/dist/src/availability/errorClassification.d.ts +7 -0
  21. package/dist/src/availability/errorClassification.js +20 -0
  22. package/dist/src/availability/errorClassification.js.map +1 -0
  23. package/dist/src/availability/modelAvailabilityService.d.ts +2 -1
  24. package/dist/src/availability/modelAvailabilityService.js +5 -2
  25. package/dist/src/availability/modelAvailabilityService.js.map +1 -1
  26. package/dist/src/availability/modelAvailabilityService.test.js +3 -3
  27. package/dist/src/availability/modelAvailabilityService.test.js.map +1 -1
  28. package/dist/src/availability/modelPolicy.d.ts +8 -1
  29. package/dist/src/availability/policyCatalog.d.ts +4 -1
  30. package/dist/src/availability/policyCatalog.js +8 -9
  31. package/dist/src/availability/policyCatalog.js.map +1 -1
  32. package/dist/src/availability/policyCatalog.test.js +2 -2
  33. package/dist/src/availability/policyCatalog.test.js.map +1 -1
  34. package/dist/src/availability/policyHelpers.d.ts +51 -0
  35. package/dist/src/availability/policyHelpers.js +145 -0
  36. package/dist/src/availability/policyHelpers.js.map +1 -0
  37. package/dist/src/availability/policyHelpers.test.d.ts +6 -0
  38. package/dist/src/availability/policyHelpers.test.js +172 -0
  39. package/dist/src/availability/policyHelpers.test.js.map +1 -0
  40. package/dist/src/availability/testUtils.d.ts +10 -0
  41. package/dist/src/availability/testUtils.js +22 -0
  42. package/dist/src/availability/testUtils.js.map +1 -0
  43. package/dist/src/code_assist/experiments/client_metadata.js +3 -2
  44. package/dist/src/code_assist/experiments/client_metadata.js.map +1 -1
  45. package/dist/src/code_assist/experiments/client_metadata.test.js +7 -10
  46. package/dist/src/code_assist/experiments/client_metadata.test.js.map +1 -1
  47. package/dist/src/code_assist/experiments/experiments.js +2 -2
  48. package/dist/src/code_assist/experiments/experiments.js.map +1 -1
  49. package/dist/src/code_assist/oauth2.d.ts +2 -0
  50. package/dist/src/code_assist/oauth2.js +41 -15
  51. package/dist/src/code_assist/oauth2.js.map +1 -1
  52. package/dist/src/code_assist/oauth2.test.js +114 -7
  53. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  54. package/dist/src/code_assist/server.d.ts +2 -1
  55. package/dist/src/code_assist/server.js +7 -4
  56. package/dist/src/code_assist/server.js.map +1 -1
  57. package/dist/src/code_assist/server.test.js +24 -0
  58. package/dist/src/code_assist/server.test.js.map +1 -1
  59. package/dist/src/code_assist/types.d.ts +14 -0
  60. package/dist/src/commands/init.d.ts +7 -0
  61. package/dist/src/commands/init.js +53 -0
  62. package/dist/src/commands/init.js.map +1 -0
  63. package/dist/src/commands/init.test.d.ts +6 -0
  64. package/dist/src/commands/init.test.js +25 -0
  65. package/dist/src/commands/init.test.js.map +1 -0
  66. package/dist/src/commands/restore.d.ts +9 -0
  67. package/dist/src/commands/restore.js +46 -0
  68. package/dist/src/commands/restore.js.map +1 -0
  69. package/dist/src/commands/restore.test.d.ts +6 -0
  70. package/dist/src/commands/restore.test.js +137 -0
  71. package/dist/src/commands/restore.test.js.map +1 -0
  72. package/dist/src/commands/types.d.ts +41 -0
  73. package/dist/src/commands/types.js +7 -0
  74. package/dist/src/commands/types.js.map +1 -0
  75. package/dist/src/config/config.d.ts +43 -3
  76. package/dist/src/config/config.js +182 -29
  77. package/dist/src/config/config.js.map +1 -1
  78. package/dist/src/config/config.test.js +217 -9
  79. package/dist/src/config/config.test.js.map +1 -1
  80. package/dist/src/config/defaultModelConfigs.js +21 -0
  81. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  82. package/dist/src/config/models.d.ts +33 -11
  83. package/dist/src/config/models.js +82 -24
  84. package/dist/src/config/models.js.map +1 -1
  85. package/dist/src/config/models.test.js +70 -76
  86. package/dist/src/config/models.test.js.map +1 -1
  87. package/dist/src/confirmation-bus/message-bus.js +1 -0
  88. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  89. package/dist/src/confirmation-bus/types.d.ts +4 -0
  90. package/dist/src/core/AuthenticatedContentGenerator.js +4 -4
  91. package/dist/src/core/AuthenticatedContentGenerator.js.map +1 -1
  92. package/dist/src/core/baseLlmClient.d.ts +3 -1
  93. package/dist/src/core/baseLlmClient.js +40 -3
  94. package/dist/src/core/baseLlmClient.js.map +1 -1
  95. package/dist/src/core/baseLlmClient.test.js +184 -7
  96. package/dist/src/core/baseLlmClient.test.js.map +1 -1
  97. package/dist/src/core/client.js +52 -51
  98. package/dist/src/core/client.js.map +1 -1
  99. package/dist/src/core/client.test.js +178 -6
  100. package/dist/src/core/client.test.js.map +1 -1
  101. package/dist/src/core/contentGenerator.js +5 -3
  102. package/dist/src/core/contentGenerator.js.map +1 -1
  103. package/dist/src/core/contentGenerator.test.js +29 -22
  104. package/dist/src/core/contentGenerator.test.js.map +1 -1
  105. package/dist/src/core/coreToolHookTriggers.d.ts +55 -0
  106. package/dist/src/core/coreToolHookTriggers.js +244 -0
  107. package/dist/src/core/coreToolHookTriggers.js.map +1 -0
  108. package/dist/src/core/coreToolScheduler.d.ts +1 -1
  109. package/dist/src/core/coreToolScheduler.js +87 -36
  110. package/dist/src/core/coreToolScheduler.js.map +1 -1
  111. package/dist/src/core/coreToolScheduler.test.js +201 -38
  112. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  113. package/dist/src/core/geminiChat.js +144 -41
  114. package/dist/src/core/geminiChat.js.map +1 -1
  115. package/dist/src/core/geminiChat.test.js +251 -192
  116. package/dist/src/core/geminiChat.test.js.map +1 -1
  117. package/dist/src/core/geminiChatHookTriggers.d.ts +64 -0
  118. package/dist/src/core/geminiChatHookTriggers.js +136 -0
  119. package/dist/src/core/geminiChatHookTriggers.js.map +1 -0
  120. package/dist/src/core/geminiChat_network_retry.test.d.ts +6 -0
  121. package/dist/src/core/geminiChat_network_retry.test.js +198 -0
  122. package/dist/src/core/geminiChat_network_retry.test.js.map +1 -0
  123. package/dist/src/core/logger.js.map +1 -1
  124. package/dist/src/core/loggingContentGenerator.js +9 -4
  125. package/dist/src/core/loggingContentGenerator.js.map +1 -1
  126. package/dist/src/core/nonInteractiveToolExecutor.test.js +4 -5
  127. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  128. package/dist/src/core/prompts.js +50 -29
  129. package/dist/src/core/prompts.js.map +1 -1
  130. package/dist/src/core/prompts.test.js +19 -8
  131. package/dist/src/core/prompts.test.js.map +1 -1
  132. package/dist/src/core/sessionHookTriggers.d.ts +28 -0
  133. package/dist/src/core/sessionHookTriggers.js +68 -0
  134. package/dist/src/core/sessionHookTriggers.js.map +1 -0
  135. package/dist/src/core/turn.d.ts +1 -0
  136. package/dist/src/core/turn.js +1 -1
  137. package/dist/src/core/turn.js.map +1 -1
  138. package/dist/src/fallback/handler.js +82 -69
  139. package/dist/src/fallback/handler.js.map +1 -1
  140. package/dist/src/fallback/handler.test.js +186 -170
  141. package/dist/src/fallback/handler.test.js.map +1 -1
  142. package/dist/src/fallback/types.d.ts +8 -0
  143. package/dist/src/generated/git-commit.d.ts +2 -2
  144. package/dist/src/generated/git-commit.js +2 -2
  145. package/dist/src/hooks/hookEventHandler.js +70 -12
  146. package/dist/src/hooks/hookEventHandler.js.map +1 -1
  147. package/dist/src/hooks/hookEventHandler.test.js +8 -1
  148. package/dist/src/hooks/hookEventHandler.test.js.map +1 -1
  149. package/dist/src/hooks/hookRegistry.d.ts +0 -7
  150. package/dist/src/hooks/hookRegistry.js +8 -21
  151. package/dist/src/hooks/hookRegistry.js.map +1 -1
  152. package/dist/src/hooks/hookRegistry.test.js +2 -7
  153. package/dist/src/hooks/hookRegistry.test.js.map +1 -1
  154. package/dist/src/hooks/hookRunner.js +19 -3
  155. package/dist/src/hooks/hookRunner.js.map +1 -1
  156. package/dist/src/hooks/hookRunner.test.js +2 -1
  157. package/dist/src/hooks/hookRunner.test.js.map +1 -1
  158. package/dist/src/hooks/hookSystem.d.ts +0 -8
  159. package/dist/src/hooks/hookSystem.js +0 -18
  160. package/dist/src/hooks/hookSystem.js.map +1 -1
  161. package/dist/src/hooks/hookSystem.test.js +124 -18
  162. package/dist/src/hooks/hookSystem.test.js.map +1 -1
  163. package/dist/src/hooks/index.d.ts +3 -1
  164. package/dist/src/hooks/index.js +3 -0
  165. package/dist/src/hooks/index.js.map +1 -1
  166. package/dist/src/hooks/types.d.ts +1 -2
  167. package/dist/src/hooks/types.js +0 -1
  168. package/dist/src/hooks/types.js.map +1 -1
  169. package/dist/src/ide/detect-ide.test.js +32 -1
  170. package/dist/src/ide/detect-ide.test.js.map +1 -1
  171. package/dist/src/ide/ide-client.js +5 -3
  172. package/dist/src/ide/ide-client.js.map +1 -1
  173. package/dist/src/ide/ide-client.test.js +17 -0
  174. package/dist/src/ide/ide-client.test.js.map +1 -1
  175. package/dist/src/ide/ide-installer.test.js +1 -1
  176. package/dist/src/ide/ide-installer.test.js.map +1 -1
  177. package/dist/src/index.d.ts +10 -0
  178. package/dist/src/index.js +10 -0
  179. package/dist/src/index.js.map +1 -1
  180. package/dist/src/mcp/auth-provider.d.ts +16 -0
  181. package/dist/src/mcp/auth-provider.js +7 -0
  182. package/dist/src/mcp/auth-provider.js.map +1 -0
  183. package/dist/src/mcp/google-auth-provider.d.ts +10 -2
  184. package/dist/src/mcp/google-auth-provider.js +28 -0
  185. package/dist/src/mcp/google-auth-provider.js.map +1 -1
  186. package/dist/src/mcp/google-auth-provider.test.js +45 -0
  187. package/dist/src/mcp/google-auth-provider.test.js.map +1 -1
  188. package/dist/src/mcp/mcpLauncher.js +6 -3
  189. package/dist/src/mcp/mcpLauncher.js.map +1 -1
  190. package/dist/src/mcp/oauth-provider.js.map +1 -1
  191. package/dist/src/mcp/sa-impersonation-provider.d.ts +2 -2
  192. package/dist/src/mcp/sa-impersonation-provider.js.map +1 -1
  193. package/dist/src/mcp/token-storage/hybrid-token-storage.js +1 -1
  194. package/dist/src/mcp/token-storage/hybrid-token-storage.js.map +1 -1
  195. package/dist/src/output/json-formatter.d.ts +2 -2
  196. package/dist/src/output/json-formatter.js +6 -3
  197. package/dist/src/output/json-formatter.js.map +1 -1
  198. package/dist/src/output/json-formatter.test.js +37 -9
  199. package/dist/src/output/json-formatter.test.js.map +1 -1
  200. package/dist/src/output/stream-json-formatter.js +6 -0
  201. package/dist/src/output/stream-json-formatter.js.map +1 -1
  202. package/dist/src/output/stream-json-formatter.test.js +98 -100
  203. package/dist/src/output/stream-json-formatter.test.js.map +1 -1
  204. package/dist/src/output/types.d.ts +3 -0
  205. package/dist/src/output/types.js.map +1 -1
  206. package/dist/src/policy/config.js +97 -11
  207. package/dist/src/policy/config.js.map +1 -1
  208. package/dist/src/policy/persistence.test.d.ts +6 -0
  209. package/dist/src/policy/persistence.test.js +154 -0
  210. package/dist/src/policy/persistence.test.js.map +1 -0
  211. package/dist/src/policy/policies/agent.toml +31 -0
  212. package/dist/src/policy/policies/read-only.toml +5 -0
  213. package/dist/src/policy/policy-engine.d.ts +10 -1
  214. package/dist/src/policy/policy-engine.js +79 -5
  215. package/dist/src/policy/policy-engine.js.map +1 -1
  216. package/dist/src/policy/policy-engine.test.js +26 -2
  217. package/dist/src/policy/policy-engine.test.js.map +1 -1
  218. package/dist/src/policy/policy-updater.test.d.ts +6 -0
  219. package/dist/src/policy/policy-updater.test.js +116 -0
  220. package/dist/src/policy/policy-updater.test.js.map +1 -0
  221. package/dist/src/policy/shell-safety.test.d.ts +6 -0
  222. package/dist/src/policy/shell-safety.test.js +75 -0
  223. package/dist/src/policy/shell-safety.test.js.map +1 -0
  224. package/dist/src/policy/toml-loader.d.ts +11 -5
  225. package/dist/src/policy/toml-loader.js +38 -23
  226. package/dist/src/policy/toml-loader.js.map +1 -1
  227. package/dist/src/policy/toml-loader.test.js +28 -7
  228. package/dist/src/policy/toml-loader.test.js.map +1 -1
  229. package/dist/src/policy/types.d.ts +15 -0
  230. package/dist/src/resources/resource-registry.d.ts +30 -0
  231. package/dist/src/resources/resource-registry.js +57 -0
  232. package/dist/src/resources/resource-registry.js.map +1 -0
  233. package/dist/src/resources/resource-registry.test.d.ts +6 -0
  234. package/dist/src/resources/resource-registry.test.js +54 -0
  235. package/dist/src/resources/resource-registry.test.js.map +1 -0
  236. package/dist/src/routing/modelRouterService.js +0 -15
  237. package/dist/src/routing/modelRouterService.js.map +1 -1
  238. package/dist/src/routing/modelRouterService.test.js +0 -62
  239. package/dist/src/routing/modelRouterService.test.js.map +1 -1
  240. package/dist/src/routing/strategies/classifierStrategy.js +10 -21
  241. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  242. package/dist/src/routing/strategies/classifierStrategy.test.js +2 -1
  243. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  244. package/dist/src/routing/strategies/fallbackStrategy.js +23 -12
  245. package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
  246. package/dist/src/routing/strategies/fallbackStrategy.test.js +69 -39
  247. package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
  248. package/dist/src/routing/strategies/overrideStrategy.js +4 -3
  249. package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
  250. package/dist/src/safety/checker-runner.js +17 -6
  251. package/dist/src/safety/checker-runner.js.map +1 -1
  252. package/dist/src/services/chatCompressionService.js +17 -3
  253. package/dist/src/services/chatCompressionService.js.map +1 -1
  254. package/dist/src/services/chatCompressionService.test.js +9 -0
  255. package/dist/src/services/chatCompressionService.test.js.map +1 -1
  256. package/dist/src/services/chatRecordingService.d.ts +14 -0
  257. package/dist/src/services/chatRecordingService.js +37 -0
  258. package/dist/src/services/chatRecordingService.js.map +1 -1
  259. package/dist/src/services/contextManager.d.ts +35 -0
  260. package/dist/src/services/contextManager.js +68 -0
  261. package/dist/src/services/contextManager.js.map +1 -0
  262. package/dist/src/services/contextManager.test.d.ts +6 -0
  263. package/dist/src/services/contextManager.test.js +105 -0
  264. package/dist/src/services/contextManager.test.js.map +1 -0
  265. package/dist/src/services/fileSystemService.d.ts +0 -9
  266. package/dist/src/services/fileSystemService.js +0 -11
  267. package/dist/src/services/fileSystemService.js.map +1 -1
  268. package/dist/src/services/gitService.js +5 -0
  269. package/dist/src/services/gitService.js.map +1 -1
  270. package/dist/src/services/gitService.test.js +28 -0
  271. package/dist/src/services/gitService.test.js.map +1 -1
  272. package/dist/src/services/loopDetectionService.js +3 -3
  273. package/dist/src/services/loopDetectionService.js.map +1 -1
  274. package/dist/src/services/modelConfig.golden.test.js +32 -0
  275. package/dist/src/services/modelConfig.golden.test.js.map +1 -1
  276. package/dist/src/services/modelConfigService.d.ts +3 -0
  277. package/dist/src/services/modelConfigService.js +3 -2
  278. package/dist/src/services/modelConfigService.js.map +1 -1
  279. package/dist/src/services/modelConfigService.test.js +110 -0
  280. package/dist/src/services/modelConfigService.test.js.map +1 -1
  281. package/dist/src/services/modelConfigServiceTestUtils.d.ts +10 -0
  282. package/dist/src/services/modelConfigServiceTestUtils.js +17 -0
  283. package/dist/src/services/modelConfigServiceTestUtils.js.map +1 -0
  284. package/dist/src/services/sessionSummaryService.d.ts +28 -0
  285. package/dist/src/services/sessionSummaryService.js +131 -0
  286. package/dist/src/services/sessionSummaryService.js.map +1 -0
  287. package/dist/src/services/sessionSummaryService.test.d.ts +6 -0
  288. package/dist/src/services/sessionSummaryService.test.js +785 -0
  289. package/dist/src/services/sessionSummaryService.test.js.map +1 -0
  290. package/dist/src/services/sessionSummaryUtils.d.ts +16 -0
  291. package/dist/src/services/sessionSummaryUtils.js +129 -0
  292. package/dist/src/services/sessionSummaryUtils.js.map +1 -0
  293. package/dist/src/services/sessionSummaryUtils.test.d.ts +6 -0
  294. package/dist/src/services/sessionSummaryUtils.test.js +137 -0
  295. package/dist/src/services/sessionSummaryUtils.test.js.map +1 -0
  296. package/dist/src/services/shellExecutionService.js +56 -22
  297. package/dist/src/services/shellExecutionService.js.map +1 -1
  298. package/dist/src/services/shellExecutionService.test.js +137 -5
  299. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  300. package/dist/src/services/test-data/resolved-aliases-retry.golden.json +238 -0
  301. package/dist/src/services/test-data/resolved-aliases.golden.json +16 -0
  302. package/dist/src/telemetry/activity-detector.test.js.map +1 -1
  303. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +1 -0
  304. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +28 -5
  305. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  306. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +67 -1
  307. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  308. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +1 -0
  309. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +3 -1
  310. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  311. package/dist/src/telemetry/config.js +2 -0
  312. package/dist/src/telemetry/config.js.map +1 -1
  313. package/dist/src/telemetry/config.test.js +25 -0
  314. package/dist/src/telemetry/config.test.js.map +1 -1
  315. package/dist/src/telemetry/gcp-exporters.d.ts +4 -3
  316. package/dist/src/telemetry/gcp-exporters.js +8 -4
  317. package/dist/src/telemetry/gcp-exporters.js.map +1 -1
  318. package/dist/src/telemetry/index.d.ts +2 -1
  319. package/dist/src/telemetry/index.js +2 -1
  320. package/dist/src/telemetry/index.js.map +1 -1
  321. package/dist/src/telemetry/loggers.d.ts +2 -1
  322. package/dist/src/telemetry/loggers.js +345 -338
  323. package/dist/src/telemetry/loggers.js.map +1 -1
  324. package/dist/src/telemetry/loggers.test.js +195 -18
  325. package/dist/src/telemetry/loggers.test.js.map +1 -1
  326. package/dist/src/telemetry/metrics.test.js.map +1 -1
  327. package/dist/src/telemetry/sdk.d.ts +9 -2
  328. package/dist/src/telemetry/sdk.js +143 -17
  329. package/dist/src/telemetry/sdk.js.map +1 -1
  330. package/dist/src/telemetry/sdk.test.js +130 -28
  331. package/dist/src/telemetry/sdk.test.js.map +1 -1
  332. package/dist/src/telemetry/startupProfiler.d.ts +51 -0
  333. package/dist/src/telemetry/startupProfiler.js +170 -0
  334. package/dist/src/telemetry/startupProfiler.js.map +1 -0
  335. package/dist/src/telemetry/startupProfiler.test.d.ts +6 -0
  336. package/dist/src/telemetry/startupProfiler.test.js +289 -0
  337. package/dist/src/telemetry/startupProfiler.test.js.map +1 -0
  338. package/dist/src/telemetry/telemetry.test.js +10 -3
  339. package/dist/src/telemetry/telemetry.test.js.map +1 -1
  340. package/dist/src/telemetry/trace.js +2 -2
  341. package/dist/src/telemetry/trace.js.map +1 -1
  342. package/dist/src/telemetry/types.d.ts +37 -10
  343. package/dist/src/telemetry/types.js +82 -17
  344. package/dist/src/telemetry/types.js.map +1 -1
  345. package/dist/src/telemetry/uiTelemetry.d.ts +1 -0
  346. package/dist/src/telemetry/uiTelemetry.js +2 -0
  347. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  348. package/dist/src/telemetry/uiTelemetry.test.js +4 -0
  349. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  350. package/dist/src/test-utils/mock-message-bus.js.map +1 -1
  351. package/dist/src/tools/confirmation-policy.test.d.ts +6 -0
  352. package/dist/src/tools/confirmation-policy.test.js +152 -0
  353. package/dist/src/tools/confirmation-policy.test.js.map +1 -0
  354. package/dist/src/tools/edit.js +5 -0
  355. package/dist/src/tools/edit.js.map +1 -1
  356. package/dist/src/tools/edit.test.js.map +1 -1
  357. package/dist/src/tools/grep.js.map +1 -1
  358. package/dist/src/tools/mcp-client-manager.d.ts +3 -1
  359. package/dist/src/tools/mcp-client-manager.js +30 -4
  360. package/dist/src/tools/mcp-client-manager.js.map +1 -1
  361. package/dist/src/tools/mcp-client-manager.test.js +38 -10
  362. package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
  363. package/dist/src/tools/mcp-client.d.ts +40 -3
  364. package/dist/src/tools/mcp-client.js +437 -174
  365. package/dist/src/tools/mcp-client.js.map +1 -1
  366. package/dist/src/tools/mcp-client.test.js +695 -28
  367. package/dist/src/tools/mcp-client.test.js.map +1 -1
  368. package/dist/src/tools/mcp-tool.js +13 -0
  369. package/dist/src/tools/mcp-tool.js.map +1 -1
  370. package/dist/src/tools/mcp-tool.test.js +25 -0
  371. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  372. package/dist/src/tools/memoryTool.js +1 -0
  373. package/dist/src/tools/memoryTool.js.map +1 -1
  374. package/dist/src/tools/modifiable-tool.js.map +1 -1
  375. package/dist/src/tools/modifiable-tool.test.js +22 -13
  376. package/dist/src/tools/modifiable-tool.test.js.map +1 -1
  377. package/dist/src/tools/read-file.js +1 -1
  378. package/dist/src/tools/read-file.js.map +1 -1
  379. package/dist/src/tools/read-file.test.js.map +1 -1
  380. package/dist/src/tools/read-many-files.js +6 -4
  381. package/dist/src/tools/read-many-files.js.map +1 -1
  382. package/dist/src/tools/read-many-files.test.js +1 -1
  383. package/dist/src/tools/read-many-files.test.js.map +1 -1
  384. package/dist/src/tools/shell.d.ts +2 -1
  385. package/dist/src/tools/shell.js +58 -4
  386. package/dist/src/tools/shell.js.map +1 -1
  387. package/dist/src/tools/shell.test.js +25 -5
  388. package/dist/src/tools/shell.test.js.map +1 -1
  389. package/dist/src/tools/smart-edit.js +6 -1
  390. package/dist/src/tools/smart-edit.js.map +1 -1
  391. package/dist/src/tools/smart-edit.test.js.map +1 -1
  392. package/dist/src/tools/tool-names.d.ts +2 -0
  393. package/dist/src/tools/tool-names.js +2 -0
  394. package/dist/src/tools/tool-names.js.map +1 -1
  395. package/dist/src/tools/tools.d.ts +19 -0
  396. package/dist/src/tools/tools.js +29 -8
  397. package/dist/src/tools/tools.js.map +1 -1
  398. package/dist/src/tools/web-fetch.js +18 -5
  399. package/dist/src/tools/web-fetch.js.map +1 -1
  400. package/dist/src/tools/web-fetch.test.js +1 -0
  401. package/dist/src/tools/web-fetch.test.js.map +1 -1
  402. package/dist/src/tools/write-file.js +5 -0
  403. package/dist/src/tools/write-file.js.map +1 -1
  404. package/dist/src/tools/write-file.test.js.map +1 -1
  405. package/dist/src/utils/bfsFileSearch.d.ts +8 -0
  406. package/dist/src/utils/bfsFileSearch.js +63 -23
  407. package/dist/src/utils/bfsFileSearch.js.map +1 -1
  408. package/dist/src/utils/bfsFileSearch.test.js +65 -1
  409. package/dist/src/utils/bfsFileSearch.test.js.map +1 -1
  410. package/dist/src/utils/checkpointUtils.d.ts +82 -0
  411. package/dist/src/utils/checkpointUtils.js +117 -0
  412. package/dist/src/utils/checkpointUtils.js.map +1 -0
  413. package/dist/src/utils/checkpointUtils.test.d.ts +6 -0
  414. package/dist/src/utils/checkpointUtils.test.js +229 -0
  415. package/dist/src/utils/checkpointUtils.test.js.map +1 -0
  416. package/dist/src/utils/debugLogger.d.ts +3 -0
  417. package/dist/src/utils/debugLogger.js +27 -0
  418. package/dist/src/utils/debugLogger.js.map +1 -1
  419. package/dist/src/utils/editCorrector.test.js +4 -0
  420. package/dist/src/utils/editCorrector.test.js.map +1 -1
  421. package/dist/src/utils/editor.d.ts +9 -1
  422. package/dist/src/utils/editor.js +23 -14
  423. package/dist/src/utils/editor.js.map +1 -1
  424. package/dist/src/utils/errors.d.ts +8 -0
  425. package/dist/src/utils/errors.js +39 -2
  426. package/dist/src/utils/errors.js.map +1 -1
  427. package/dist/src/utils/errors.test.d.ts +6 -0
  428. package/dist/src/utils/errors.test.js +155 -0
  429. package/dist/src/utils/errors.test.js.map +1 -0
  430. package/dist/src/utils/exitCodes.d.ts +12 -0
  431. package/dist/src/utils/exitCodes.js +13 -0
  432. package/dist/src/utils/exitCodes.js.map +1 -0
  433. package/dist/src/utils/extensionLoader.d.ts +2 -2
  434. package/dist/src/utils/extensionLoader.js +5 -6
  435. package/dist/src/utils/extensionLoader.js.map +1 -1
  436. package/dist/src/utils/extensionLoader.test.js +11 -0
  437. package/dist/src/utils/extensionLoader.test.js.map +1 -1
  438. package/dist/src/utils/fetch.d.ts +1 -1
  439. package/dist/src/utils/fetch.js +3 -3
  440. package/dist/src/utils/fetch.js.map +1 -1
  441. package/dist/src/utils/fileUtils.test.js +15 -0
  442. package/dist/src/utils/fileUtils.test.js.map +1 -1
  443. package/dist/src/utils/filesearch/crawlCache.js.map +1 -1
  444. package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
  445. package/dist/src/utils/flashFallback.test.js +1 -1
  446. package/dist/src/utils/flashFallback.test.js.map +1 -1
  447. package/dist/src/utils/googleErrors.js +31 -18
  448. package/dist/src/utils/googleErrors.js.map +1 -1
  449. package/dist/src/utils/googleErrors.test.js +10 -2
  450. package/dist/src/utils/googleErrors.test.js.map +1 -1
  451. package/dist/src/utils/googleQuotaErrors.d.ts +3 -3
  452. package/dist/src/utils/googleQuotaErrors.js +32 -6
  453. package/dist/src/utils/googleQuotaErrors.js.map +1 -1
  454. package/dist/src/utils/googleQuotaErrors.test.js +94 -2
  455. package/dist/src/utils/googleQuotaErrors.test.js.map +1 -1
  456. package/dist/src/utils/memoryDiscovery.d.ts +5 -0
  457. package/dist/src/utils/memoryDiscovery.js +7 -3
  458. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  459. package/dist/src/utils/memoryDiscovery.test.js +28 -0
  460. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  461. package/dist/src/utils/nextSpeakerChecker.test.js +4 -0
  462. package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
  463. package/dist/src/utils/pathCorrector.js +12 -2
  464. package/dist/src/utils/pathCorrector.js.map +1 -1
  465. package/dist/src/utils/pathCorrector.test.js +6 -2
  466. package/dist/src/utils/pathCorrector.test.js.map +1 -1
  467. package/dist/src/utils/retry.d.ts +11 -0
  468. package/dist/src/utils/retry.js +54 -13
  469. package/dist/src/utils/retry.js.map +1 -1
  470. package/dist/src/utils/retry.test.js +170 -10
  471. package/dist/src/utils/retry.test.js.map +1 -1
  472. package/dist/src/utils/shell-permissions.d.ts +52 -0
  473. package/dist/src/utils/shell-permissions.js +188 -0
  474. package/dist/src/utils/shell-permissions.js.map +1 -0
  475. package/dist/src/utils/shell-permissions.test.d.ts +6 -0
  476. package/dist/src/utils/shell-permissions.test.js +347 -0
  477. package/dist/src/utils/shell-permissions.test.js.map +1 -0
  478. package/dist/src/utils/shell-utils.d.ts +10 -47
  479. package/dist/src/utils/shell-utils.js +1 -182
  480. package/dist/src/utils/shell-utils.js.map +1 -1
  481. package/dist/src/utils/shell-utils.test.js +1 -288
  482. package/dist/src/utils/shell-utils.test.js.map +1 -1
  483. package/dist/src/utils/stdio.d.ts +2 -2
  484. package/dist/src/utils/stdio.js +2 -2
  485. package/dist/src/utils/stdio.js.map +1 -1
  486. package/dist/src/utils/stdio.test.js +5 -5
  487. package/dist/src/utils/stdio.test.js.map +1 -1
  488. package/dist/src/utils/terminalSerializer.test.js +17 -0
  489. package/dist/src/utils/terminalSerializer.test.js.map +1 -1
  490. package/dist/src/utils/tokenCalculation.d.ts +19 -0
  491. package/dist/src/utils/tokenCalculation.js +70 -0
  492. package/dist/src/utils/tokenCalculation.js.map +1 -0
  493. package/dist/src/utils/tokenCalculation.test.d.ts +6 -0
  494. package/dist/src/utils/tokenCalculation.test.js +78 -0
  495. package/dist/src/utils/tokenCalculation.test.js.map +1 -0
  496. package/dist/src/utils/tool-utils.js.map +1 -1
  497. package/dist/src/utils/version.d.ts +6 -0
  498. package/dist/src/utils/version.js +15 -0
  499. package/dist/src/utils/version.js.map +1 -0
  500. package/dist/src/utils/version.test.d.ts +6 -0
  501. package/dist/src/utils/version.test.js +39 -0
  502. package/dist/src/utils/version.test.js.map +1 -0
  503. package/dist/tsconfig.tsbuildinfo +1 -1
  504. package/package.json +1 -1
@@ -3,16 +3,18 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ import { GoogleAuth } from 'google-auth-library';
6
7
  import * as ClientLib from '@modelcontextprotocol/sdk/client/index.js';
7
8
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
8
9
  import * as SdkClientStdioLib from '@modelcontextprotocol/sdk/client/stdio.js';
9
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
10
+ import { StreamableHTTPClientTransport, StreamableHTTPError, } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
10
11
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
11
12
  import { AuthProviderType } from '../config/config.js';
12
13
  import { GoogleCredentialProvider } from '../mcp/google-auth-provider.js';
13
14
  import { MCPOAuthProvider } from '../mcp/oauth-provider.js';
14
15
  import { MCPOAuthTokenStorage } from '../mcp/oauth-token-storage.js';
15
16
  import { OAuthUtils } from '../mcp/oauth-utils.js';
17
+ import { ToolListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
16
18
  import { WorkspaceContext } from '../utils/workspaceContext.js';
17
19
  import { connectToMcpServer, createTransport, hasNetworkTransport, isEnabled, McpClient, populateMcpServerCommand, } from './mcp-client.js';
18
20
  import * as fs from 'node:fs';
@@ -59,6 +61,7 @@ describe('mcp-client', () => {
59
61
  getStatus: vi.fn(),
60
62
  registerCapabilities: vi.fn(),
61
63
  setRequestHandler: vi.fn(),
64
+ setNotificationHandler: vi.fn(),
62
65
  getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
63
66
  listTools: vi.fn().mockResolvedValue({
64
67
  tools: [
@@ -83,12 +86,20 @@ describe('mcp-client', () => {
83
86
  sortTools: vi.fn(),
84
87
  getMessageBus: vi.fn().mockReturnValue(undefined),
85
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
+ };
86
97
  const client = new McpClient('test-server', {
87
98
  command: 'test-command',
88
- }, mockedToolRegistry, {}, workspaceContext, false);
99
+ }, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
89
100
  await client.connect();
90
101
  await client.discover({});
91
- expect(mockedClient.listTools).toHaveBeenCalledWith({});
102
+ expect(mockedClient.listTools).toHaveBeenCalledWith({}, { timeout: 600000 });
92
103
  });
93
104
  it('should not skip tools even if a parameter is missing a type', async () => {
94
105
  const consoleWarnSpy = vi
@@ -101,6 +112,7 @@ describe('mcp-client', () => {
101
112
  getStatus: vi.fn(),
102
113
  registerCapabilities: vi.fn(),
103
114
  setRequestHandler: vi.fn(),
115
+ setNotificationHandler: vi.fn(),
104
116
  getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
105
117
  listTools: vi.fn().mockResolvedValue({
106
118
  tools: [
@@ -136,9 +148,17 @@ describe('mcp-client', () => {
136
148
  sortTools: vi.fn(),
137
149
  getMessageBus: vi.fn().mockReturnValue(undefined),
138
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
+ };
139
159
  const client = new McpClient('test-server', {
140
160
  command: 'test-command',
141
- }, mockedToolRegistry, {}, workspaceContext, false);
161
+ }, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
142
162
  await client.connect();
143
163
  await client.discover({});
144
164
  expect(mockedToolRegistry.registerTool).toHaveBeenCalledTimes(2);
@@ -153,6 +173,7 @@ describe('mcp-client', () => {
153
173
  getStatus: vi.fn(),
154
174
  registerCapabilities: vi.fn(),
155
175
  setRequestHandler: vi.fn(),
176
+ setNotificationHandler: vi.fn(),
156
177
  getServerCapabilities: vi.fn().mockReturnValue({ prompts: {} }),
157
178
  listTools: vi.fn().mockResolvedValue({ tools: [] }),
158
179
  listPrompts: vi.fn().mockRejectedValue(new Error('Test error')),
@@ -164,11 +185,19 @@ describe('mcp-client', () => {
164
185
  registerTool: vi.fn(),
165
186
  getMessageBus: vi.fn().mockReturnValue(undefined),
166
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
+ };
167
196
  const client = new McpClient('test-server', {
168
197
  command: 'test-command',
169
- }, mockedToolRegistry, {}, workspaceContext, false);
198
+ }, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
170
199
  await client.connect();
171
- 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.');
172
201
  expect(coreEvents.emitFeedback).toHaveBeenCalledWith('error', `Error discovering prompts from test-server: Test error`, expect.any(Error));
173
202
  });
174
203
  it('should not discover tools if server does not support them', async () => {
@@ -179,6 +208,7 @@ describe('mcp-client', () => {
179
208
  getStatus: vi.fn(),
180
209
  registerCapabilities: vi.fn(),
181
210
  setRequestHandler: vi.fn(),
211
+ setNotificationHandler: vi.fn(),
182
212
  getServerCapabilities: vi.fn().mockReturnValue({ prompts: {} }),
183
213
  listPrompts: vi.fn().mockResolvedValue({ prompts: [] }),
184
214
  request: vi.fn().mockResolvedValue({}),
@@ -190,11 +220,19 @@ describe('mcp-client', () => {
190
220
  sortTools: vi.fn(),
191
221
  getMessageBus: vi.fn().mockReturnValue(undefined),
192
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
+ };
193
231
  const client = new McpClient('test-server', {
194
232
  command: 'test-command',
195
- }, mockedToolRegistry, {}, workspaceContext, false);
233
+ }, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
196
234
  await client.connect();
197
- 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.');
198
236
  });
199
237
  it('should discover tools if server supports them', async () => {
200
238
  const mockedClient = {
@@ -204,6 +242,7 @@ describe('mcp-client', () => {
204
242
  getStatus: vi.fn(),
205
243
  registerCapabilities: vi.fn(),
206
244
  setRequestHandler: vi.fn(),
245
+ setNotificationHandler: vi.fn(),
207
246
  getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
208
247
  listTools: vi.fn().mockResolvedValue({
209
248
  tools: [
@@ -224,9 +263,17 @@ describe('mcp-client', () => {
224
263
  sortTools: vi.fn(),
225
264
  getMessageBus: vi.fn().mockReturnValue(undefined),
226
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
+ };
227
274
  const client = new McpClient('test-server', {
228
275
  command: 'test-command',
229
- }, mockedToolRegistry, {}, workspaceContext, false);
276
+ }, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
230
277
  await client.connect();
231
278
  await client.discover({});
232
279
  expect(mockedToolRegistry.registerTool).toHaveBeenCalledOnce();
@@ -239,6 +286,7 @@ describe('mcp-client', () => {
239
286
  getStatus: vi.fn(),
240
287
  registerCapabilities: vi.fn(),
241
288
  setRequestHandler: vi.fn(),
289
+ setNotificationHandler: vi.fn(),
242
290
  getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
243
291
  listTools: vi.fn().mockResolvedValue({
244
292
  tools: [
@@ -274,9 +322,17 @@ describe('mcp-client', () => {
274
322
  sortTools: vi.fn(),
275
323
  getMessageBus: vi.fn().mockReturnValue(undefined),
276
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
+ };
277
333
  const client = new McpClient('test-server', {
278
334
  command: 'test-command',
279
- }, mockedToolRegistry, {}, workspaceContext, false);
335
+ }, mockedToolRegistry, promptRegistry, resourceRegistry, workspaceContext, {}, false);
280
336
  await client.connect();
281
337
  await client.discover({});
282
338
  expect(mockedToolRegistry.registerTool).toHaveBeenCalledOnce();
@@ -297,6 +353,126 @@ describe('mcp-client', () => {
297
353
  },
298
354
  });
299
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
+ });
300
476
  it('should remove tools and prompts on disconnect', async () => {
301
477
  const mockedClient = {
302
478
  connect: vi.fn(),
@@ -304,6 +480,7 @@ describe('mcp-client', () => {
304
480
  getStatus: vi.fn(),
305
481
  registerCapabilities: vi.fn(),
306
482
  setRequestHandler: vi.fn(),
483
+ setNotificationHandler: vi.fn(),
307
484
  getServerCapabilities: vi
308
485
  .fn()
309
486
  .mockReturnValue({ tools: {}, prompts: {} }),
@@ -335,9 +512,13 @@ describe('mcp-client', () => {
335
512
  unregisterPrompt: vi.fn(),
336
513
  removePromptsByServer: vi.fn(),
337
514
  };
515
+ const resourceRegistry = {
516
+ setResourcesForServer: vi.fn(),
517
+ removeResourcesByServer: vi.fn(),
518
+ };
338
519
  const client = new McpClient('test-server', {
339
520
  command: 'test-command',
340
- }, mockedToolRegistry, mockedPromptRegistry, workspaceContext, false);
521
+ }, mockedToolRegistry, mockedPromptRegistry, resourceRegistry, workspaceContext, {}, false);
341
522
  await client.connect();
342
523
  await client.discover({});
343
524
  expect(mockedToolRegistry.registerTool).toHaveBeenCalledOnce();
@@ -346,6 +527,251 @@ describe('mcp-client', () => {
346
527
  expect(mockedClient.close).toHaveBeenCalledOnce();
347
528
  expect(mockedToolRegistry.removeMcpToolsByServer).toHaveBeenCalledOnce();
348
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);
349
775
  });
350
776
  });
351
777
  describe('appendMcpServerCommand', () => {
@@ -374,19 +800,23 @@ describe('mcp-client', () => {
374
800
  httpUrl: 'http://test-server',
375
801
  }, false);
376
802
  expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
377
- expect(transport).toHaveProperty('_url', new URL('http://test-server/'));
803
+ expect(transport).toMatchObject({
804
+ _url: new URL('http://test-server'),
805
+ _requestInit: { headers: {} },
806
+ });
378
807
  });
379
808
  it('with headers', async () => {
380
- // We need this to be an any type because we dig into its private state.
381
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
382
809
  const transport = await createTransport('test-server', {
383
810
  httpUrl: 'http://test-server',
384
811
  headers: { Authorization: 'derp' },
385
812
  }, false);
386
813
  expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
387
- expect(transport).toHaveProperty('_url', new URL('http://test-server/'));
388
- const authHeader = transport._requestInit?.headers?.['Authorization'];
389
- expect(authHeader).toBe('derp');
814
+ expect(transport).toMatchObject({
815
+ _url: new URL('http://test-server'),
816
+ _requestInit: {
817
+ headers: { Authorization: 'derp' },
818
+ },
819
+ });
390
820
  });
391
821
  });
392
822
  describe('should connect via url', () => {
@@ -394,20 +824,96 @@ describe('mcp-client', () => {
394
824
  const transport = await createTransport('test-server', {
395
825
  url: 'http://test-server',
396
826
  }, false);
397
- expect(transport).toBeInstanceOf(SSEClientTransport);
398
- 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
+ });
399
832
  });
400
833
  it('with headers', async () => {
401
- // We need this to be an any type because we dig into its private state.
402
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
403
834
  const transport = await createTransport('test-server', {
404
835
  url: 'http://test-server',
405
836
  headers: { Authorization: 'derp' },
406
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);
862
+ expect(transport).toBeInstanceOf(SSEClientTransport);
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);
407
898
  expect(transport).toBeInstanceOf(SSEClientTransport);
408
- expect(transport).toHaveProperty('_url', new URL('http://test-server/'));
409
- const authHeader = transport._requestInit?.headers?.['Authorization'];
410
- expect(authHeader).toBe('derp');
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
+ });
411
917
  });
412
918
  });
413
919
  it('should connect via command', async () => {
@@ -429,6 +935,14 @@ describe('mcp-client', () => {
429
935
  });
430
936
  });
431
937
  describe('useGoogleCredentialProvider', () => {
938
+ beforeEach(() => {
939
+ // Mock GoogleAuth client
940
+ const mockClient = {
941
+ getAccessToken: vi.fn().mockResolvedValue({ token: 'test-token' }),
942
+ quotaProjectId: 'myproject',
943
+ };
944
+ GoogleAuth.prototype.getClient = vi.fn().mockResolvedValue(mockClient);
945
+ });
432
946
  it('should use GoogleCredentialProvider when specified', async () => {
433
947
  const transport = await createTransport('test-server', {
434
948
  httpUrl: 'http://test.googleapis.com',
@@ -448,9 +962,48 @@ describe('mcp-client', () => {
448
962
  const googUserProject = transport._requestInit?.headers?.['X-Goog-User-Project'];
449
963
  expect(googUserProject).toBe('myproject');
450
964
  });
965
+ it('should use headers from GoogleCredentialProvider', async () => {
966
+ const mockGetRequestHeaders = vi.fn().mockResolvedValue({
967
+ 'X-Goog-User-Project': 'provider-project',
968
+ });
969
+ vi.spyOn(GoogleCredentialProvider.prototype, 'getRequestHeaders').mockImplementation(mockGetRequestHeaders);
970
+ const transport = await createTransport('test-server', {
971
+ httpUrl: 'http://test.googleapis.com',
972
+ authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
973
+ oauth: {
974
+ scopes: ['scope1'],
975
+ },
976
+ }, false);
977
+ expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
978
+ expect(mockGetRequestHeaders).toHaveBeenCalled();
979
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
980
+ const headers = transport._requestInit?.headers;
981
+ expect(headers['X-Goog-User-Project']).toBe('provider-project');
982
+ });
983
+ it('should prioritize provider headers over config headers', async () => {
984
+ const mockGetRequestHeaders = vi.fn().mockResolvedValue({
985
+ 'X-Goog-User-Project': 'provider-project',
986
+ });
987
+ vi.spyOn(GoogleCredentialProvider.prototype, 'getRequestHeaders').mockImplementation(mockGetRequestHeaders);
988
+ const transport = await createTransport('test-server', {
989
+ httpUrl: 'http://test.googleapis.com',
990
+ authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
991
+ oauth: {
992
+ scopes: ['scope1'],
993
+ },
994
+ headers: {
995
+ 'X-Goog-User-Project': 'config-project',
996
+ },
997
+ }, false);
998
+ expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
999
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1000
+ const headers = transport._requestInit?.headers;
1001
+ expect(headers['X-Goog-User-Project']).toBe('provider-project');
1002
+ });
451
1003
  it('should use GoogleCredentialProvider with SSE transport', async () => {
452
1004
  const transport = await createTransport('test-server', {
453
1005
  url: 'http://test.googleapis.com',
1006
+ type: 'sse',
454
1007
  authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
455
1008
  oauth: {
456
1009
  scopes: ['scope1'],
@@ -573,7 +1126,7 @@ describe('connectToMcpServer with OAuth', () => {
573
1126
  const authUrl = 'http://auth.example.com/auth';
574
1127
  const tokenUrl = 'http://auth.example.com/token';
575
1128
  const wwwAuthHeader = `Bearer realm="test", resource_metadata="http://test-server.com/.well-known/oauth-protected-resource"`;
576
- 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}`));
577
1130
  vi.mocked(OAuthUtils.discoverOAuthConfig).mockResolvedValue({
578
1131
  authorizationUrl: authUrl,
579
1132
  tokenUrl,
@@ -586,7 +1139,7 @@ describe('connectToMcpServer with OAuth', () => {
586
1139
  capturedTransport = transport;
587
1140
  return Promise.resolve();
588
1141
  });
589
- const client = await connectToMcpServer('test-server', { httpUrl: serverUrl }, false, workspaceContext);
1142
+ const client = await connectToMcpServer('test-server', { httpUrl: serverUrl, oauth: { enabled: true } }, false, workspaceContext);
590
1143
  expect(client).toBe(mockedClient);
591
1144
  expect(mockedClient.connect).toHaveBeenCalledTimes(2);
592
1145
  expect(mockAuthProvider.authenticate).toHaveBeenCalledOnce();
@@ -597,7 +1150,7 @@ describe('connectToMcpServer with OAuth', () => {
597
1150
  const serverUrl = 'http://test-server.com';
598
1151
  const authUrl = 'http://auth.example.com/auth';
599
1152
  const tokenUrl = 'http://auth.example.com/token';
600
- vi.mocked(mockedClient.connect).mockRejectedValueOnce(new Error('401 Unauthorized'));
1153
+ vi.mocked(mockedClient.connect).mockRejectedValueOnce(new StreamableHTTPError(401, 'Unauthorized'));
601
1154
  vi.mocked(OAuthUtils.discoverOAuthConfig).mockResolvedValue({
602
1155
  authorizationUrl: authUrl,
603
1156
  tokenUrl,
@@ -611,7 +1164,7 @@ describe('connectToMcpServer with OAuth', () => {
611
1164
  capturedTransport = transport;
612
1165
  return Promise.resolve();
613
1166
  });
614
- const client = await connectToMcpServer('test-server', { httpUrl: serverUrl }, false, workspaceContext);
1167
+ const client = await connectToMcpServer('test-server', { httpUrl: serverUrl, oauth: { enabled: true } }, false, workspaceContext);
615
1168
  expect(client).toBe(mockedClient);
616
1169
  expect(mockedClient.connect).toHaveBeenCalledTimes(2);
617
1170
  expect(mockAuthProvider.authenticate).toHaveBeenCalledOnce();
@@ -620,4 +1173,118 @@ describe('connectToMcpServer with OAuth', () => {
620
1173
  expect(authHeader).toBe('Bearer test-access-token-from-discovery');
621
1174
  });
622
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
+ });
623
1290
  //# sourceMappingURL=mcp-client.test.js.map