@google/gemini-cli-core 0.1.12 → 0.1.14

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 (287) hide show
  1. package/README.md +30 -3
  2. package/dist/google-gemini-cli-core-0.1.13.tgz +0 -0
  3. package/dist/src/code_assist/codeAssist.js +2 -2
  4. package/dist/src/code_assist/codeAssist.js.map +1 -1
  5. package/dist/src/code_assist/oauth2.js +46 -5
  6. package/dist/src/code_assist/oauth2.js.map +1 -1
  7. package/dist/src/code_assist/oauth2.test.js +100 -3
  8. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  9. package/dist/src/code_assist/server.d.ts +4 -6
  10. package/dist/src/code_assist/server.js +4 -69
  11. package/dist/src/code_assist/server.js.map +1 -1
  12. package/dist/src/code_assist/server.test.js +10 -2
  13. package/dist/src/code_assist/server.test.js.map +1 -1
  14. package/dist/src/code_assist/setup.d.ts +6 -1
  15. package/dist/src/code_assist/setup.js +4 -1
  16. package/dist/src/code_assist/setup.js.map +1 -1
  17. package/dist/src/code_assist/setup.test.js +4 -1
  18. package/dist/src/code_assist/setup.test.js.map +1 -1
  19. package/dist/src/code_assist/types.d.ts +2 -2
  20. package/dist/src/config/config.d.ts +52 -11
  21. package/dist/src/config/config.js +79 -33
  22. package/dist/src/config/config.js.map +1 -1
  23. package/dist/src/config/config.test.js +29 -26
  24. package/dist/src/config/config.test.js.map +1 -1
  25. package/dist/src/config/flashFallback.test.js +1 -1
  26. package/dist/src/config/flashFallback.test.js.map +1 -1
  27. package/dist/src/core/client.d.ts +8 -3
  28. package/dist/src/core/client.js +62 -3
  29. package/dist/src/core/client.js.map +1 -1
  30. package/dist/src/core/client.test.js +145 -37
  31. package/dist/src/core/client.test.js.map +1 -1
  32. package/dist/src/core/contentGenerator.d.ts +3 -2
  33. package/dist/src/core/contentGenerator.js +5 -4
  34. package/dist/src/core/contentGenerator.js.map +1 -1
  35. package/dist/src/core/contentGenerator.test.js +12 -5
  36. package/dist/src/core/contentGenerator.test.js.map +1 -1
  37. package/dist/src/core/coreToolScheduler.js +14 -1
  38. package/dist/src/core/coreToolScheduler.js.map +1 -1
  39. package/dist/src/core/coreToolScheduler.test.js +84 -2
  40. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  41. package/dist/src/core/geminiChat.d.ts +4 -3
  42. package/dist/src/core/geminiChat.js +8 -11
  43. package/dist/src/core/geminiChat.js.map +1 -1
  44. package/dist/src/core/geminiRequest.js +2 -37
  45. package/dist/src/core/geminiRequest.js.map +1 -1
  46. package/dist/src/core/logger.js +6 -0
  47. package/dist/src/core/logger.js.map +1 -1
  48. package/dist/src/core/logger.test.js +1 -1
  49. package/dist/src/core/logger.test.js.map +1 -1
  50. package/dist/src/core/modelCheck.d.ts +1 -1
  51. package/dist/src/core/modelCheck.js +10 -3
  52. package/dist/src/core/modelCheck.js.map +1 -1
  53. package/dist/src/core/nonInteractiveToolExecutor.test.js +8 -5
  54. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  55. package/dist/src/core/prompts.js +42 -18
  56. package/dist/src/core/prompts.js.map +1 -1
  57. package/dist/src/core/prompts.test.js +121 -4
  58. package/dist/src/core/prompts.test.js.map +1 -1
  59. package/dist/src/core/turn.d.ts +12 -3
  60. package/dist/src/core/turn.js +10 -0
  61. package/dist/src/core/turn.js.map +1 -1
  62. package/dist/src/core/turn.test.js +129 -0
  63. package/dist/src/core/turn.test.js.map +1 -1
  64. package/dist/src/ide/ide-client.d.ts +28 -0
  65. package/dist/src/ide/ide-client.js +79 -0
  66. package/dist/src/ide/ide-client.js.map +1 -0
  67. package/dist/src/ide/ideContext.d.ts +174 -0
  68. package/dist/src/ide/ideContext.js +101 -0
  69. package/dist/src/ide/ideContext.js.map +1 -0
  70. package/dist/src/ide/ideContext.test.js +111 -0
  71. package/dist/src/ide/ideContext.test.js.map +1 -0
  72. package/dist/src/index.d.ts +11 -0
  73. package/dist/src/index.js +11 -0
  74. package/dist/src/index.js.map +1 -1
  75. package/dist/src/mcp/google-auth-provider.d.ts +23 -0
  76. package/dist/src/mcp/google-auth-provider.js +63 -0
  77. package/dist/src/mcp/google-auth-provider.js.map +1 -0
  78. package/dist/src/mcp/google-auth-provider.test.d.ts +6 -0
  79. package/dist/src/mcp/google-auth-provider.test.js +54 -0
  80. package/dist/src/mcp/google-auth-provider.test.js.map +1 -0
  81. package/dist/src/mcp/oauth-provider.d.ts +142 -0
  82. package/dist/src/mcp/oauth-provider.js +446 -0
  83. package/dist/src/mcp/oauth-provider.js.map +1 -0
  84. package/dist/src/mcp/oauth-provider.test.d.ts +6 -0
  85. package/dist/src/mcp/oauth-provider.test.js +520 -0
  86. package/dist/src/mcp/oauth-provider.test.js.map +1 -0
  87. package/dist/src/mcp/oauth-token-storage.d.ts +81 -0
  88. package/dist/src/mcp/oauth-token-storage.js +149 -0
  89. package/dist/src/mcp/oauth-token-storage.js.map +1 -0
  90. package/dist/src/mcp/oauth-token-storage.test.d.ts +6 -0
  91. package/dist/src/mcp/oauth-token-storage.test.js +205 -0
  92. package/dist/src/mcp/oauth-token-storage.test.js.map +1 -0
  93. package/dist/src/mcp/oauth-utils.d.ts +109 -0
  94. package/dist/src/mcp/oauth-utils.js +183 -0
  95. package/dist/src/mcp/oauth-utils.js.map +1 -0
  96. package/dist/src/mcp/oauth-utils.test.d.ts +6 -0
  97. package/dist/src/mcp/oauth-utils.test.js +144 -0
  98. package/dist/src/mcp/oauth-utils.test.js.map +1 -0
  99. package/dist/src/services/gitService.js +1 -5
  100. package/dist/src/services/gitService.js.map +1 -1
  101. package/dist/src/services/gitService.test.js +1 -6
  102. package/dist/src/services/gitService.test.js.map +1 -1
  103. package/dist/src/services/loopDetectionService.d.ts +94 -0
  104. package/dist/src/services/loopDetectionService.js +318 -0
  105. package/dist/src/services/loopDetectionService.js.map +1 -0
  106. package/dist/src/services/loopDetectionService.test.d.ts +6 -0
  107. package/dist/src/services/loopDetectionService.test.js +266 -0
  108. package/dist/src/services/loopDetectionService.test.js.map +1 -0
  109. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +5 -1
  110. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +69 -4
  111. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  112. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +4 -1
  113. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +9 -0
  114. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  115. package/dist/src/telemetry/constants.d.ts +1 -0
  116. package/dist/src/telemetry/constants.js +1 -0
  117. package/dist/src/telemetry/constants.js.map +1 -1
  118. package/dist/src/telemetry/file-exporters.d.ts +28 -0
  119. package/dist/src/telemetry/file-exporters.js +62 -0
  120. package/dist/src/telemetry/file-exporters.js.map +1 -0
  121. package/dist/src/telemetry/integration.test.circular.d.ts +6 -0
  122. package/dist/src/telemetry/integration.test.circular.js +53 -0
  123. package/dist/src/telemetry/integration.test.circular.js.map +1 -0
  124. package/dist/src/telemetry/loggers.d.ts +3 -1
  125. package/dist/src/telemetry/loggers.js +34 -2
  126. package/dist/src/telemetry/loggers.js.map +1 -1
  127. package/dist/src/telemetry/loggers.test.circular.d.ts +6 -0
  128. package/dist/src/telemetry/loggers.test.circular.js +100 -0
  129. package/dist/src/telemetry/loggers.test.circular.js.map +1 -0
  130. package/dist/src/telemetry/sdk.js +17 -6
  131. package/dist/src/telemetry/sdk.js.map +1 -1
  132. package/dist/src/telemetry/types.d.ts +19 -1
  133. package/dist/src/telemetry/types.js +28 -0
  134. package/dist/src/telemetry/types.js.map +1 -1
  135. package/dist/src/telemetry/uiTelemetry.d.ts +1 -0
  136. package/dist/src/telemetry/uiTelemetry.js +7 -0
  137. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  138. package/dist/src/telemetry/uiTelemetry.test.js +92 -0
  139. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  140. package/dist/src/tools/edit.d.ts +7 -12
  141. package/dist/src/tools/edit.js +34 -32
  142. package/dist/src/tools/edit.js.map +1 -1
  143. package/dist/src/tools/edit.test.js +12 -0
  144. package/dist/src/tools/edit.test.js.map +1 -1
  145. package/dist/src/tools/glob.d.ts +1 -14
  146. package/dist/src/tools/glob.js +13 -36
  147. package/dist/src/tools/glob.js.map +1 -1
  148. package/dist/src/tools/glob.test.js +4 -3
  149. package/dist/src/tools/glob.test.js.map +1 -1
  150. package/dist/src/tools/grep.d.ts +3 -6
  151. package/dist/src/tools/grep.js +12 -18
  152. package/dist/src/tools/grep.js.map +1 -1
  153. package/dist/src/tools/grep.test.js +5 -2
  154. package/dist/src/tools/grep.test.js.map +1 -1
  155. package/dist/src/tools/ls.d.ts +6 -14
  156. package/dist/src/tools/ls.js +47 -40
  157. package/dist/src/tools/ls.js.map +1 -1
  158. package/dist/src/tools/mcp-client.d.ts +59 -1
  159. package/dist/src/tools/mcp-client.js +557 -146
  160. package/dist/src/tools/mcp-client.js.map +1 -1
  161. package/dist/src/tools/mcp-client.test.js +166 -623
  162. package/dist/src/tools/mcp-client.test.js.map +1 -1
  163. package/dist/src/tools/mcp-tool.d.ts +11 -5
  164. package/dist/src/tools/mcp-tool.js +34 -10
  165. package/dist/src/tools/mcp-tool.js.map +1 -1
  166. package/dist/src/tools/mcp-tool.test.js +74 -24
  167. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  168. package/dist/src/tools/memoryTool.js +2 -2
  169. package/dist/src/tools/memoryTool.js.map +1 -1
  170. package/dist/src/tools/read-file.d.ts +3 -3
  171. package/dist/src/tools/read-file.js +10 -10
  172. package/dist/src/tools/read-file.js.map +1 -1
  173. package/dist/src/tools/read-file.test.js +100 -70
  174. package/dist/src/tools/read-file.test.js.map +1 -1
  175. package/dist/src/tools/read-many-files.d.ts +6 -10
  176. package/dist/src/tools/read-many-files.js +74 -43
  177. package/dist/src/tools/read-many-files.js.map +1 -1
  178. package/dist/src/tools/read-many-files.test.js +7 -3
  179. package/dist/src/tools/read-many-files.test.js.map +1 -1
  180. package/dist/src/tools/shell.d.ts +2 -23
  181. package/dist/src/tools/shell.js +58 -138
  182. package/dist/src/tools/shell.js.map +1 -1
  183. package/dist/src/tools/shell.test.js +85 -311
  184. package/dist/src/tools/shell.test.js.map +1 -1
  185. package/dist/src/tools/tool-registry.d.ts +13 -2
  186. package/dist/src/tools/tool-registry.js +57 -10
  187. package/dist/src/tools/tool-registry.js.map +1 -1
  188. package/dist/src/tools/tool-registry.test.js +112 -41
  189. package/dist/src/tools/tool-registry.test.js.map +1 -1
  190. package/dist/src/tools/tools.d.ts +37 -2
  191. package/dist/src/tools/tools.js +25 -2
  192. package/dist/src/tools/tools.js.map +1 -1
  193. package/dist/src/tools/web-fetch.js +7 -2
  194. package/dist/src/tools/web-fetch.js.map +1 -1
  195. package/dist/src/tools/web-fetch.test.js +1 -0
  196. package/dist/src/tools/web-fetch.test.js.map +1 -1
  197. package/dist/src/tools/web-search.js +2 -2
  198. package/dist/src/tools/web-search.js.map +1 -1
  199. package/dist/src/tools/write-file.d.ts +0 -8
  200. package/dist/src/tools/write-file.js +14 -23
  201. package/dist/src/tools/write-file.js.map +1 -1
  202. package/dist/src/utils/bfsFileSearch.d.ts +2 -0
  203. package/dist/src/utils/bfsFileSearch.js +4 -1
  204. package/dist/src/utils/bfsFileSearch.js.map +1 -1
  205. package/dist/src/utils/bfsFileSearch.test.js +108 -105
  206. package/dist/src/utils/bfsFileSearch.test.js.map +1 -1
  207. package/dist/src/utils/browser.d.ts +13 -0
  208. package/dist/src/utils/browser.js +49 -0
  209. package/dist/src/utils/browser.js.map +1 -0
  210. package/dist/src/utils/editCorrector.js +4 -4
  211. package/dist/src/utils/editCorrector.js.map +1 -1
  212. package/dist/src/utils/editCorrector.test.js +1 -1
  213. package/dist/src/utils/editor.js +16 -10
  214. package/dist/src/utils/editor.js.map +1 -1
  215. package/dist/src/utils/editor.test.js +128 -28
  216. package/dist/src/utils/editor.test.js.map +1 -1
  217. package/dist/src/utils/errorReporting.d.ts +1 -1
  218. package/dist/src/utils/errorReporting.js +2 -2
  219. package/dist/src/utils/errorReporting.js.map +1 -1
  220. package/dist/src/utils/errorReporting.test.js +44 -38
  221. package/dist/src/utils/errorReporting.test.js.map +1 -1
  222. package/dist/src/utils/errors.js +4 -4
  223. package/dist/src/utils/errors.js.map +1 -1
  224. package/dist/src/utils/fileUtils.d.ts +4 -4
  225. package/dist/src/utils/fileUtils.js +33 -17
  226. package/dist/src/utils/fileUtils.js.map +1 -1
  227. package/dist/src/utils/fileUtils.test.js +37 -37
  228. package/dist/src/utils/fileUtils.test.js.map +1 -1
  229. package/dist/src/utils/getFolderStructure.d.ts +3 -2
  230. package/dist/src/utils/getFolderStructure.js +27 -28
  231. package/dist/src/utils/getFolderStructure.js.map +1 -1
  232. package/dist/src/utils/getFolderStructure.test.js +169 -187
  233. package/dist/src/utils/getFolderStructure.test.js.map +1 -1
  234. package/dist/src/utils/gitIgnoreParser.js +4 -7
  235. package/dist/src/utils/gitIgnoreParser.js.map +1 -1
  236. package/dist/src/utils/gitIgnoreParser.test.js +70 -61
  237. package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
  238. package/dist/src/utils/memoryDiscovery.d.ts +2 -1
  239. package/dist/src/utils/memoryDiscovery.js +11 -5
  240. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  241. package/dist/src/utils/memoryDiscovery.test.js +160 -371
  242. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  243. package/dist/src/utils/partUtils.d.ts +14 -0
  244. package/dist/src/utils/partUtils.js +65 -0
  245. package/dist/src/utils/partUtils.js.map +1 -0
  246. package/dist/src/utils/partUtils.test.d.ts +6 -0
  247. package/dist/src/utils/partUtils.test.js +130 -0
  248. package/dist/src/utils/partUtils.test.js.map +1 -0
  249. package/dist/src/utils/paths.d.ts +11 -0
  250. package/dist/src/utils/paths.js +17 -1
  251. package/dist/src/utils/paths.js.map +1 -1
  252. package/dist/src/utils/quotaErrorDetection.js +2 -11
  253. package/dist/src/utils/quotaErrorDetection.js.map +1 -1
  254. package/dist/src/utils/retry.d.ts +6 -0
  255. package/dist/src/utils/retry.js +2 -2
  256. package/dist/src/utils/retry.js.map +1 -1
  257. package/dist/src/utils/safeJsonStringify.d.ts +13 -0
  258. package/dist/src/utils/safeJsonStringify.js +25 -0
  259. package/dist/src/utils/safeJsonStringify.js.map +1 -0
  260. package/dist/src/utils/safeJsonStringify.test.d.ts +6 -0
  261. package/dist/src/utils/safeJsonStringify.test.js +61 -0
  262. package/dist/src/utils/safeJsonStringify.test.js.map +1 -0
  263. package/dist/src/utils/schemaValidator.d.ts +1 -1
  264. package/dist/src/utils/schemaValidator.js +6 -3
  265. package/dist/src/utils/schemaValidator.js.map +1 -1
  266. package/dist/src/utils/shell-utils.d.ts +44 -0
  267. package/dist/src/utils/shell-utils.js +243 -0
  268. package/dist/src/utils/shell-utils.js.map +1 -0
  269. package/dist/src/utils/shell-utils.test.d.ts +6 -0
  270. package/dist/src/utils/shell-utils.test.js +450 -0
  271. package/dist/src/utils/shell-utils.test.js.map +1 -0
  272. package/dist/src/utils/summarizer.d.ts +1 -1
  273. package/dist/src/utils/summarizer.js +11 -39
  274. package/dist/src/utils/summarizer.js.map +1 -1
  275. package/dist/src/utils/summarizer.test.js +1 -1
  276. package/dist/src/utils/systemEncoding.d.ts +40 -0
  277. package/dist/src/utils/systemEncoding.js +149 -0
  278. package/dist/src/utils/systemEncoding.js.map +1 -0
  279. package/dist/src/utils/systemEncoding.test.d.ts +6 -0
  280. package/dist/src/utils/systemEncoding.test.js +368 -0
  281. package/dist/src/utils/systemEncoding.test.js.map +1 -0
  282. package/dist/tsconfig.tsbuildinfo +1 -1
  283. package/package.json +4 -3
  284. package/dist/google-gemini-cli-core-0.1.11.tgz +0 -0
  285. package/dist/src/core/geminiRequest.test.js +0 -72
  286. package/dist/src/core/geminiRequest.test.js.map +0 -1
  287. /package/dist/src/{core/geminiRequest.test.d.ts → ide/ideContext.test.d.ts} +0 -0
@@ -3,651 +3,194 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- /* eslint-disable @typescript-eslint/no-explicit-any */
7
- import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
8
- import { discoverMcpTools } from './mcp-client.js';
9
- import { sanitizeParameters } from './tool-registry.js';
10
- import { Type } from '@google/genai';
11
- import { DiscoveredMCPTool } from './mcp-tool.js';
12
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
13
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
14
- import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
6
+ import { afterEach, describe, expect, it, vi, beforeEach } from 'vitest';
15
7
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
16
- import { parse } from 'shell-quote';
17
- // Mock dependencies
18
- vi.mock('shell-quote');
19
- vi.mock('@modelcontextprotocol/sdk/client/index.js', () => {
20
- const MockedClient = vi.fn();
21
- MockedClient.prototype.connect = vi.fn();
22
- MockedClient.prototype.listTools = vi.fn();
23
- // Ensure instances have an onerror property that can be spied on or assigned to
24
- MockedClient.mockImplementation(() => ({
25
- connect: MockedClient.prototype.connect,
26
- listTools: MockedClient.prototype.listTools,
27
- onerror: vi.fn(), // Each instance gets its own onerror mock
28
- }));
29
- return { Client: MockedClient };
30
- });
31
- // Define a global mock for stderr.on that can be cleared and checked
32
- const mockGlobalStdioStderrOn = vi.fn();
33
- vi.mock('@modelcontextprotocol/sdk/client/stdio.js', () => {
34
- // This is the constructor for StdioClientTransport
35
- const MockedStdioTransport = vi.fn().mockImplementation(function (options) {
36
- // Always return a new object with a fresh reference to the global mock for .on
37
- this.options = options;
38
- this.stderr = { on: mockGlobalStdioStderrOn };
39
- this.close = vi.fn().mockResolvedValue(undefined); // Add mock close method
40
- return this;
41
- });
42
- return { StdioClientTransport: MockedStdioTransport };
43
- });
44
- vi.mock('@modelcontextprotocol/sdk/client/sse.js', () => {
45
- const MockedSSETransport = vi.fn().mockImplementation(function () {
46
- this.close = vi.fn().mockResolvedValue(undefined); // Add mock close method
47
- return this;
48
- });
49
- return { SSEClientTransport: MockedSSETransport };
50
- });
51
- vi.mock('@modelcontextprotocol/sdk/client/streamableHttp.js', () => {
52
- const MockedStreamableHTTPTransport = vi.fn().mockImplementation(function () {
53
- this.close = vi.fn().mockResolvedValue(undefined); // Add mock close method
54
- return this;
55
- });
56
- return { StreamableHTTPClientTransport: MockedStreamableHTTPTransport };
57
- });
58
- const mockToolRegistryInstance = {
59
- registerTool: vi.fn(),
60
- getToolsByServer: vi.fn().mockReturnValue([]), // Default to empty array
61
- // Add other methods if they are called by the code under test, with default mocks
62
- getTool: vi.fn(),
63
- getAllTools: vi.fn().mockReturnValue([]),
64
- getFunctionDeclarations: vi.fn().mockReturnValue([]),
65
- discoverTools: vi.fn().mockResolvedValue(undefined),
66
- };
67
- vi.mock('./tool-registry.js', async (importOriginal) => {
68
- const actual = await importOriginal();
69
- return {
70
- ...actual,
71
- ToolRegistry: vi.fn(() => mockToolRegistryInstance),
72
- sanitizeParameters: actual.sanitizeParameters,
73
- };
74
- });
75
- describe('discoverMcpTools', () => {
76
- let mockConfig;
77
- // Use the instance from the module mock
78
- let mockToolRegistry;
79
- beforeEach(() => {
80
- // Assign the shared mock instance to the test-scoped variable
81
- mockToolRegistry = mockToolRegistryInstance;
82
- // Reset individual spies on the shared instance before each test
83
- mockToolRegistry.registerTool.mockClear();
84
- mockToolRegistry.getToolsByServer.mockClear().mockReturnValue([]); // Reset to default
85
- mockToolRegistry.getTool.mockClear().mockReturnValue(undefined); // Default to no existing tool
86
- mockToolRegistry.getAllTools.mockClear().mockReturnValue([]);
87
- mockToolRegistry.getFunctionDeclarations.mockClear().mockReturnValue([]);
88
- mockToolRegistry.discoverTools.mockClear().mockResolvedValue(undefined);
89
- mockConfig = {
90
- getMcpServers: vi.fn().mockReturnValue({}),
91
- getMcpServerCommand: vi.fn().mockReturnValue(undefined),
92
- // getToolRegistry should now return the same shared mock instance
93
- getToolRegistry: vi.fn(() => mockToolRegistry),
94
- };
95
- vi.mocked(parse).mockClear();
96
- vi.mocked(Client).mockClear();
97
- vi.mocked(Client.prototype.connect)
98
- .mockClear()
99
- .mockResolvedValue(undefined);
100
- vi.mocked(Client.prototype.listTools)
101
- .mockClear()
102
- .mockResolvedValue({ tools: [] });
103
- vi.mocked(StdioClientTransport).mockClear();
104
- // Ensure the StdioClientTransport mock constructor returns an object with a close method
105
- vi.mocked(StdioClientTransport).mockImplementation(function (options) {
106
- this.options = options;
107
- this.stderr = { on: mockGlobalStdioStderrOn };
108
- this.close = vi.fn().mockResolvedValue(undefined);
109
- return this;
110
- });
111
- mockGlobalStdioStderrOn.mockClear(); // Clear the global mock in beforeEach
112
- vi.mocked(SSEClientTransport).mockClear();
113
- // Ensure the SSEClientTransport mock constructor returns an object with a close method
114
- vi.mocked(SSEClientTransport).mockImplementation(function () {
115
- this.close = vi.fn().mockResolvedValue(undefined);
116
- return this;
117
- });
118
- vi.mocked(StreamableHTTPClientTransport).mockClear();
119
- // Ensure the StreamableHTTPClientTransport mock constructor returns an object with a close method
120
- vi.mocked(StreamableHTTPClientTransport).mockImplementation(function () {
121
- this.close = vi.fn().mockResolvedValue(undefined);
122
- return this;
123
- });
124
- });
8
+ import { populateMcpServerCommand, createTransport, isEnabled, discoverTools, } from './mcp-client.js';
9
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
10
+ import * as SdkClientStdioLib from '@modelcontextprotocol/sdk/client/stdio.js';
11
+ import * as GenAiLib from '@google/genai';
12
+ import { GoogleCredentialProvider } from '../mcp/google-auth-provider.js';
13
+ import { AuthProviderType } from '../config/config.js';
14
+ vi.mock('@modelcontextprotocol/sdk/client/stdio.js');
15
+ vi.mock('@modelcontextprotocol/sdk/client/index.js');
16
+ vi.mock('@google/genai');
17
+ vi.mock('../mcp/oauth-provider.js');
18
+ vi.mock('../mcp/oauth-token-storage.js');
19
+ describe('mcp-client', () => {
125
20
  afterEach(() => {
126
21
  vi.restoreAllMocks();
127
22
  });
128
- it('should do nothing if no MCP servers or command are configured', async () => {
129
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
130
- expect(mockConfig.getMcpServers).toHaveBeenCalledTimes(1);
131
- expect(mockConfig.getMcpServerCommand).toHaveBeenCalledTimes(1);
132
- expect(Client).not.toHaveBeenCalled();
133
- expect(mockToolRegistry.registerTool).not.toHaveBeenCalled();
134
- });
135
- it('should discover tools via mcpServerCommand', async () => {
136
- const commandString = 'my-mcp-server --start';
137
- const parsedCommand = ['my-mcp-server', '--start'];
138
- mockConfig.getMcpServerCommand.mockReturnValue(commandString);
139
- vi.mocked(parse).mockReturnValue(parsedCommand);
140
- const mockTool = {
141
- name: 'tool1',
142
- description: 'desc1',
143
- inputSchema: { type: 'object', properties: {} },
144
- };
145
- vi.mocked(Client.prototype.listTools).mockResolvedValue({
146
- tools: [mockTool],
147
- });
148
- // PRE-MOCK getToolsByServer for the expected server name
149
- // In this case, listTools fails, so no tools are registered.
150
- // The default mock `mockReturnValue([])` from beforeEach should apply.
151
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
152
- expect(parse).toHaveBeenCalledWith(commandString, process.env);
153
- expect(StdioClientTransport).toHaveBeenCalledWith({
154
- command: parsedCommand[0],
155
- args: parsedCommand.slice(1),
156
- env: expect.any(Object),
157
- cwd: undefined,
158
- stderr: 'pipe',
159
- });
160
- expect(Client.prototype.connect).toHaveBeenCalledTimes(1);
161
- expect(Client.prototype.listTools).toHaveBeenCalledTimes(1);
162
- expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(1);
163
- expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.any(DiscoveredMCPTool));
164
- const registeredTool = mockToolRegistry.registerTool.mock
165
- .calls[0][0];
166
- expect(registeredTool.name).toBe('tool1');
167
- expect(registeredTool.serverToolName).toBe('tool1');
168
- });
169
- it('should discover tools via mcpServers config (stdio)', async () => {
170
- const serverConfig = {
171
- command: './mcp-stdio',
172
- args: ['arg1'],
173
- };
174
- mockConfig.getMcpServers.mockReturnValue({ 'stdio-server': serverConfig });
175
- const mockTool = {
176
- name: 'tool-stdio',
177
- description: 'desc-stdio',
178
- inputSchema: { type: 'object', properties: {} },
179
- };
180
- vi.mocked(Client.prototype.listTools).mockResolvedValue({
181
- tools: [mockTool],
182
- });
183
- // PRE-MOCK getToolsByServer for the expected server name
184
- mockToolRegistry.getToolsByServer.mockReturnValueOnce([
185
- expect.any(DiscoveredMCPTool),
186
- ]);
187
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
188
- expect(StdioClientTransport).toHaveBeenCalledWith({
189
- command: serverConfig.command,
190
- args: serverConfig.args,
191
- env: expect.any(Object),
192
- cwd: undefined,
193
- stderr: 'pipe',
194
- });
195
- expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.any(DiscoveredMCPTool));
196
- const registeredTool = mockToolRegistry.registerTool.mock
197
- .calls[0][0];
198
- expect(registeredTool.name).toBe('tool-stdio');
199
- });
200
- it('should discover tools via mcpServers config (sse)', async () => {
201
- const serverConfig = { url: 'http://localhost:1234/sse' };
202
- mockConfig.getMcpServers.mockReturnValue({ 'sse-server': serverConfig });
203
- const mockTool = {
204
- name: 'tool-sse',
205
- description: 'desc-sse',
206
- inputSchema: { type: 'object', properties: {} },
207
- };
208
- vi.mocked(Client.prototype.listTools).mockResolvedValue({
209
- tools: [mockTool],
210
- });
211
- // PRE-MOCK getToolsByServer for the expected server name
212
- mockToolRegistry.getToolsByServer.mockReturnValueOnce([
213
- expect.any(DiscoveredMCPTool),
214
- ]);
215
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
216
- expect(SSEClientTransport).toHaveBeenCalledWith(new URL(serverConfig.url), {});
217
- expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.any(DiscoveredMCPTool));
218
- const registeredTool = mockToolRegistry.registerTool.mock
219
- .calls[0][0];
220
- expect(registeredTool.name).toBe('tool-sse');
221
- });
222
- describe('SseClientTransport headers', () => {
223
- const setupSseTest = async (headers) => {
224
- const serverConfig = {
225
- url: 'http://localhost:1234/sse',
226
- ...(headers && { headers }),
227
- };
228
- const serverName = headers
229
- ? 'sse-server-with-headers'
230
- : 'sse-server-no-headers';
231
- const toolName = headers ? 'tool-http-headers' : 'tool-http-no-headers';
232
- mockConfig.getMcpServers.mockReturnValue({ [serverName]: serverConfig });
233
- const mockTool = {
234
- name: toolName,
235
- description: `desc-${toolName}`,
236
- inputSchema: { type: 'object', properties: {} },
237
- };
238
- vi.mocked(Client.prototype.listTools).mockResolvedValue({
239
- tools: [mockTool],
23
+ describe('discoverTools', () => {
24
+ it('should discover tools', async () => {
25
+ const mockedClient = {};
26
+ const mockedMcpToTool = vi.mocked(GenAiLib.mcpToTool).mockReturnValue({
27
+ tool: () => ({
28
+ functionDeclarations: [
29
+ {
30
+ name: 'testFunction',
31
+ },
32
+ ],
33
+ }),
240
34
  });
241
- mockToolRegistry.getToolsByServer.mockReturnValueOnce([
242
- expect.any(DiscoveredMCPTool),
243
- ]);
244
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
245
- return { serverConfig };
246
- };
247
- it('should pass headers when provided', async () => {
248
- const headers = {
249
- Authorization: 'Bearer test-token',
250
- 'X-Custom-Header': 'custom-value',
251
- };
252
- const { serverConfig } = await setupSseTest(headers);
253
- expect(SSEClientTransport).toHaveBeenCalledWith(new URL(serverConfig.url), { requestInit: { headers } });
254
- });
255
- it('should work without headers (backwards compatibility)', async () => {
256
- const { serverConfig } = await setupSseTest();
257
- expect(SSEClientTransport).toHaveBeenCalledWith(new URL(serverConfig.url), {});
258
- });
259
- it('should pass oauth token when provided', async () => {
260
- const headers = {
261
- Authorization: 'Bearer test-token',
262
- };
263
- const { serverConfig } = await setupSseTest(headers);
264
- expect(SSEClientTransport).toHaveBeenCalledWith(new URL(serverConfig.url), { requestInit: { headers } });
265
- });
266
- });
267
- it('should discover tools via mcpServers config (streamable http)', async () => {
268
- const serverConfig = {
269
- httpUrl: 'http://localhost:3000/mcp',
270
- };
271
- mockConfig.getMcpServers.mockReturnValue({ 'http-server': serverConfig });
272
- const mockTool = {
273
- name: 'tool-http',
274
- description: 'desc-http',
275
- inputSchema: { type: 'object', properties: {} },
276
- };
277
- vi.mocked(Client.prototype.listTools).mockResolvedValue({
278
- tools: [mockTool],
279
- });
280
- mockToolRegistry.getToolsByServer.mockReturnValueOnce([
281
- expect.any(DiscoveredMCPTool),
282
- ]);
283
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
284
- expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL(serverConfig.httpUrl), {});
285
- expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.any(DiscoveredMCPTool));
286
- const registeredTool = mockToolRegistry.registerTool.mock
287
- .calls[0][0];
288
- expect(registeredTool.name).toBe('tool-http');
289
- });
290
- describe('StreamableHTTPClientTransport headers', () => {
291
- const setupHttpTest = async (headers) => {
292
- const serverConfig = {
293
- httpUrl: 'http://localhost:3000/mcp',
294
- ...(headers && { headers }),
295
- };
296
- const serverName = headers
297
- ? 'http-server-with-headers'
298
- : 'http-server-no-headers';
299
- const toolName = headers ? 'tool-http-headers' : 'tool-http-no-headers';
300
- mockConfig.getMcpServers.mockReturnValue({ [serverName]: serverConfig });
301
- const mockTool = {
302
- name: toolName,
303
- description: `desc-${toolName}`,
304
- inputSchema: { type: 'object', properties: {} },
305
- };
306
- vi.mocked(Client.prototype.listTools).mockResolvedValue({
307
- tools: [mockTool],
35
+ const tools = await discoverTools('test-server', {}, mockedClient);
36
+ expect(tools.length).toBe(1);
37
+ expect(mockedMcpToTool).toHaveBeenCalledOnce();
38
+ });
39
+ });
40
+ describe('appendMcpServerCommand', () => {
41
+ it('should do nothing if no MCP servers or command are configured', () => {
42
+ const out = populateMcpServerCommand({}, undefined);
43
+ expect(out).toEqual({});
44
+ });
45
+ it('should discover tools via mcpServerCommand', () => {
46
+ const commandString = 'command --arg1 value1';
47
+ const out = populateMcpServerCommand({}, commandString);
48
+ expect(out).toEqual({
49
+ mcp: {
50
+ command: 'command',
51
+ args: ['--arg1', 'value1'],
52
+ },
308
53
  });
309
- mockToolRegistry.getToolsByServer.mockReturnValueOnce([
310
- expect.any(DiscoveredMCPTool),
311
- ]);
312
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
313
- return { serverConfig };
314
- };
315
- it('should pass headers when provided', async () => {
316
- const headers = {
317
- Authorization: 'Bearer test-token',
318
- 'X-Custom-Header': 'custom-value',
319
- };
320
- const { serverConfig } = await setupHttpTest(headers);
321
- expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL(serverConfig.httpUrl), { requestInit: { headers } });
322
54
  });
323
- it('should work without headers (backwards compatibility)', async () => {
324
- const { serverConfig } = await setupHttpTest();
325
- expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL(serverConfig.httpUrl), {});
326
- });
327
- it('should pass oauth token when provided', async () => {
328
- const headers = {
329
- Authorization: 'Bearer test-token',
330
- };
331
- const { serverConfig } = await setupHttpTest(headers);
332
- expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL(serverConfig.httpUrl), { requestInit: { headers } });
55
+ it('should handle error if mcpServerCommand parsing fails', () => {
56
+ expect(() => populateMcpServerCommand({}, 'derp && herp')).toThrowError();
333
57
  });
334
58
  });
335
- it('should prefix tool names if multiple MCP servers are configured', async () => {
336
- const serverConfig1 = { command: './mcp1' };
337
- const serverConfig2 = { url: 'http://mcp2/sse' };
338
- mockConfig.getMcpServers.mockReturnValue({
339
- server1: serverConfig1,
340
- server2: serverConfig2,
341
- });
342
- const mockTool1 = {
343
- name: 'toolA', // Same original name
344
- description: 'd1',
345
- inputSchema: { type: 'object', properties: {} },
346
- };
347
- const mockTool2 = {
348
- name: 'toolA', // Same original name
349
- description: 'd2',
350
- inputSchema: { type: 'object', properties: {} },
351
- };
352
- const mockToolB = {
353
- name: 'toolB',
354
- description: 'dB',
355
- inputSchema: { type: 'object', properties: {} },
356
- };
357
- vi.mocked(Client.prototype.listTools)
358
- .mockResolvedValueOnce({ tools: [mockTool1, mockToolB] }) // Tools for server1
359
- .mockResolvedValueOnce({ tools: [mockTool2] }); // Tool for server2 (toolA)
360
- const effectivelyRegisteredTools = new Map();
361
- mockToolRegistry.getTool.mockImplementation((toolName) => effectivelyRegisteredTools.get(toolName));
362
- // Store the original spy implementation if needed, or just let the new one be the behavior.
363
- // The mockToolRegistry.registerTool is already a vi.fn() from mockToolRegistryInstance.
364
- // We are setting its behavior for this test.
365
- mockToolRegistry.registerTool.mockImplementation((toolToRegister) => {
366
- // Simulate the actual registration name being stored for getTool to find
367
- effectivelyRegisteredTools.set(toolToRegister.name, toolToRegister);
368
- // If it's the first time toolA is registered (from server1, not prefixed),
369
- // also make it findable by its original name for the prefixing check of server2/toolA.
370
- if (toolToRegister.serverName === 'server1' &&
371
- toolToRegister.serverToolName === 'toolA' &&
372
- toolToRegister.name === 'toolA') {
373
- effectivelyRegisteredTools.set('toolA', toolToRegister);
374
- }
375
- // The spy call count is inherently tracked by mockToolRegistry.registerTool itself.
59
+ describe('createTransport', () => {
60
+ const originalEnv = process.env;
61
+ beforeEach(() => {
62
+ vi.resetModules();
63
+ process.env = {};
64
+ });
65
+ afterEach(() => {
66
+ process.env = originalEnv;
67
+ });
68
+ describe('should connect via httpUrl', () => {
69
+ it('without headers', async () => {
70
+ const transport = await createTransport('test-server', {
71
+ httpUrl: 'http://test-server',
72
+ }, false);
73
+ expect(transport).toEqual(new StreamableHTTPClientTransport(new URL('http://test-server'), {}));
74
+ });
75
+ it('with headers', async () => {
76
+ const transport = await createTransport('test-server', {
77
+ httpUrl: 'http://test-server',
78
+ headers: { Authorization: 'derp' },
79
+ }, false);
80
+ expect(transport).toEqual(new StreamableHTTPClientTransport(new URL('http://test-server'), {
81
+ requestInit: {
82
+ headers: { Authorization: 'derp' },
83
+ },
84
+ }));
85
+ });
376
86
  });
377
- // PRE-MOCK getToolsByServer for the expected server names
378
- // This is for the final check in connectAndDiscover to see if any tools were registered *from that server*
379
- mockToolRegistry.getToolsByServer.mockImplementation((serverName) => {
380
- if (serverName === 'server1')
381
- return [
382
- expect.objectContaining({ name: 'toolA' }),
383
- expect.objectContaining({ name: 'toolB' }),
384
- ];
385
- if (serverName === 'server2')
386
- return [expect.objectContaining({ name: 'server2__toolA' })];
387
- return [];
87
+ describe('should connect via url', () => {
88
+ it('without headers', async () => {
89
+ const transport = await createTransport('test-server', {
90
+ url: 'http://test-server',
91
+ }, false);
92
+ expect(transport).toEqual(new SSEClientTransport(new URL('http://test-server'), {}));
93
+ });
94
+ it('with headers', async () => {
95
+ const transport = await createTransport('test-server', {
96
+ url: 'http://test-server',
97
+ headers: { Authorization: 'derp' },
98
+ }, false);
99
+ expect(transport).toEqual(new SSEClientTransport(new URL('http://test-server'), {
100
+ requestInit: {
101
+ headers: { Authorization: 'derp' },
102
+ },
103
+ }));
104
+ });
388
105
  });
389
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
390
- expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(3);
391
- const registeredArgs = mockToolRegistry.registerTool.mock.calls.map((call) => call[0]);
392
- // The order of server processing by Promise.all is not guaranteed.
393
- // One 'toolA' will be unprefixed, the other will be prefixed.
394
- const toolA_from_server1 = registeredArgs.find((t) => t.serverToolName === 'toolA' && t.serverName === 'server1');
395
- const toolA_from_server2 = registeredArgs.find((t) => t.serverToolName === 'toolA' && t.serverName === 'server2');
396
- const toolB_from_server1 = registeredArgs.find((t) => t.serverToolName === 'toolB' && t.serverName === 'server1');
397
- expect(toolA_from_server1).toBeDefined();
398
- expect(toolA_from_server2).toBeDefined();
399
- expect(toolB_from_server1).toBeDefined();
400
- expect(toolB_from_server1?.name).toBe('toolB'); // toolB is unique
401
- // Check that one of toolA is prefixed and the other is not, and the prefixed one is correct.
402
- if (toolA_from_server1?.name === 'toolA') {
403
- expect(toolA_from_server2?.name).toBe('server2__toolA');
404
- }
405
- else {
406
- expect(toolA_from_server1?.name).toBe('server1__toolA');
407
- expect(toolA_from_server2?.name).toBe('toolA');
408
- }
409
- });
410
- it('should clean schema properties ($schema, additionalProperties)', async () => {
411
- const serverConfig = { command: './mcp-clean' };
412
- mockConfig.getMcpServers.mockReturnValue({ 'clean-server': serverConfig });
413
- const rawSchema = {
414
- type: 'object',
415
- $schema: 'http://json-schema.org/draft-07/schema#',
416
- additionalProperties: true,
417
- properties: {
418
- prop1: { type: 'string', $schema: 'remove-this' },
419
- prop2: {
420
- type: 'object',
421
- additionalProperties: false,
422
- properties: { nested: { type: 'number' } },
423
- },
424
- },
425
- };
426
- const mockTool = {
427
- name: 'cleanTool',
428
- description: 'd',
429
- inputSchema: JSON.parse(JSON.stringify(rawSchema)),
430
- };
431
- vi.mocked(Client.prototype.listTools).mockResolvedValue({
432
- tools: [mockTool],
106
+ it('should connect via command', async () => {
107
+ const mockedTransport = vi.mocked(SdkClientStdioLib.StdioClientTransport);
108
+ await createTransport('test-server', {
109
+ command: 'test-command',
110
+ args: ['--foo', 'bar'],
111
+ env: { FOO: 'bar' },
112
+ cwd: 'test/cwd',
113
+ }, false);
114
+ expect(mockedTransport).toHaveBeenCalledWith({
115
+ command: 'test-command',
116
+ args: ['--foo', 'bar'],
117
+ cwd: 'test/cwd',
118
+ env: { FOO: 'bar' },
119
+ stderr: 'pipe',
120
+ });
433
121
  });
434
- // PRE-MOCK getToolsByServer for the expected server name
435
- mockToolRegistry.getToolsByServer.mockReturnValueOnce([
436
- expect.any(DiscoveredMCPTool),
437
- ]);
438
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
439
- expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(1);
440
- const registeredTool = mockToolRegistry.registerTool.mock
441
- .calls[0][0];
442
- const cleanedParams = registeredTool.schema.parameters;
443
- expect(cleanedParams).not.toHaveProperty('$schema');
444
- expect(cleanedParams).not.toHaveProperty('additionalProperties');
445
- expect(cleanedParams.properties.prop1).not.toHaveProperty('$schema');
446
- expect(cleanedParams.properties.prop2).not.toHaveProperty('additionalProperties');
447
- expect(cleanedParams.properties.prop2.properties.nested).not.toHaveProperty('$schema');
448
- expect(cleanedParams.properties.prop2.properties.nested).not.toHaveProperty('additionalProperties');
449
- });
450
- it('should handle error if mcpServerCommand parsing fails', async () => {
451
- const commandString = 'my-mcp-server "unterminated quote';
452
- mockConfig.getMcpServerCommand.mockReturnValue(commandString);
453
- vi.mocked(parse).mockImplementation(() => {
454
- throw new Error('Parsing failed');
122
+ describe('useGoogleCredentialProvider', () => {
123
+ it('should use GoogleCredentialProvider when specified', async () => {
124
+ const transport = await createTransport('test-server', {
125
+ httpUrl: 'http://test-server',
126
+ authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
127
+ oauth: {
128
+ scopes: ['scope1'],
129
+ },
130
+ }, false);
131
+ expect(transport).toBeInstanceOf(StreamableHTTPClientTransport);
132
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
133
+ const authProvider = transport._authProvider;
134
+ expect(authProvider).toBeInstanceOf(GoogleCredentialProvider);
135
+ });
136
+ it('should use GoogleCredentialProvider with SSE transport', async () => {
137
+ const transport = await createTransport('test-server', {
138
+ url: 'http://test-server',
139
+ authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
140
+ oauth: {
141
+ scopes: ['scope1'],
142
+ },
143
+ }, false);
144
+ expect(transport).toBeInstanceOf(SSEClientTransport);
145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
+ const authProvider = transport._authProvider;
147
+ expect(authProvider).toBeInstanceOf(GoogleCredentialProvider);
148
+ });
149
+ it('should throw an error if no URL is provided with GoogleCredentialProvider', async () => {
150
+ await expect(createTransport('test-server', {
151
+ authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
152
+ oauth: {
153
+ scopes: ['scope1'],
154
+ },
155
+ }, false)).rejects.toThrow('No URL configured for Google Credentials MCP server');
156
+ });
455
157
  });
456
- vi.spyOn(console, 'error').mockImplementation(() => { });
457
- await expect(discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry)).rejects.toThrow('Parsing failed');
458
- expect(mockToolRegistry.registerTool).not.toHaveBeenCalled();
459
- expect(console.error).not.toHaveBeenCalled();
460
- });
461
- it('should log error and skip server if config is invalid (missing url and command)', async () => {
462
- mockConfig.getMcpServers.mockReturnValue({ 'bad-server': {} });
463
- vi.spyOn(console, 'error').mockImplementation(() => { });
464
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
465
- expect(console.error).toHaveBeenCalledWith(expect.stringContaining("MCP server 'bad-server' has invalid configuration"));
466
- // Client constructor should not be called if config is invalid before instantiation
467
- expect(Client).not.toHaveBeenCalled();
468
158
  });
469
- it('should log error and skip server if mcpClient.connect fails', async () => {
470
- const serverConfig = { command: './mcp-fail-connect' };
471
- mockConfig.getMcpServers.mockReturnValue({
472
- 'fail-connect-server': serverConfig,
159
+ describe('isEnabled', () => {
160
+ const funcDecl = { name: 'myTool' };
161
+ const serverName = 'myServer';
162
+ it('should return true if no include or exclude lists are provided', () => {
163
+ const mcpServerConfig = {};
164
+ expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
473
165
  });
474
- vi.mocked(Client.prototype.connect).mockRejectedValue(new Error('Connection refused'));
475
- vi.spyOn(console, 'error').mockImplementation(() => { });
476
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
477
- expect(console.error).toHaveBeenCalledWith(expect.stringContaining("failed to start or connect to MCP server 'fail-connect-server'"));
478
- expect(Client.prototype.listTools).not.toHaveBeenCalled();
479
- expect(mockToolRegistry.registerTool).not.toHaveBeenCalled();
480
- });
481
- it('should log error and skip server if mcpClient.listTools fails', async () => {
482
- const serverConfig = { command: './mcp-fail-list' };
483
- mockConfig.getMcpServers.mockReturnValue({
484
- 'fail-list-server': serverConfig,
166
+ it('should return false if the tool is in the exclude list', () => {
167
+ const mcpServerConfig = { excludeTools: ['myTool'] };
168
+ expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
485
169
  });
486
- vi.mocked(Client.prototype.listTools).mockRejectedValue(new Error('ListTools error'));
487
- vi.spyOn(console, 'error').mockImplementation(() => { });
488
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
489
- expect(console.error).toHaveBeenCalledWith(expect.stringContaining("Failed to list or register tools for MCP server 'fail-list-server'"));
490
- expect(mockToolRegistry.registerTool).not.toHaveBeenCalled();
491
- });
492
- it('should assign mcpClient.onerror handler', async () => {
493
- const serverConfig = { command: './mcp-onerror' };
494
- mockConfig.getMcpServers.mockReturnValue({
495
- 'onerror-server': serverConfig,
170
+ it('should return true if the tool is in the include list', () => {
171
+ const mcpServerConfig = { includeTools: ['myTool'] };
172
+ expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
496
173
  });
497
- // PRE-MOCK getToolsByServer for the expected server name
498
- mockToolRegistry.getToolsByServer.mockReturnValueOnce([
499
- expect.any(DiscoveredMCPTool),
500
- ]);
501
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
502
- const clientInstances = vi.mocked(Client).mock.results;
503
- expect(clientInstances.length).toBeGreaterThan(0);
504
- const lastClientInstance = clientInstances[clientInstances.length - 1]?.value;
505
- expect(lastClientInstance?.onerror).toEqual(expect.any(Function));
506
- });
507
- describe('Tool Filtering', () => {
508
- const mockTools = [
509
- {
510
- name: 'toolA',
511
- description: 'descA',
512
- inputSchema: { type: 'object', properties: {} },
513
- },
514
- {
515
- name: 'toolB',
516
- description: 'descB',
517
- inputSchema: { type: 'object', properties: {} },
518
- },
519
- {
520
- name: 'toolC',
521
- description: 'descC',
522
- inputSchema: { type: 'object', properties: {} },
523
- },
524
- ];
525
- beforeEach(() => {
526
- vi.mocked(Client.prototype.listTools).mockResolvedValue({
527
- tools: mockTools,
528
- });
529
- mockToolRegistry.getToolsByServer.mockReturnValue([
530
- expect.any(DiscoveredMCPTool),
531
- ]);
174
+ it('should return true if the tool is in the include list with parentheses', () => {
175
+ const mcpServerConfig = { includeTools: ['myTool()'] };
176
+ expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(true);
532
177
  });
533
- it('should only include specified tools with includeTools', async () => {
534
- const serverConfig = {
535
- command: './mcp-include',
536
- includeTools: ['toolA', 'toolC'],
537
- };
538
- mockConfig.getMcpServers.mockReturnValue({
539
- 'include-server': serverConfig,
540
- });
541
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
542
- expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(2);
543
- expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolA' }));
544
- expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolC' }));
545
- expect(mockToolRegistry.registerTool).not.toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolB' }));
178
+ it('should return false if the include list exists but does not contain the tool', () => {
179
+ const mcpServerConfig = { includeTools: ['anotherTool'] };
180
+ expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
546
181
  });
547
- it('should exclude specified tools with excludeTools', async () => {
548
- const serverConfig = {
549
- command: './mcp-exclude',
550
- excludeTools: ['toolB'],
182
+ it('should return false if the tool is in both the include and exclude lists', () => {
183
+ const mcpServerConfig = {
184
+ includeTools: ['myTool'],
185
+ excludeTools: ['myTool'],
551
186
  };
552
- mockConfig.getMcpServers.mockReturnValue({
553
- 'exclude-server': serverConfig,
554
- });
555
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
556
- expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(2);
557
- expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolA' }));
558
- expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolC' }));
559
- expect(mockToolRegistry.registerTool).not.toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolB' }));
187
+ expect(isEnabled(funcDecl, serverName, mcpServerConfig)).toBe(false);
560
188
  });
561
- it('should handle both includeTools and excludeTools', async () => {
562
- const serverConfig = {
563
- command: './mcp-both',
564
- includeTools: ['toolA', 'toolB'],
565
- excludeTools: ['toolB'],
566
- };
567
- mockConfig.getMcpServers.mockReturnValue({ 'both-server': serverConfig });
568
- await discoverMcpTools(mockConfig.getMcpServers() ?? {}, mockConfig.getMcpServerCommand(), mockToolRegistry);
569
- expect(mockToolRegistry.registerTool).toHaveBeenCalledTimes(1);
570
- expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolA' }));
571
- expect(mockToolRegistry.registerTool).not.toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolB' }));
572
- expect(mockToolRegistry.registerTool).not.toHaveBeenCalledWith(expect.objectContaining({ serverToolName: 'toolC' }));
189
+ it('should return false if the function declaration has no name', () => {
190
+ const namelessFuncDecl = {};
191
+ const mcpServerConfig = {};
192
+ expect(isEnabled(namelessFuncDecl, serverName, mcpServerConfig)).toBe(false);
573
193
  });
574
194
  });
575
195
  });
576
- describe('sanitizeParameters', () => {
577
- it('should do nothing for an undefined schema', () => {
578
- const schema = undefined;
579
- sanitizeParameters(schema);
580
- });
581
- it('should remove default when anyOf is present', () => {
582
- const schema = {
583
- anyOf: [{ type: Type.STRING }, { type: Type.NUMBER }],
584
- default: 'hello',
585
- };
586
- sanitizeParameters(schema);
587
- expect(schema.default).toBeUndefined();
588
- });
589
- it('should recursively sanitize items in anyOf', () => {
590
- const schema = {
591
- anyOf: [
592
- {
593
- anyOf: [{ type: Type.STRING }],
594
- default: 'world',
595
- },
596
- { type: Type.NUMBER },
597
- ],
598
- };
599
- sanitizeParameters(schema);
600
- expect(schema.anyOf[0].default).toBeUndefined();
601
- });
602
- it('should recursively sanitize items in items', () => {
603
- const schema = {
604
- items: {
605
- anyOf: [{ type: Type.STRING }],
606
- default: 'world',
607
- },
608
- };
609
- sanitizeParameters(schema);
610
- expect(schema.items.default).toBeUndefined();
611
- });
612
- it('should recursively sanitize items in properties', () => {
613
- const schema = {
614
- properties: {
615
- prop1: {
616
- anyOf: [{ type: Type.STRING }],
617
- default: 'world',
618
- },
619
- },
620
- };
621
- sanitizeParameters(schema);
622
- expect(schema.properties.prop1.default).toBeUndefined();
623
- });
624
- it('should handle complex nested schemas', () => {
625
- const schema = {
626
- properties: {
627
- prop1: {
628
- items: {
629
- anyOf: [{ type: Type.STRING }],
630
- default: 'world',
631
- },
632
- },
633
- prop2: {
634
- anyOf: [
635
- {
636
- properties: {
637
- nestedProp: {
638
- anyOf: [{ type: Type.NUMBER }],
639
- default: 123,
640
- },
641
- },
642
- },
643
- ],
644
- },
645
- },
646
- };
647
- sanitizeParameters(schema);
648
- expect(schema.properties.prop1.items.default).toBeUndefined();
649
- const nestedProp = schema.properties.prop2.anyOf[0].properties.nestedProp;
650
- expect(nestedProp?.default).toBeUndefined();
651
- });
652
- });
653
196
  //# sourceMappingURL=mcp-client.test.js.map