f3884d36 by Ean Schuessler

Fix MCP shell session header and add timeout protection

- Fix session header variable expansion in make_mcp_request
- Add MAIN_SCRIPT check to prevent main logic when sourcing
- Add timeout protection around curl and jq commands
- Add debug output for troubleshooting hanging issues

The MCP shell now properly handles session IDs and has timeout protection
to prevent hanging on long responses. Screen execution service is working
but response processing needs optimization for large JSON responses.
1 parent 08a2d205
...@@ -481,8 +481,9 @@ ...@@ -481,8 +481,9 @@
481 } 481 }
482 482
483 // Now call the screen tool with proper user context 483 // Now call the screen tool with proper user context
484 def screenParams = arguments ?: [:]
484 serviceResult = ec.service.sync().name("McpServices.execute#ScreenAsMcpTool") 485 serviceResult = ec.service.sync().name("McpServices.execute#ScreenAsMcpTool")
485 .parameters([screenPath: screenPath, parameters: arguments ?: [:], renderMode: "html"]) 486 .parameters([screenPath: screenPath, parameters: screenParams, renderMode: "html"])
486 .call() 487 .call()
487 488
488 } finally { 489 } finally {
...@@ -1353,57 +1354,47 @@ try { ...@@ -1353,57 +1354,47 @@ try {
1353 def screenUrl = "http://localhost:8080/${screenPath}" 1354 def screenUrl = "http://localhost:8080/${screenPath}"
1354 1355
1355 try { 1356 try {
1356 ec.logger.info("MCP Screen Execution: Attempting to render screen ${screenPath}") 1357 ec.logger.info("MCP Screen Execution: Attempting to render screen ${screenPath} using Moqui's test framework")
1357 1358
1358 // Mock web context objects that screens may expect 1359 // Determine appropriate base path based on screen path
1359 ec.context.put("html_scripts", new LinkedHashSet<String>()) 1360 def basePath = ""
1360 ec.context.put("html_stylesheets", new LinkedHashSet<String>()) 1361 if (screenPath.startsWith("component://")) {
1361 1362 basePath = "" // Use empty base path for component paths
1362 // Mock other common web context objects 1363 } else if (screenPath.startsWith("apps/")) {
1363 ec.context.put("webappName", "mcp") 1364 basePath = "apps" // Use apps base path for apps screens
1364 ec.context.put("servletContext", [:]) 1365 } else if (screenPath.startsWith("webroot/")) {
1365 ec.context.put("request", [:]) 1366 basePath = "" // Use empty base path for webroot screens
1366 ec.context.put("response", [:]) 1367 }
1367 1368
1368 // Mock ec.web for getResourceDistinctValue() calls 1369 // Use Moqui's official test screen rendering framework
1369 def mockWeb = [ 1370 // This creates proper WebFacadeStub with all necessary web context objects
1370 getResourceDistinctValue: { -> return System.currentTimeMillis() } 1371 def screenTest = ec.screen.makeTest()
1371 ] 1372 .baseScreenPath(basePath)
1372 ec.context.put("web", mockWeb) 1373 .renderMode(renderMode ? renderMode : "text")
1373 1374
1374 // Try to render screen with specified render mode for LLM to read 1375 ec.logger.info("MCP Screen Execution: ScreenTest object created: ${screenTest?.getClass()?.getSimpleName()}")
1375 def screenRender = ec.screen.makeRender()
1376 .rootScreen(screenPath) // Set root screen location
1377 .renderMode(renderMode) // Set render mode from parameter, but don't set additional path for root screen
1378 1376
1379 // Get the real sri object and override problematic methods 1377 if (screenTest) {
1380 // Put mock sri in context that will be used by templates 1378 def renderParams = parameters ?: [:]
1381 def mockSriForContext = [
1382 buildUrl: { String path ->
1383 return [
1384 url: path.startsWith("/") ? path : "/${path}",
1385 path: path,
1386 isPermitted: { -> true },
1387 toString: { -> return path.startsWith("/") ? path : "/${path}" }
1388 ]
1389 },
1390 getThemeValues: { String enumId ->
1391 return []
1392 },
1393 sendRedirectAndStopRender: { String redirectUrl ->
1394 ec.logger.info("MCP Screen Execution: Ignoring redirect to ${redirectUrl} - continuing screen render for MCP")
1395 // Don't actually redirect, just log and continue
1396 }
1397 ]
1398 ec.context.put("sri", mockSriForContext)
1399 1379
1400 ec.logger.info("MCP Screen Execution: ScreenRender object created: ${screenRender?.getClass()?.getSimpleName()}") 1380 // Add timeout to prevent hanging
1381 def future = java.util.concurrent.Executors.newSingleThreadExecutor().submit({
1382 return screenTest.render(screenPath, renderParams, null)
1383 } as java.util.concurrent.Callable)
1401 1384
1402 if (screenRender) { 1385 try {
1403 output = screenRender.render() 1386 def testRender = future.get(30, java.util.concurrent.TimeUnit.SECONDS) // 30 second timeout
1404 ec.logger.info("MCP Screen Execution: Successfully rendered screen ${screenPath}, output length: ${output?.length() ?: 0}") 1387 output = testRender.output
1388 def outputLength = output?.length() ?: 0
1389 ec.logger.info("MCP Screen Execution: Successfully rendered screen ${screenPath}, output length: ${outputLength}")
1390 } catch (java.util.concurrent.TimeoutException e) {
1391 future.cancel(true)
1392 throw new Exception("Screen rendering timed out after 30 seconds for ${screenPath}")
1393 } finally {
1394 future.cancel(true)
1395 }
1405 } else { 1396 } else {
1406 throw new Exception("ScreenRender object is null") 1397 throw new Exception("ScreenTest object is null")
1407 } 1398 }
1408 1399
1409 } catch (Exception e) { 1400 } catch (Exception e) {
......