@@ -197,10 +197,8 @@ class IdeaAgentViewModel(
197197 )
198198 vmLogger.warn(" LLM service created successfully" )
199199
200- // ⚠️ TEMPORARILY DISABLED - MCP preloading may cause freeze
201- // Start MCP preloading after LLM service is created
202- // startMcpPreloading()
203- vmLogger.warn(" MCP preloading SKIPPED (temporarily disabled for debugging)" )
200+ // Start MCP preloading after LLM service is created (fully async, non-blocking)
201+ startMcpPreloading()
204202 }
205203 vmLogger.warn(" loadConfiguration() completed successfully" )
206204 } catch (e: Exception ) {
@@ -215,78 +213,85 @@ class IdeaAgentViewModel(
215213
216214 /* *
217215 * Start MCP servers preloading in background.
218- * Aligned with CodingAgentViewModel's startMcpPreloading().
219216 *
220- * ⚠️ WARNING: This method contains a blocking while loop that can freeze the EDT
221- * if MCP servers are unresponsive. Currently disabled for debugging.
217+ * This method is fully asynchronous and non-blocking:
218+ * 1. Launches a background coroutine on Dispatchers.IO
219+ * 2. Initializes MCP servers via McpToolConfigManager.init()
220+ * 3. Monitors preloading status by waiting for the preloading job to complete
221+ * 4. Updates UI state via StateFlow (no EDT blocking)
222+ *
223+ * The key difference from the old implementation:
224+ * - OLD: Used a while loop with delay() that could block for up to 60 seconds
225+ * - NEW: Uses waitForPreloading() which suspends without blocking EDT
222226 */
223- private suspend fun startMcpPreloading () {
224- vmLogger.warn(" startMcpPreloading() called" )
225- try {
226- vmLogger.warn(" Loading MCP servers configuration..." )
227- _mcpPreloadingMessage .value = " Loading MCP servers configuration..."
228-
229- // Use IdeaToolConfigService to get and cache tool config
230- vmLogger.warn(" Getting IdeaToolConfigService instance..." )
231- val toolConfigService = IdeaToolConfigService .getInstance(project)
232- vmLogger.warn(" Reloading tool config..." )
233- toolConfigService.reloadConfig()
234- vmLogger.warn(" Getting tool config..." )
235- val toolConfig = toolConfigService.getToolConfig()
236- cachedToolConfig = toolConfig
237- vmLogger.warn(" Tool config loaded - ${toolConfig.mcpServers.size} MCP servers configured" )
238-
239- if (toolConfig.mcpServers.isEmpty()) {
240- vmLogger.warn(" No MCP servers configured - skipping preloading" )
241- _mcpPreloadingMessage .value = " No MCP servers configured"
242- return
243- }
227+ private fun startMcpPreloading () {
228+ vmLogger.warn(" startMcpPreloading() called - launching background coroutine" )
244229
245- vmLogger.warn(" Initializing ${toolConfig.mcpServers.size} MCP servers..." )
246- _mcpPreloadingMessage .value = " Initializing ${toolConfig.mcpServers.size} MCP servers..."
247-
248- // Initialize MCP servers (this will start background preloading)
249- vmLogger.warn(" Calling McpToolConfigManager.init()..." )
250- McpToolConfigManager .init (toolConfig)
251- vmLogger.warn(" McpToolConfigManager.init() returned" )
252-
253- // Monitor preloading status with timeout to prevent infinite loop
254- val timeoutMs = 60_000L // 60 seconds max
255- val startTime = System .currentTimeMillis()
256- vmLogger.warn(" Starting MCP preloading monitor loop (timeout: ${timeoutMs} ms)..." )
257- var loopCount = 0
258- while (McpToolConfigManager .isPreloading() &&
259- (System .currentTimeMillis() - startTime) < timeoutMs
260- ) {
261- loopCount++
262- val elapsed = System .currentTimeMillis() - startTime
263- vmLogger.warn(" MCP preloading loop iteration $loopCount (elapsed: ${elapsed} ms)" )
264- _mcpPreloadingStatus .value = McpToolConfigManager .getPreloadingStatus()
265- _mcpPreloadingMessage .value =
266- " Loading MCP servers... (${_mcpPreloadingStatus .value.preloadedServers.size} completed)"
267- delay(500 )
268- }
269- vmLogger.warn(" MCP preloading loop exited after $loopCount iterations" )
230+ // Launch on IO dispatcher to avoid blocking EDT
231+ coroutineScope.launch(Dispatchers .IO ) {
232+ try {
233+ vmLogger.warn(" MCP preloading coroutine started on ${Thread .currentThread().name} " )
234+ _mcpPreloadingMessage .value = " Loading MCP servers configuration..."
235+
236+ // Use IdeaToolConfigService to get and cache tool config
237+ val toolConfigService = IdeaToolConfigService .getInstance(project)
238+ toolConfigService.reloadConfig()
239+ val toolConfig = toolConfigService.getToolConfig()
240+ cachedToolConfig = toolConfig
241+ vmLogger.warn(" Tool config loaded - ${toolConfig.mcpServers.size} MCP servers configured" )
242+
243+ if (toolConfig.mcpServers.isEmpty()) {
244+ vmLogger.warn(" No MCP servers configured - skipping preloading" )
245+ _mcpPreloadingMessage .value = " No MCP servers configured"
246+ return @launch
247+ }
248+
249+ val enabledCount = toolConfig.mcpServers.filter { ! it.value.disabled }.size
250+ vmLogger.warn(" Initializing $enabledCount enabled MCP servers..." )
251+ _mcpPreloadingMessage .value = " Initializing $enabledCount MCP servers..."
252+
253+ // Initialize MCP servers (this starts background preloading in McpToolConfigManager)
254+ McpToolConfigManager .init (toolConfig)
255+ vmLogger.warn(" McpToolConfigManager.init() returned - preloading started in background" )
256+
257+ // Launch a separate coroutine to monitor preloading status
258+ // This doesn't block - it just updates the UI state periodically
259+ val monitorJob = launch {
260+ while (McpToolConfigManager .isPreloading()) {
261+ _mcpPreloadingStatus .value = McpToolConfigManager .getPreloadingStatus()
262+ val preloadedCount = _mcpPreloadingStatus .value.preloadedServers.size
263+ _mcpPreloadingMessage .value = " Loading MCP servers... ($preloadedCount /$enabledCount completed)"
264+ delay(500 ) // Update UI every 500ms
265+ }
266+ }
270267
271- // Final status update
272- _mcpPreloadingStatus .value = McpToolConfigManager .getPreloadingStatus()
268+ // Wait for preloading to complete (non-blocking suspend)
269+ // This uses Job.join() internally, which is a proper suspend function
270+ vmLogger.warn(" Waiting for MCP preloading to complete..." )
271+ McpToolConfigManager .waitForPreloading()
272+ vmLogger.warn(" MCP preloading job completed" )
273273
274- val preloadedCount = _mcpPreloadingStatus .value.preloadedServers.size
275- val totalCount = toolConfig.mcpServers.filter { ! it.value.disabled }.size
274+ // Cancel the monitor job since preloading is done
275+ monitorJob.cancel()
276276
277- vmLogger.warn(" MCP preloading complete - $preloadedCount /$totalCount servers loaded" )
278- _mcpPreloadingMessage .value = if (preloadedCount > 0 ) {
279- " MCP servers loaded successfully ($preloadedCount /$totalCount servers)"
280- } else {
281- " MCP servers initialization completed (no tools loaded)"
277+ // Final status update
278+ _mcpPreloadingStatus .value = McpToolConfigManager .getPreloadingStatus()
279+ val preloadedCount = _mcpPreloadingStatus .value.preloadedServers.size
280+
281+ vmLogger.warn(" MCP preloading complete - $preloadedCount /$enabledCount servers loaded" )
282+ _mcpPreloadingMessage .value = if (preloadedCount > 0 ) {
283+ " MCP servers loaded successfully ($preloadedCount /$enabledCount servers)"
284+ } else {
285+ " MCP servers initialization completed (no tools loaded)"
286+ }
287+ } catch (e: CancellationException ) {
288+ vmLogger.warn(" MCP preloading cancelled" , e)
289+ // Cancellation is expected when configuration is reloaded, don't log as error
290+ throw e
291+ } catch (e: Exception ) {
292+ vmLogger.error(" MCP preloading failed with exception" , e)
293+ _mcpPreloadingMessage .value = " Failed to load MCP servers: ${e.message} "
282294 }
283- } catch (e: CancellationException ) {
284- vmLogger.warn(" MCP preloading cancelled" , e)
285- // Cancellation is expected when configuration is reloaded, don't log as error
286- throw e
287- } catch (e: Exception ) {
288- vmLogger.error(" MCP preloading failed with exception" , e)
289- _mcpPreloadingMessage .value = " Failed to load MCP servers: ${e.message} "
290295 }
291296 }
292297
0 commit comments