@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
@@ -8,9 +8,14 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
8
8
  import { SSEClientTransport, } from '@modelcontextprotocol/sdk/client/sse.js';
9
9
  import { StreamableHTTPClientTransport, } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
10
10
  import { parse } from 'shell-quote';
11
+ import { AuthProviderType } from '../config/config.js';
12
+ import { GoogleCredentialProvider } from '../mcp/google-auth-provider.js';
11
13
  import { DiscoveredMCPTool } from './mcp-tool.js';
12
- import { Type, mcpToTool } from '@google/genai';
13
- import { sanitizeParameters } from './tool-registry.js';
14
+ import { mcpToTool } from '@google/genai';
15
+ import { MCPOAuthProvider } from '../mcp/oauth-provider.js';
16
+ import { OAuthUtils } from '../mcp/oauth-utils.js';
17
+ import { MCPOAuthTokenStorage } from '../mcp/oauth-token-storage.js';
18
+ import { getErrorMessage } from '../utils/errors.js';
14
19
  export const MCP_DEFAULT_TIMEOUT_MSEC = 10 * 60 * 1000; // default to 10 minutes
15
20
  /**
16
21
  * Enum representing the connection status of an MCP server
@@ -44,6 +49,10 @@ const mcpServerStatusesInternal = new Map();
44
49
  * Track the overall MCP discovery state
45
50
  */
46
51
  let mcpDiscoveryState = MCPDiscoveryState.NOT_STARTED;
52
+ /**
53
+ * Map to track which MCP servers have been discovered to require OAuth
54
+ */
55
+ export const mcpServerRequiresOAuth = new Map();
47
56
  const statusChangeListeners = [];
48
57
  /**
49
58
  * Add a listener for MCP server status changes
@@ -88,33 +97,169 @@ export function getAllMCPServerStatuses() {
88
97
  export function getMCPDiscoveryState() {
89
98
  return mcpDiscoveryState;
90
99
  }
91
- export async function discoverMcpTools(mcpServers, mcpServerCommand, toolRegistry) {
92
- // Set discovery state to in progress
93
- mcpDiscoveryState = MCPDiscoveryState.IN_PROGRESS;
100
+ /**
101
+ * Parse www-authenticate header to extract OAuth metadata URI.
102
+ *
103
+ * @param wwwAuthenticate The www-authenticate header value
104
+ * @returns The resource metadata URI if found, null otherwise
105
+ */
106
+ function _parseWWWAuthenticate(wwwAuthenticate) {
107
+ // Parse header like: Bearer realm="MCP Server", resource_metadata_uri="https://..."
108
+ const resourceMetadataMatch = wwwAuthenticate.match(/resource_metadata_uri="([^"]+)"/);
109
+ return resourceMetadataMatch ? resourceMetadataMatch[1] : null;
110
+ }
111
+ /**
112
+ * Extract WWW-Authenticate header from error message string.
113
+ * This is a more robust approach than regex matching.
114
+ *
115
+ * @param errorString The error message string
116
+ * @returns The www-authenticate header value if found, null otherwise
117
+ */
118
+ function extractWWWAuthenticateHeader(errorString) {
119
+ // Try multiple patterns to extract the header
120
+ const patterns = [
121
+ /www-authenticate:\s*([^\n\r]+)/i,
122
+ /WWW-Authenticate:\s*([^\n\r]+)/i,
123
+ /"www-authenticate":\s*"([^"]+)"/i,
124
+ /'www-authenticate':\s*'([^']+)'/i,
125
+ ];
126
+ for (const pattern of patterns) {
127
+ const match = errorString.match(pattern);
128
+ if (match) {
129
+ return match[1].trim();
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+ /**
135
+ * Handle automatic OAuth discovery and authentication for a server.
136
+ *
137
+ * @param mcpServerName The name of the MCP server
138
+ * @param mcpServerConfig The MCP server configuration
139
+ * @param wwwAuthenticate The www-authenticate header value
140
+ * @returns True if OAuth was successfully configured and authenticated, false otherwise
141
+ */
142
+ async function handleAutomaticOAuth(mcpServerName, mcpServerConfig, wwwAuthenticate) {
94
143
  try {
95
- if (mcpServerCommand) {
96
- const cmd = mcpServerCommand;
97
- const args = parse(cmd, process.env);
98
- if (args.some((arg) => typeof arg !== 'string')) {
99
- throw new Error('failed to parse mcpServerCommand: ' + cmd);
100
- }
101
- // use generic server name 'mcp'
102
- mcpServers['mcp'] = {
103
- command: args[0],
104
- args: args.slice(1),
144
+ console.log(`🔐 '${mcpServerName}' requires OAuth authentication`);
145
+ // Always try to parse the resource metadata URI from the www-authenticate header
146
+ let oauthConfig;
147
+ const resourceMetadataUri = OAuthUtils.parseWWWAuthenticateHeader(wwwAuthenticate);
148
+ if (resourceMetadataUri) {
149
+ oauthConfig = await OAuthUtils.discoverOAuthConfig(resourceMetadataUri);
150
+ }
151
+ else if (mcpServerConfig.url) {
152
+ // Fallback: try to discover OAuth config from the base URL for SSE
153
+ const sseUrl = new URL(mcpServerConfig.url);
154
+ const baseUrl = `${sseUrl.protocol}//${sseUrl.host}`;
155
+ oauthConfig = await OAuthUtils.discoverOAuthConfig(baseUrl);
156
+ }
157
+ else if (mcpServerConfig.httpUrl) {
158
+ // Fallback: try to discover OAuth config from the base URL for HTTP
159
+ const httpUrl = new URL(mcpServerConfig.httpUrl);
160
+ const baseUrl = `${httpUrl.protocol}//${httpUrl.host}`;
161
+ oauthConfig = await OAuthUtils.discoverOAuthConfig(baseUrl);
162
+ }
163
+ if (!oauthConfig) {
164
+ console.error(`❌ Could not configure OAuth for '${mcpServerName}' - please authenticate manually with /mcp auth ${mcpServerName}`);
165
+ return false;
166
+ }
167
+ // OAuth configuration discovered - proceed with authentication
168
+ // Create OAuth configuration for authentication
169
+ const oauthAuthConfig = {
170
+ enabled: true,
171
+ authorizationUrl: oauthConfig.authorizationUrl,
172
+ tokenUrl: oauthConfig.tokenUrl,
173
+ scopes: oauthConfig.scopes || [],
174
+ };
175
+ // Perform OAuth authentication
176
+ console.log(`Starting OAuth authentication for server '${mcpServerName}'...`);
177
+ await MCPOAuthProvider.authenticate(mcpServerName, oauthAuthConfig);
178
+ console.log(`OAuth authentication successful for server '${mcpServerName}'`);
179
+ return true;
180
+ }
181
+ catch (error) {
182
+ console.error(`Failed to handle automatic OAuth for server '${mcpServerName}': ${getErrorMessage(error)}`);
183
+ return false;
184
+ }
185
+ }
186
+ /**
187
+ * Create a transport with OAuth token for the given server configuration.
188
+ *
189
+ * @param mcpServerName The name of the MCP server
190
+ * @param mcpServerConfig The MCP server configuration
191
+ * @param accessToken The OAuth access token
192
+ * @returns The transport with OAuth token, or null if creation fails
193
+ */
194
+ async function createTransportWithOAuth(mcpServerName, mcpServerConfig, accessToken) {
195
+ try {
196
+ if (mcpServerConfig.httpUrl) {
197
+ // Create HTTP transport with OAuth token
198
+ const oauthTransportOptions = {
199
+ requestInit: {
200
+ headers: {
201
+ ...mcpServerConfig.headers,
202
+ Authorization: `Bearer ${accessToken}`,
203
+ },
204
+ },
105
205
  };
206
+ return new StreamableHTTPClientTransport(new URL(mcpServerConfig.httpUrl), oauthTransportOptions);
106
207
  }
107
- const discoveryPromises = Object.entries(mcpServers).map(([mcpServerName, mcpServerConfig]) => connectAndDiscover(mcpServerName, mcpServerConfig, toolRegistry));
108
- await Promise.all(discoveryPromises);
109
- // Mark discovery as completed
110
- mcpDiscoveryState = MCPDiscoveryState.COMPLETED;
208
+ else if (mcpServerConfig.url) {
209
+ // Create SSE transport with OAuth token in Authorization header
210
+ return new SSEClientTransport(new URL(mcpServerConfig.url), {
211
+ requestInit: {
212
+ headers: {
213
+ ...mcpServerConfig.headers,
214
+ Authorization: `Bearer ${accessToken}`,
215
+ },
216
+ },
217
+ });
218
+ }
219
+ return null;
111
220
  }
112
221
  catch (error) {
113
- // Still mark as completed even with errors
222
+ console.error(`Failed to create OAuth transport for server '${mcpServerName}': ${getErrorMessage(error)}`);
223
+ return null;
224
+ }
225
+ }
226
+ /**
227
+ * Discovers tools from all configured MCP servers and registers them with the tool registry.
228
+ * It orchestrates the connection and discovery process for each server defined in the
229
+ * configuration, as well as any server specified via a command-line argument.
230
+ *
231
+ * @param mcpServers A record of named MCP server configurations.
232
+ * @param mcpServerCommand An optional command string for a dynamically specified MCP server.
233
+ * @param toolRegistry The central registry where discovered tools will be registered.
234
+ * @returns A promise that resolves when the discovery process has been attempted for all servers.
235
+ */
236
+ export async function discoverMcpTools(mcpServers, mcpServerCommand, toolRegistry, debugMode) {
237
+ mcpDiscoveryState = MCPDiscoveryState.IN_PROGRESS;
238
+ try {
239
+ mcpServers = populateMcpServerCommand(mcpServers, mcpServerCommand);
240
+ const discoveryPromises = Object.entries(mcpServers).map(([mcpServerName, mcpServerConfig]) => connectAndDiscover(mcpServerName, mcpServerConfig, toolRegistry, debugMode));
241
+ await Promise.all(discoveryPromises);
242
+ }
243
+ finally {
114
244
  mcpDiscoveryState = MCPDiscoveryState.COMPLETED;
115
- throw error;
116
245
  }
117
246
  }
247
+ /** Visible for Testing */
248
+ export function populateMcpServerCommand(mcpServers, mcpServerCommand) {
249
+ if (mcpServerCommand) {
250
+ const cmd = mcpServerCommand;
251
+ const args = parse(cmd, process.env);
252
+ if (args.some((arg) => typeof arg !== 'string')) {
253
+ throw new Error('failed to parse mcpServerCommand: ' + cmd);
254
+ }
255
+ // use generic server name 'mcp'
256
+ mcpServers['mcp'] = {
257
+ command: args[0],
258
+ args: args.slice(1),
259
+ };
260
+ }
261
+ return mcpServers;
262
+ }
118
263
  /**
119
264
  * Connects to an MCP server and discovers available tools, registering them with the tool registry.
120
265
  * This function handles the complete lifecycle of connecting to a server, discovering tools,
@@ -125,46 +270,76 @@ export async function discoverMcpTools(mcpServers, mcpServerCommand, toolRegistr
125
270
  * @param toolRegistry The registry to register discovered tools with
126
271
  * @returns Promise that resolves when discovery is complete
127
272
  */
128
- async function connectAndDiscover(mcpServerName, mcpServerConfig, toolRegistry) {
129
- // Initialize the server status as connecting
273
+ export async function connectAndDiscover(mcpServerName, mcpServerConfig, toolRegistry, debugMode) {
130
274
  updateMCPServerStatus(mcpServerName, MCPServerStatus.CONNECTING);
131
- let transport;
132
- if (mcpServerConfig.httpUrl) {
133
- const transportOptions = {};
134
- if (mcpServerConfig.headers) {
135
- transportOptions.requestInit = {
136
- headers: mcpServerConfig.headers,
275
+ try {
276
+ const mcpClient = await connectToMcpServer(mcpServerName, mcpServerConfig, debugMode);
277
+ try {
278
+ updateMCPServerStatus(mcpServerName, MCPServerStatus.CONNECTED);
279
+ mcpClient.onerror = (error) => {
280
+ console.error(`MCP ERROR (${mcpServerName}):`, error.toString());
281
+ updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED);
137
282
  };
283
+ const tools = await discoverTools(mcpServerName, mcpServerConfig, mcpClient);
284
+ for (const tool of tools) {
285
+ toolRegistry.registerTool(tool);
286
+ }
138
287
  }
139
- transport = new StreamableHTTPClientTransport(new URL(mcpServerConfig.httpUrl), transportOptions);
140
- }
141
- else if (mcpServerConfig.url) {
142
- const transportOptions = {};
143
- if (mcpServerConfig.headers) {
144
- transportOptions.requestInit = {
145
- headers: mcpServerConfig.headers,
146
- };
288
+ catch (error) {
289
+ mcpClient.close();
290
+ throw error;
147
291
  }
148
- transport = new SSEClientTransport(new URL(mcpServerConfig.url), transportOptions);
149
292
  }
150
- else if (mcpServerConfig.command) {
151
- transport = new StdioClientTransport({
152
- command: mcpServerConfig.command,
153
- args: mcpServerConfig.args || [],
154
- env: {
155
- ...process.env,
156
- ...(mcpServerConfig.env || {}),
157
- },
158
- cwd: mcpServerConfig.cwd,
159
- stderr: 'pipe',
160
- });
161
- }
162
- else {
163
- console.error(`MCP server '${mcpServerName}' has invalid configuration: missing httpUrl (for Streamable HTTP), url (for SSE), and command (for stdio). Skipping.`);
164
- // Update status to disconnected
293
+ catch (error) {
294
+ console.error(`Error connecting to MCP server '${mcpServerName}': ${getErrorMessage(error)}`);
165
295
  updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED);
166
- return;
167
296
  }
297
+ }
298
+ /**
299
+ * Discovers and sanitizes tools from a connected MCP client.
300
+ * It retrieves function declarations from the client, filters out disabled tools,
301
+ * generates valid names for them, and wraps them in `DiscoveredMCPTool` instances.
302
+ *
303
+ * @param mcpServerName The name of the MCP server.
304
+ * @param mcpServerConfig The configuration for the MCP server.
305
+ * @param mcpClient The active MCP client instance.
306
+ * @returns A promise that resolves to an array of discovered and enabled tools.
307
+ * @throws An error if no enabled tools are found or if the server provides invalid function declarations.
308
+ */
309
+ export async function discoverTools(mcpServerName, mcpServerConfig, mcpClient) {
310
+ try {
311
+ const mcpCallableTool = mcpToTool(mcpClient);
312
+ const tool = await mcpCallableTool.tool();
313
+ if (!Array.isArray(tool.functionDeclarations)) {
314
+ throw new Error(`Server did not return valid function declarations.`);
315
+ }
316
+ const discoveredTools = [];
317
+ for (const funcDecl of tool.functionDeclarations) {
318
+ if (!isEnabled(funcDecl, mcpServerName, mcpServerConfig)) {
319
+ continue;
320
+ }
321
+ discoveredTools.push(new DiscoveredMCPTool(mcpCallableTool, mcpServerName, funcDecl.name, funcDecl.description ?? '', funcDecl.parametersJsonSchema ?? { type: 'object', properties: {} }, mcpServerConfig.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC, mcpServerConfig.trust));
322
+ }
323
+ if (discoveredTools.length === 0) {
324
+ throw Error('No enabled tools found');
325
+ }
326
+ return discoveredTools;
327
+ }
328
+ catch (error) {
329
+ throw new Error(`Error discovering tools: ${error}`);
330
+ }
331
+ }
332
+ /**
333
+ * Creates and connects an MCP client to a server based on the provided configuration.
334
+ * It determines the appropriate transport (Stdio, SSE, or Streamable HTTP) and
335
+ * establishes a connection. It also applies a patch to handle request timeouts.
336
+ *
337
+ * @param mcpServerName The name of the MCP server, used for logging and identification.
338
+ * @param mcpServerConfig The configuration specifying how to connect to the server.
339
+ * @returns A promise that resolves to a connected MCP `Client` instance.
340
+ * @throws An error if the connection fails or the configuration is invalid.
341
+ */
342
+ export async function connectToMcpServer(mcpServerName, mcpServerConfig, debugMode) {
168
343
  const mcpClient = new Client({
169
344
  name: 'gemini-cli-mcp-client',
170
345
  version: '0.0.1',
@@ -181,114 +356,350 @@ async function connectAndDiscover(mcpServerName, mcpServerConfig, toolRegistry)
181
356
  };
182
357
  }
183
358
  try {
184
- await mcpClient.connect(transport, {
185
- timeout: mcpServerConfig.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC,
186
- });
187
- // Connection successful
188
- updateMCPServerStatus(mcpServerName, MCPServerStatus.CONNECTED);
189
- }
190
- catch (error) {
191
- // Create a safe config object that excludes sensitive information
192
- const safeConfig = {
193
- command: mcpServerConfig.command,
194
- url: mcpServerConfig.url,
195
- httpUrl: mcpServerConfig.httpUrl,
196
- cwd: mcpServerConfig.cwd,
197
- timeout: mcpServerConfig.timeout,
198
- trust: mcpServerConfig.trust,
199
- // Exclude args, env, and headers which may contain sensitive data
200
- };
201
- let errorString = `failed to start or connect to MCP server '${mcpServerName}' ` +
202
- `${JSON.stringify(safeConfig)}; \n${error}`;
203
- if (process.env.SANDBOX) {
204
- errorString += `\nMake sure it is available in the sandbox`;
359
+ const transport = await createTransport(mcpServerName, mcpServerConfig, debugMode);
360
+ try {
361
+ await mcpClient.connect(transport, {
362
+ timeout: mcpServerConfig.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC,
363
+ });
364
+ return mcpClient;
365
+ }
366
+ catch (error) {
367
+ await transport.close();
368
+ throw error;
205
369
  }
206
- console.error(errorString);
207
- // Update status to disconnected
208
- updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED);
209
- return;
210
370
  }
211
- mcpClient.onerror = (error) => {
212
- console.error(`MCP ERROR (${mcpServerName}):`, error.toString());
213
- // Update status to disconnected on error
214
- updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED);
215
- };
216
- try {
217
- const mcpCallableTool = mcpToTool(mcpClient);
218
- const tool = await mcpCallableTool.tool();
219
- if (!tool || !Array.isArray(tool.functionDeclarations)) {
220
- console.error(`MCP server '${mcpServerName}' did not return valid tool function declarations. Skipping.`);
221
- if (transport instanceof StdioClientTransport ||
222
- transport instanceof SSEClientTransport ||
223
- transport instanceof StreamableHTTPClientTransport) {
224
- await transport.close();
371
+ catch (error) {
372
+ // Check if this is a 401 error that might indicate OAuth is required
373
+ const errorString = String(error);
374
+ if (errorString.includes('401') &&
375
+ (mcpServerConfig.httpUrl || mcpServerConfig.url)) {
376
+ mcpServerRequiresOAuth.set(mcpServerName, true);
377
+ // Only trigger automatic OAuth discovery for HTTP servers or when OAuth is explicitly configured
378
+ // For SSE servers, we should not trigger new OAuth flows automatically
379
+ const shouldTriggerOAuth = mcpServerConfig.httpUrl || mcpServerConfig.oauth?.enabled;
380
+ if (!shouldTriggerOAuth) {
381
+ // For SSE servers without explicit OAuth config, if a token was found but rejected, report it accurately.
382
+ const credentials = await MCPOAuthTokenStorage.getToken(mcpServerName);
383
+ if (credentials) {
384
+ const hasStoredTokens = await MCPOAuthProvider.getValidToken(mcpServerName, {
385
+ // Pass client ID if available
386
+ clientId: credentials.clientId,
387
+ });
388
+ if (hasStoredTokens) {
389
+ console.log(`Stored OAuth token for SSE server '${mcpServerName}' was rejected. ` +
390
+ `Please re-authenticate using: /mcp auth ${mcpServerName}`);
391
+ }
392
+ else {
393
+ console.log(`401 error received for SSE server '${mcpServerName}' without OAuth configuration. ` +
394
+ `Please authenticate using: /mcp auth ${mcpServerName}`);
395
+ }
396
+ }
397
+ throw new Error(`401 error received for SSE server '${mcpServerName}' without OAuth configuration. ` +
398
+ `Please authenticate using: /mcp auth ${mcpServerName}`);
225
399
  }
226
- // Update status to disconnected
227
- updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED);
228
- return;
229
- }
230
- for (const funcDecl of tool.functionDeclarations) {
231
- if (!funcDecl.name) {
232
- console.warn(`Discovered a function declaration without a name from MCP server '${mcpServerName}'. Skipping.`);
233
- continue;
400
+ // Try to extract www-authenticate header from the error
401
+ let wwwAuthenticate = extractWWWAuthenticateHeader(errorString);
402
+ // If we didn't get the header from the error string, try to get it from the server
403
+ if (!wwwAuthenticate && mcpServerConfig.url) {
404
+ console.log(`No www-authenticate header in error, trying to fetch it from server...`);
405
+ try {
406
+ const response = await fetch(mcpServerConfig.url, {
407
+ method: 'HEAD',
408
+ headers: {
409
+ Accept: 'text/event-stream',
410
+ },
411
+ signal: AbortSignal.timeout(5000),
412
+ });
413
+ if (response.status === 401) {
414
+ wwwAuthenticate = response.headers.get('www-authenticate');
415
+ if (wwwAuthenticate) {
416
+ console.log(`Found www-authenticate header from server: ${wwwAuthenticate}`);
417
+ }
418
+ }
419
+ }
420
+ catch (fetchError) {
421
+ console.debug(`Failed to fetch www-authenticate header: ${getErrorMessage(fetchError)}`);
422
+ }
234
423
  }
235
- const { includeTools, excludeTools } = mcpServerConfig;
236
- const toolName = funcDecl.name;
237
- let isEnabled = false;
238
- if (includeTools === undefined) {
239
- isEnabled = true;
424
+ if (wwwAuthenticate) {
425
+ console.log(`Received 401 with www-authenticate header: ${wwwAuthenticate}`);
426
+ // Try automatic OAuth discovery and authentication
427
+ const oauthSuccess = await handleAutomaticOAuth(mcpServerName, mcpServerConfig, wwwAuthenticate);
428
+ if (oauthSuccess) {
429
+ // Retry connection with OAuth token
430
+ console.log(`Retrying connection to '${mcpServerName}' with OAuth token...`);
431
+ // Get the valid token - we need to create a proper OAuth config
432
+ // The token should already be available from the authentication process
433
+ const credentials = await MCPOAuthTokenStorage.getToken(mcpServerName);
434
+ if (credentials) {
435
+ const accessToken = await MCPOAuthProvider.getValidToken(mcpServerName, {
436
+ // Pass client ID if available
437
+ clientId: credentials.clientId,
438
+ });
439
+ if (accessToken) {
440
+ // Create transport with OAuth token
441
+ const oauthTransport = await createTransportWithOAuth(mcpServerName, mcpServerConfig, accessToken);
442
+ if (oauthTransport) {
443
+ try {
444
+ await mcpClient.connect(oauthTransport, {
445
+ timeout: mcpServerConfig.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC,
446
+ });
447
+ // Connection successful with OAuth
448
+ return mcpClient;
449
+ }
450
+ catch (retryError) {
451
+ console.error(`Failed to connect with OAuth token: ${getErrorMessage(retryError)}`);
452
+ throw retryError;
453
+ }
454
+ }
455
+ else {
456
+ console.error(`Failed to create OAuth transport for server '${mcpServerName}'`);
457
+ throw new Error(`Failed to create OAuth transport for server '${mcpServerName}'`);
458
+ }
459
+ }
460
+ else {
461
+ console.error(`Failed to get OAuth token for server '${mcpServerName}'`);
462
+ throw new Error(`Failed to get OAuth token for server '${mcpServerName}'`);
463
+ }
464
+ }
465
+ else {
466
+ console.error(`Failed to get credentials for server '${mcpServerName}' after successful OAuth authentication`);
467
+ throw new Error(`Failed to get credentials for server '${mcpServerName}' after successful OAuth authentication`);
468
+ }
469
+ }
470
+ else {
471
+ console.error(`Failed to handle automatic OAuth for server '${mcpServerName}'`);
472
+ throw new Error(`Failed to handle automatic OAuth for server '${mcpServerName}'`);
473
+ }
240
474
  }
241
475
  else {
242
- isEnabled = includeTools.some((tool) => tool === toolName || tool.startsWith(`${toolName}(`));
476
+ // No www-authenticate header found, but we got a 401
477
+ // Only try OAuth discovery for HTTP servers or when OAuth is explicitly configured
478
+ // For SSE servers, we should not trigger new OAuth flows automatically
479
+ const shouldTryDiscovery = mcpServerConfig.httpUrl || mcpServerConfig.oauth?.enabled;
480
+ if (!shouldTryDiscovery) {
481
+ const credentials = await MCPOAuthTokenStorage.getToken(mcpServerName);
482
+ if (credentials) {
483
+ const hasStoredTokens = await MCPOAuthProvider.getValidToken(mcpServerName, {
484
+ // Pass client ID if available
485
+ clientId: credentials.clientId,
486
+ });
487
+ if (hasStoredTokens) {
488
+ console.log(`Stored OAuth token for SSE server '${mcpServerName}' was rejected. ` +
489
+ `Please re-authenticate using: /mcp auth ${mcpServerName}`);
490
+ }
491
+ else {
492
+ console.log(`401 error received for SSE server '${mcpServerName}' without OAuth configuration. ` +
493
+ `Please authenticate using: /mcp auth ${mcpServerName}`);
494
+ }
495
+ }
496
+ throw new Error(`401 error received for SSE server '${mcpServerName}' without OAuth configuration. ` +
497
+ `Please authenticate using: /mcp auth ${mcpServerName}`);
498
+ }
499
+ // For SSE servers, try to discover OAuth configuration from the base URL
500
+ console.log(`🔍 Attempting OAuth discovery for '${mcpServerName}'...`);
501
+ if (mcpServerConfig.url) {
502
+ const sseUrl = new URL(mcpServerConfig.url);
503
+ const baseUrl = `${sseUrl.protocol}//${sseUrl.host}`;
504
+ try {
505
+ // Try to discover OAuth configuration from the base URL
506
+ const oauthConfig = await OAuthUtils.discoverOAuthConfig(baseUrl);
507
+ if (oauthConfig) {
508
+ console.log(`Discovered OAuth configuration from base URL for server '${mcpServerName}'`);
509
+ // Create OAuth configuration for authentication
510
+ const oauthAuthConfig = {
511
+ enabled: true,
512
+ authorizationUrl: oauthConfig.authorizationUrl,
513
+ tokenUrl: oauthConfig.tokenUrl,
514
+ scopes: oauthConfig.scopes || [],
515
+ };
516
+ // Perform OAuth authentication
517
+ console.log(`Starting OAuth authentication for server '${mcpServerName}'...`);
518
+ await MCPOAuthProvider.authenticate(mcpServerName, oauthAuthConfig);
519
+ // Retry connection with OAuth token
520
+ const credentials = await MCPOAuthTokenStorage.getToken(mcpServerName);
521
+ if (credentials) {
522
+ const accessToken = await MCPOAuthProvider.getValidToken(mcpServerName, {
523
+ // Pass client ID if available
524
+ clientId: credentials.clientId,
525
+ });
526
+ if (accessToken) {
527
+ // Create transport with OAuth token
528
+ const oauthTransport = await createTransportWithOAuth(mcpServerName, mcpServerConfig, accessToken);
529
+ if (oauthTransport) {
530
+ try {
531
+ await mcpClient.connect(oauthTransport, {
532
+ timeout: mcpServerConfig.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC,
533
+ });
534
+ // Connection successful with OAuth
535
+ return mcpClient;
536
+ }
537
+ catch (retryError) {
538
+ console.error(`Failed to connect with OAuth token: ${getErrorMessage(retryError)}`);
539
+ throw retryError;
540
+ }
541
+ }
542
+ else {
543
+ console.error(`Failed to create OAuth transport for server '${mcpServerName}'`);
544
+ throw new Error(`Failed to create OAuth transport for server '${mcpServerName}'`);
545
+ }
546
+ }
547
+ else {
548
+ console.error(`Failed to get OAuth token for server '${mcpServerName}'`);
549
+ throw new Error(`Failed to get OAuth token for server '${mcpServerName}'`);
550
+ }
551
+ }
552
+ else {
553
+ console.error(`Failed to get stored credentials for server '${mcpServerName}'`);
554
+ throw new Error(`Failed to get stored credentials for server '${mcpServerName}'`);
555
+ }
556
+ }
557
+ else {
558
+ console.error(`❌ Could not configure OAuth for '${mcpServerName}' - please authenticate manually with /mcp auth ${mcpServerName}`);
559
+ throw new Error(`OAuth configuration failed for '${mcpServerName}'. Please authenticate manually with /mcp auth ${mcpServerName}`);
560
+ }
561
+ }
562
+ catch (discoveryError) {
563
+ console.error(`❌ OAuth discovery failed for '${mcpServerName}' - please authenticate manually with /mcp auth ${mcpServerName}`);
564
+ throw discoveryError;
565
+ }
566
+ }
567
+ else {
568
+ console.error(`❌ '${mcpServerName}' requires authentication but no OAuth configuration found`);
569
+ throw new Error(`MCP server '${mcpServerName}' requires authentication. Please configure OAuth or check server settings.`);
570
+ }
243
571
  }
244
- if (excludeTools?.includes(toolName)) {
245
- isEnabled = false;
572
+ }
573
+ else {
574
+ // Handle other connection errors
575
+ // Create a concise error message
576
+ const errorMessage = error.message || String(error);
577
+ const isNetworkError = errorMessage.includes('ENOTFOUND') ||
578
+ errorMessage.includes('ECONNREFUSED');
579
+ let conciseError;
580
+ if (isNetworkError) {
581
+ conciseError = `Cannot connect to '${mcpServerName}' - server may be down or URL incorrect`;
246
582
  }
247
- if (!isEnabled) {
248
- continue;
583
+ else {
584
+ conciseError = `Connection failed for '${mcpServerName}': ${errorMessage}`;
249
585
  }
250
- let toolNameForModel = funcDecl.name;
251
- // Replace invalid characters (based on 400 error message from Gemini API) with underscores
252
- toolNameForModel = toolNameForModel.replace(/[^a-zA-Z0-9_.-]/g, '_');
253
- const existingTool = toolRegistry.getTool(toolNameForModel);
254
- if (existingTool) {
255
- toolNameForModel = mcpServerName + '__' + toolNameForModel;
586
+ if (process.env.SANDBOX) {
587
+ conciseError += ` (check sandbox availability)`;
256
588
  }
257
- // If longer than 63 characters, replace middle with '___'
258
- // (Gemini API says max length 64, but actual limit seems to be 63)
259
- if (toolNameForModel.length > 63) {
260
- toolNameForModel =
261
- toolNameForModel.slice(0, 28) + '___' + toolNameForModel.slice(-32);
589
+ throw new Error(conciseError);
590
+ }
591
+ }
592
+ }
593
+ /** Visible for Testing */
594
+ export async function createTransport(mcpServerName, mcpServerConfig, debugMode) {
595
+ if (mcpServerConfig.authProviderType === AuthProviderType.GOOGLE_CREDENTIALS) {
596
+ const provider = new GoogleCredentialProvider(mcpServerConfig);
597
+ const transportOptions = {
598
+ authProvider: provider,
599
+ };
600
+ if (mcpServerConfig.httpUrl) {
601
+ return new StreamableHTTPClientTransport(new URL(mcpServerConfig.httpUrl), transportOptions);
602
+ }
603
+ else if (mcpServerConfig.url) {
604
+ return new SSEClientTransport(new URL(mcpServerConfig.url), transportOptions);
605
+ }
606
+ throw new Error('No URL configured for Google Credentials MCP server');
607
+ }
608
+ // Check if we have OAuth configuration or stored tokens
609
+ let accessToken = null;
610
+ let hasOAuthConfig = mcpServerConfig.oauth?.enabled;
611
+ if (hasOAuthConfig && mcpServerConfig.oauth) {
612
+ accessToken = await MCPOAuthProvider.getValidToken(mcpServerName, mcpServerConfig.oauth);
613
+ if (!accessToken) {
614
+ console.error(`MCP server '${mcpServerName}' requires OAuth authentication. ` +
615
+ `Please authenticate using the /mcp auth command.`);
616
+ throw new Error(`MCP server '${mcpServerName}' requires OAuth authentication. ` +
617
+ `Please authenticate using the /mcp auth command.`);
618
+ }
619
+ }
620
+ else {
621
+ // Check if we have stored OAuth tokens for this server (from previous authentication)
622
+ const credentials = await MCPOAuthTokenStorage.getToken(mcpServerName);
623
+ if (credentials) {
624
+ accessToken = await MCPOAuthProvider.getValidToken(mcpServerName, {
625
+ // Pass client ID if available
626
+ clientId: credentials.clientId,
627
+ });
628
+ if (accessToken) {
629
+ hasOAuthConfig = true;
630
+ console.log(`Found stored OAuth token for server '${mcpServerName}'`);
262
631
  }
263
- sanitizeParameters(funcDecl.parameters);
264
- toolRegistry.registerTool(new DiscoveredMCPTool(mcpCallableTool, mcpServerName, toolNameForModel, funcDecl.description ?? '', funcDecl.parameters ?? { type: Type.OBJECT, properties: {} }, funcDecl.name, mcpServerConfig.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC, mcpServerConfig.trust));
265
632
  }
266
633
  }
267
- catch (error) {
268
- console.error(`Failed to list or register tools for MCP server '${mcpServerName}': ${error}`);
269
- // Ensure transport is cleaned up on error too
270
- if (transport instanceof StdioClientTransport ||
271
- transport instanceof SSEClientTransport ||
272
- transport instanceof StreamableHTTPClientTransport) {
273
- await transport.close();
634
+ if (mcpServerConfig.httpUrl) {
635
+ const transportOptions = {};
636
+ // Set up headers with OAuth token if available
637
+ if (hasOAuthConfig && accessToken) {
638
+ transportOptions.requestInit = {
639
+ headers: {
640
+ ...mcpServerConfig.headers,
641
+ Authorization: `Bearer ${accessToken}`,
642
+ },
643
+ };
274
644
  }
275
- // Update status to disconnected
276
- updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED);
645
+ else if (mcpServerConfig.headers) {
646
+ transportOptions.requestInit = {
647
+ headers: mcpServerConfig.headers,
648
+ };
649
+ }
650
+ return new StreamableHTTPClientTransport(new URL(mcpServerConfig.httpUrl), transportOptions);
277
651
  }
278
- // If no tools were registered from this MCP server, the following 'if' block
279
- // will close the connection. This is done to conserve resources and prevent
280
- // an orphaned connection to a server that isn't providing any usable
281
- // functionality. Connections to servers that did provide tools are kept
282
- // open, as those tools will require the connection to function.
283
- if (toolRegistry.getToolsByServer(mcpServerName).length === 0) {
284
- console.log(`No tools registered from MCP server '${mcpServerName}'. Closing connection.`);
285
- if (transport instanceof StdioClientTransport ||
286
- transport instanceof SSEClientTransport ||
287
- transport instanceof StreamableHTTPClientTransport) {
288
- await transport.close();
289
- // Update status to disconnected
290
- updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED);
652
+ if (mcpServerConfig.url) {
653
+ const transportOptions = {};
654
+ // Set up headers with OAuth token if available
655
+ if (hasOAuthConfig && accessToken) {
656
+ transportOptions.requestInit = {
657
+ headers: {
658
+ ...mcpServerConfig.headers,
659
+ Authorization: `Bearer ${accessToken}`,
660
+ },
661
+ };
291
662
  }
663
+ else if (mcpServerConfig.headers) {
664
+ transportOptions.requestInit = {
665
+ headers: mcpServerConfig.headers,
666
+ };
667
+ }
668
+ return new SSEClientTransport(new URL(mcpServerConfig.url), transportOptions);
669
+ }
670
+ if (mcpServerConfig.command) {
671
+ const transport = new StdioClientTransport({
672
+ command: mcpServerConfig.command,
673
+ args: mcpServerConfig.args || [],
674
+ env: {
675
+ ...process.env,
676
+ ...(mcpServerConfig.env || {}),
677
+ },
678
+ cwd: mcpServerConfig.cwd,
679
+ stderr: 'pipe',
680
+ });
681
+ if (debugMode) {
682
+ transport.stderr.on('data', (data) => {
683
+ const stderrStr = data.toString().trim();
684
+ console.debug(`[DEBUG] [MCP STDERR (${mcpServerName})]: `, stderrStr);
685
+ });
686
+ }
687
+ return transport;
688
+ }
689
+ throw new Error(`Invalid configuration: missing httpUrl (for Streamable HTTP), url (for SSE), and command (for stdio).`);
690
+ }
691
+ /** Visible for testing */
692
+ export function isEnabled(funcDecl, mcpServerName, mcpServerConfig) {
693
+ if (!funcDecl.name) {
694
+ console.warn(`Discovered a function declaration without a name from MCP server '${mcpServerName}'. Skipping.`);
695
+ return false;
696
+ }
697
+ const { includeTools, excludeTools } = mcpServerConfig;
698
+ // excludeTools takes precedence over includeTools
699
+ if (excludeTools && excludeTools.includes(funcDecl.name)) {
700
+ return false;
292
701
  }
702
+ return (!includeTools ||
703
+ includeTools.some((tool) => tool === funcDecl.name || tool.startsWith(`${funcDecl.name}(`)));
293
704
  }
294
705
  //# sourceMappingURL=mcp-client.js.map