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
Showing
3 changed files
with
27 additions
and
99 deletions
This diff is collapsed.
Click to expand it.
| ... | @@ -143,12 +143,8 @@ class EnhancedMcpServlet extends HttpServlet { | ... | @@ -143,12 +143,8 @@ class EnhancedMcpServlet extends HttpServlet { |
| 143 | 143 | ||
| 144 | ExecutionContextImpl ec = ecfi.getEci() | 144 | ExecutionContextImpl ec = ecfi.getEci() |
| 145 | 145 | ||
| 146 | try { | 146 | try { |
| 147 | // Handle Basic Authentication directly without triggering screen system | 147 | // Read request body VERY early before any other processing can consume it |
| 148 | String authzHeader = request.getHeader("Authorization") | ||
| 149 | boolean authenticated = false | ||
| 150 | |||
| 151 | // Read request body early before any other processing can consume it | ||
| 152 | String requestBody = null | 148 | String requestBody = null |
| 153 | if ("POST".equals(request.getMethod())) { | 149 | if ("POST".equals(request.getMethod())) { |
| 154 | try { | 150 | try { |
| ... | @@ -168,6 +164,17 @@ try { | ... | @@ -168,6 +164,17 @@ try { |
| 168 | } | 164 | } |
| 169 | } | 165 | } |
| 170 | 166 | ||
| 167 | // Initialize web facade early to set up session and visit context | ||
| 168 | try { | ||
| 169 | ec.initWebFacade(webappName, request, response) | ||
| 170 | } catch (Exception e) { | ||
| 171 | logger.warn("Web facade initialization warning: ${e.message}") | ||
| 172 | } | ||
| 173 | |||
| 174 | // Handle Basic Authentication directly | ||
| 175 | String authzHeader = request.getHeader("Authorization") | ||
| 176 | boolean authenticated = false | ||
| 177 | |||
| 171 | if (authzHeader != null && authzHeader.length() > 6 && authzHeader.startsWith("Basic ")) { | 178 | if (authzHeader != null && authzHeader.length() > 6 && authzHeader.startsWith("Basic ")) { |
| 172 | String basicAuthEncoded = authzHeader.substring(6).trim() | 179 | String basicAuthEncoded = authzHeader.substring(6).trim() |
| 173 | String basicAuthAsString = new String(basicAuthEncoded.decodeBase64()) | 180 | String basicAuthAsString = new String(basicAuthEncoded.decodeBase64()) |
| ... | @@ -176,12 +183,15 @@ try { | ... | @@ -176,12 +183,15 @@ try { |
| 176 | String username = basicAuthAsString.substring(0, indexOfColon) | 183 | String username = basicAuthAsString.substring(0, indexOfColon) |
| 177 | String password = basicAuthAsString.substring(indexOfColon + 1) | 184 | String password = basicAuthAsString.substring(indexOfColon + 1) |
| 178 | try { | 185 | try { |
| 179 | logger.info("LOGGING IN ${username} ${password}") | 186 | logger.info("LOGGING IN ${username}") |
| 180 | ec.user.loginUser(username, password) | 187 | authenticated = ec.user.loginUser(username, password) |
| 181 | authenticated = true | 188 | if (authenticated) { |
| 182 | logger.info("Enhanced MCP Basic auth successful for user: ${ec.user?.username}") | 189 | logger.info("Enhanced MCP Basic auth successful for user: ${ec.user?.username}") |
| 190 | } else { | ||
| 191 | logger.warn("Enhanced MCP Basic auth failed for user: ${username}") | ||
| 192 | } | ||
| 183 | } catch (Exception e) { | 193 | } catch (Exception e) { |
| 184 | logger.warn("Enhanced MCP Basic auth failed for user ${username}: ${e.message}") | 194 | logger.warn("Enhanced MCP Basic auth exception for user ${username}: ${e.message}") |
| 185 | } | 195 | } |
| 186 | } else { | 196 | } else { |
| 187 | logger.warn("Enhanced MCP got bad Basic auth credentials string") | 197 | logger.warn("Enhanced MCP got bad Basic auth credentials string") |
| ... | @@ -190,7 +200,7 @@ try { | ... | @@ -190,7 +200,7 @@ try { |
| 190 | 200 | ||
| 191 | // Re-enabled proper authentication - UserServices compilation issues resolved | 201 | // Re-enabled proper authentication - UserServices compilation issues resolved |
| 192 | if (!authenticated || !ec.user?.userId) { | 202 | if (!authenticated || !ec.user?.userId) { |
| 193 | logger.warn("Enhanced MCP authentication failed - no valid user authenticated") | 203 | logger.warn("Enhanced MCP authentication failed - authenticated=${authenticated}, userId=${ec.user?.userId}") |
| 194 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) | 204 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) |
| 195 | response.setContentType("application/json") | 205 | response.setContentType("application/json") |
| 196 | response.setHeader("WWW-Authenticate", "Basic realm=\"Moqui MCP\"") | 206 | response.setHeader("WWW-Authenticate", "Basic realm=\"Moqui MCP\"") |
| ... | @@ -202,24 +212,10 @@ try { | ... | @@ -202,24 +212,10 @@ try { |
| 202 | return | 212 | return |
| 203 | } | 213 | } |
| 204 | 214 | ||
| 205 | // Create Visit for JSON-RPC requests too | 215 | // Get Visit created by web facade |
| 206 | def visit = null | 216 | def visit = ec.user.getVisit() |
| 207 | try { | ||
| 208 | // Initialize web facade for Visit creation | ||
| 209 | ec.initWebFacade(webappName, request, response) | ||
| 210 | // Web facade was successful, get Visit it created | ||
| 211 | visit = ec.user.getVisit() | ||
| 212 | if (!visit) { | ||
| 213 | throw new Exception("Web facade succeeded but no Visit created") | ||
| 214 | } | ||
| 215 | } catch (Exception e) { | ||
| 216 | logger.error("Web facade initialization failed - this is a system configuration error: ${e.message}", e) | ||
| 217 | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "System configuration error: Web facade failed to initialize. Check Moqui logs for details.") | ||
| 218 | return | ||
| 219 | } | ||
| 220 | |||
| 221 | // Final check that we have a Visit | ||
| 222 | if (!visit) { | 217 | if (!visit) { |
| 218 | logger.error("Web facade initialized but no Visit created") | ||
| 223 | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to create Visit") | 219 | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to create Visit") |
| 224 | return | 220 | return |
| 225 | } | 221 | } | ... | ... |
| ... | @@ -17,75 +17,7 @@ import org.moqui.context.ExecutionContext | ... | @@ -17,75 +17,7 @@ import org.moqui.context.ExecutionContext |
| 17 | import org.moqui.util.MNode | 17 | import org.moqui.util.MNode |
| 18 | 18 | ||
| 19 | class McpUtils { | 19 | class McpUtils { |
| 20 | /** | 20 | // This class is now primarily a placeholder. All tool-name to screen-path |
| 21 | * Convert a Moqui screen path to an MCP tool name. | 21 | // conversion logic has been removed to simplify the system to a single |
| 22 | * Format: moqui_<Component>_<Path_Parts> | 22 | // moqui_render_screen tool. |
| 23 | * Example: component://PopCommerce/screen/PopCommerceAdmin/Catalog.xml -> moqui_PopCommerce_PopCommerceAdmin_Catalog | ||
| 24 | */ | ||
| 25 | static String getToolName(String screenPath) { | ||
| 26 | if (!screenPath) return null | ||
| 27 | |||
| 28 | String cleanPath = screenPath | ||
| 29 | if (cleanPath.startsWith("component://")) cleanPath = cleanPath.substring(12) | ||
| 30 | if (cleanPath.endsWith(".xml")) cleanPath = cleanPath.substring(0, cleanPath.length() - 4) | ||
| 31 | |||
| 32 | List<String> parts = cleanPath.split('/').toList() | ||
| 33 | if (parts.size() > 1 && parts[1] == "screen") { | ||
| 34 | parts.remove(1) | ||
| 35 | } | ||
| 36 | |||
| 37 | return "moqui_" + parts.join('_') | ||
| 38 | } | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Convert an MCP tool name back to a Moqui screen path. | ||
| 42 | * Assumes standard component://<Component>/screen/<Path>.xml structure. | ||
| 43 | */ | ||
| 44 | static String getScreenPath(String toolName) { | ||
| 45 | if (!toolName || !toolName.startsWith("moqui_")) return null | ||
| 46 | |||
| 47 | String cleanName = toolName.substring(6) // Remove moqui_ | ||
| 48 | List<String> parts = cleanName.split('_').toList() | ||
| 49 | if (parts.size() < 1) return null | ||
| 50 | |||
| 51 | String component = parts[0] | ||
| 52 | if (parts.size() == 1) { | ||
| 53 | return "component://${component}/screen/${component}.xml" | ||
| 54 | } | ||
| 55 | |||
| 56 | String path = parts.subList(1, parts.size()).join('/') | ||
| 57 | return "component://${component}/screen/${path}.xml" | ||
| 58 | } | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Decodes a tool name into a screen path and potential subscreen path by walking the component structure. | ||
| 62 | * This handles cases where a single XML file contains multiple nested subscreens. | ||
| 63 | */ | ||
| 64 | static Map decodeToolName(String toolName, ExecutionContext ec) { | ||
| 65 | if (!toolName || !toolName.startsWith("moqui_")) return [:] | ||
| 66 | |||
| 67 | String cleanName = toolName.substring(6) // Remove moqui_ | ||
| 68 | List<String> parts = cleanName.split('_').toList() | ||
| 69 | String component = parts[0] | ||
| 70 | |||
| 71 | String currentPath = "component://${component}/screen" | ||
| 72 | List<String> subNameParts = [] | ||
| 73 | |||
| 74 | // Walk down the parts to find where the XML file ends and subscreens begin | ||
| 75 | for (int i = 1; i < parts.size(); i++) { | ||
| 76 | String part = parts[i] | ||
| 77 | String nextPath = "${currentPath}/${part}" | ||
| 78 | if (ec.resource.getLocationReference(nextPath + ".xml").getExists()) { | ||
| 79 | currentPath = nextPath | ||
| 80 | subNameParts = [] // Reset subscreens if we found a deeper file | ||
| 81 | } else { | ||
| 82 | subNameParts << part | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | return [ | ||
| 87 | screenPath: currentPath + ".xml", | ||
| 88 | subscreenName: subNameParts ? subNameParts.join("_") : null | ||
| 89 | ] | ||
| 90 | } | ||
| 91 | } | 23 | } | ... | ... |
-
Please register or sign in to post a comment