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 { ...@@ -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 }
......