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 @@
}
// Now call the screen tool with proper user context
def screenParams = arguments ?: [:]
serviceResult = ec.service.sync().name("McpServices.execute#ScreenAsMcpTool")
.parameters([screenPath: screenPath, parameters: arguments ?: [:], renderMode: "html"])
.parameters([screenPath: screenPath, parameters: screenParams, renderMode: "html"])
.call()
} finally {
......@@ -1353,57 +1354,47 @@ try {
def screenUrl = "http://localhost:8080/${screenPath}"
try {
ec.logger.info("MCP Screen Execution: Attempting to render screen ${screenPath}")
ec.logger.info("MCP Screen Execution: Attempting to render screen ${screenPath} using Moqui's test framework")
// Mock web context objects that screens may expect
ec.context.put("html_scripts", new LinkedHashSet<String>())
ec.context.put("html_stylesheets", new LinkedHashSet<String>())
// Mock other common web context objects
ec.context.put("webappName", "mcp")
ec.context.put("servletContext", [:])
ec.context.put("request", [:])
ec.context.put("response", [:])
// Determine appropriate base path based on screen path
def basePath = ""
if (screenPath.startsWith("component://")) {
basePath = "" // Use empty base path for component paths
} else if (screenPath.startsWith("apps/")) {
basePath = "apps" // Use apps base path for apps screens
} else if (screenPath.startsWith("webroot/")) {
basePath = "" // Use empty base path for webroot screens
}
// Mock ec.web for getResourceDistinctValue() calls
def mockWeb = [
getResourceDistinctValue: { -> return System.currentTimeMillis() }
]
ec.context.put("web", mockWeb)
// Use Moqui's official test screen rendering framework
// This creates proper WebFacadeStub with all necessary web context objects
def screenTest = ec.screen.makeTest()
.baseScreenPath(basePath)
.renderMode(renderMode ? renderMode : "text")
// Try to render screen with specified render mode for LLM to read
def screenRender = ec.screen.makeRender()
.rootScreen(screenPath) // Set root screen location
.renderMode(renderMode) // Set render mode from parameter, but don't set additional path for root screen
ec.logger.info("MCP Screen Execution: ScreenTest object created: ${screenTest?.getClass()?.getSimpleName()}")
// Get the real sri object and override problematic methods
// Put mock sri in context that will be used by templates
def mockSriForContext = [
buildUrl: { String path ->
return [
url: path.startsWith("/") ? path : "/${path}",
path: path,
isPermitted: { -> true },
toString: { -> return path.startsWith("/") ? path : "/${path}" }
]
},
getThemeValues: { String enumId ->
return []
},
sendRedirectAndStopRender: { String redirectUrl ->
ec.logger.info("MCP Screen Execution: Ignoring redirect to ${redirectUrl} - continuing screen render for MCP")
// Don't actually redirect, just log and continue
if (screenTest) {
def renderParams = parameters ?: [:]
// Add timeout to prevent hanging
def future = java.util.concurrent.Executors.newSingleThreadExecutor().submit({
return screenTest.render(screenPath, renderParams, null)
} as java.util.concurrent.Callable)
try {
def testRender = future.get(30, java.util.concurrent.TimeUnit.SECONDS) // 30 second timeout
output = testRender.output
def outputLength = output?.length() ?: 0
ec.logger.info("MCP Screen Execution: Successfully rendered screen ${screenPath}, output length: ${outputLength}")
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true)
throw new Exception("Screen rendering timed out after 30 seconds for ${screenPath}")
} finally {
future.cancel(true)
}
]
ec.context.put("sri", mockSriForContext)
ec.logger.info("MCP Screen Execution: ScreenRender object created: ${screenRender?.getClass()?.getSimpleName()}")
if (screenRender) {
output = screenRender.render()
ec.logger.info("MCP Screen Execution: Successfully rendered screen ${screenPath}, output length: ${output?.length() ?: 0}")
} else {
throw new Exception("ScreenRender object is null")
throw new Exception("ScreenTest object is null")
}
} catch (Exception e) {
......