9f389520 by Ean Schuessler

Consolidate screen routing into a single unified tool

- Replace redundant screen-specific tools with moqui_render_screen
- Simplify ToolsCall dispatcher and remove dead protocol mapping logic
- Improve GetScreenDetails to extract parameters from XML and entities
- Fix Basic auth and session handling in EnhancedMcpServlet
- Remove obsolete name decoding logic from McpUtils
1 parent 1bb29d0c
......@@ -143,12 +143,8 @@ class EnhancedMcpServlet extends HttpServlet {
ExecutionContextImpl ec = ecfi.getEci()
try {
// Handle Basic Authentication directly without triggering screen system
String authzHeader = request.getHeader("Authorization")
boolean authenticated = false
// Read request body early before any other processing can consume it
try {
// Read request body VERY early before any other processing can consume it
String requestBody = null
if ("POST".equals(request.getMethod())) {
try {
......@@ -168,6 +164,17 @@ try {
}
}
// Initialize web facade early to set up session and visit context
try {
ec.initWebFacade(webappName, request, response)
} catch (Exception e) {
logger.warn("Web facade initialization warning: ${e.message}")
}
// Handle Basic Authentication directly
String authzHeader = request.getHeader("Authorization")
boolean authenticated = false
if (authzHeader != null && authzHeader.length() > 6 && authzHeader.startsWith("Basic ")) {
String basicAuthEncoded = authzHeader.substring(6).trim()
String basicAuthAsString = new String(basicAuthEncoded.decodeBase64())
......@@ -176,12 +183,15 @@ try {
String username = basicAuthAsString.substring(0, indexOfColon)
String password = basicAuthAsString.substring(indexOfColon + 1)
try {
logger.info("LOGGING IN ${username} ${password}")
ec.user.loginUser(username, password)
authenticated = true
logger.info("LOGGING IN ${username}")
authenticated = ec.user.loginUser(username, password)
if (authenticated) {
logger.info("Enhanced MCP Basic auth successful for user: ${ec.user?.username}")
} else {
logger.warn("Enhanced MCP Basic auth failed for user: ${username}")
}
} catch (Exception e) {
logger.warn("Enhanced MCP Basic auth failed for user ${username}: ${e.message}")
logger.warn("Enhanced MCP Basic auth exception for user ${username}: ${e.message}")
}
} else {
logger.warn("Enhanced MCP got bad Basic auth credentials string")
......@@ -190,7 +200,7 @@ try {
// Re-enabled proper authentication - UserServices compilation issues resolved
if (!authenticated || !ec.user?.userId) {
logger.warn("Enhanced MCP authentication failed - no valid user authenticated")
logger.warn("Enhanced MCP authentication failed - authenticated=${authenticated}, userId=${ec.user?.userId}")
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)
response.setContentType("application/json")
response.setHeader("WWW-Authenticate", "Basic realm=\"Moqui MCP\"")
......@@ -202,24 +212,10 @@ try {
return
}
// Create Visit for JSON-RPC requests too
def visit = null
try {
// Initialize web facade for Visit creation
ec.initWebFacade(webappName, request, response)
// Web facade was successful, get Visit it created
visit = ec.user.getVisit()
if (!visit) {
throw new Exception("Web facade succeeded but no Visit created")
}
} catch (Exception e) {
logger.error("Web facade initialization failed - this is a system configuration error: ${e.message}", e)
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "System configuration error: Web facade failed to initialize. Check Moqui logs for details.")
return
}
// Final check that we have a Visit
// Get Visit created by web facade
def visit = ec.user.getVisit()
if (!visit) {
logger.error("Web facade initialized but no Visit created")
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to create Visit")
return
}
......
......@@ -17,75 +17,7 @@ import org.moqui.context.ExecutionContext
import org.moqui.util.MNode
class McpUtils {
/**
* Convert a Moqui screen path to an MCP tool name.
* Format: moqui_<Component>_<Path_Parts>
* Example: component://PopCommerce/screen/PopCommerceAdmin/Catalog.xml -> moqui_PopCommerce_PopCommerceAdmin_Catalog
*/
static String getToolName(String screenPath) {
if (!screenPath) return null
String cleanPath = screenPath
if (cleanPath.startsWith("component://")) cleanPath = cleanPath.substring(12)
if (cleanPath.endsWith(".xml")) cleanPath = cleanPath.substring(0, cleanPath.length() - 4)
List<String> parts = cleanPath.split('/').toList()
if (parts.size() > 1 && parts[1] == "screen") {
parts.remove(1)
}
return "moqui_" + parts.join('_')
}
/**
* Convert an MCP tool name back to a Moqui screen path.
* Assumes standard component://<Component>/screen/<Path>.xml structure.
*/
static String getScreenPath(String toolName) {
if (!toolName || !toolName.startsWith("moqui_")) return null
String cleanName = toolName.substring(6) // Remove moqui_
List<String> parts = cleanName.split('_').toList()
if (parts.size() < 1) return null
String component = parts[0]
if (parts.size() == 1) {
return "component://${component}/screen/${component}.xml"
}
String path = parts.subList(1, parts.size()).join('/')
return "component://${component}/screen/${path}.xml"
}
/**
* Decodes a tool name into a screen path and potential subscreen path by walking the component structure.
* This handles cases where a single XML file contains multiple nested subscreens.
*/
static Map decodeToolName(String toolName, ExecutionContext ec) {
if (!toolName || !toolName.startsWith("moqui_")) return [:]
String cleanName = toolName.substring(6) // Remove moqui_
List<String> parts = cleanName.split('_').toList()
String component = parts[0]
String currentPath = "component://${component}/screen"
List<String> subNameParts = []
// Walk down the parts to find where the XML file ends and subscreens begin
for (int i = 1; i < parts.size(); i++) {
String part = parts[i]
String nextPath = "${currentPath}/${part}"
if (ec.resource.getLocationReference(nextPath + ".xml").getExists()) {
currentPath = nextPath
subNameParts = [] // Reset subscreens if we found a deeper file
} else {
subNameParts << part
}
}
return [
screenPath: currentPath + ".xml",
subscreenName: subNameParts ? subNameParts.join("_") : null
]
}
// This class is now primarily a placeholder. All tool-name to screen-path
// conversion logic has been removed to simplify the system to a single
// moqui_render_screen tool.
}
......