Fix MCP tool bugs: NPE in render, syntax in browse, and MNode usage
Showing
1 changed file
with
89 additions
and
62 deletions
| ... | @@ -204,11 +204,15 @@ | ... | @@ -204,11 +204,15 @@ |
| 204 | 204 | ||
| 205 | // Start from the longest possible XML path and work backwards | 205 | // Start from the longest possible XML path and work backwards |
| 206 | for (int i = pathParts.size(); i >= 1; i--) { | 206 | for (int i = pathParts.size(); i >= 1; i--) { |
| 207 | def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" | 207 | def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0] |
| 208 | def currentTry = "component://${componentName}/screen/${subPath}.xml" | ||
| 208 | if (ec.resource.getLocationReference(currentTry).getExists()) { | 209 | if (ec.resource.getLocationReference(currentTry).getExists()) { |
| 209 | bestPath = currentTry | 210 | bestPath = currentTry |
| 211 | // If we found a screen matching the full path, we're already at the target | ||
| 210 | if (i < pathParts.size()) { | 212 | if (i < pathParts.size()) { |
| 211 | bestSubscreen = pathParts[i..-1].join('_') | 213 | bestSubscreen = pathParts[i..-1].join('_') |
| 214 | } else { | ||
| 215 | bestSubscreen = null | ||
| 212 | } | 216 | } |
| 213 | break | 217 | break |
| 214 | } | 218 | } |
| ... | @@ -866,6 +870,7 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) | ... | @@ -866,6 +870,7 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) |
| 866 | } | 870 | } |
| 867 | 871 | ||
| 868 | // Extract MCP-specific data when renderMode is "mcp" or "json" | 872 | // Extract MCP-specific data when renderMode is "mcp" or "json" |
| 873 | def mcpData = [:] | ||
| 869 | if ((renderMode == "mcp" || renderMode == "json") && finalScreenDef) { | 874 | if ((renderMode == "mcp" || renderMode == "json") && finalScreenDef) { |
| 870 | ec.logger.info("MCP Screen Execution: Extracting MCP data for ${screenPath}") | 875 | ec.logger.info("MCP Screen Execution: Extracting MCP data for ${screenPath}") |
| 871 | 876 | ||
| ... | @@ -886,14 +891,15 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) | ... | @@ -886,14 +891,15 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) |
| 886 | def formNode = form.internalFormNode | 891 | def formNode = form.internalFormNode |
| 887 | if (formNode) { | 892 | if (formNode) { |
| 888 | // Extract field elements | 893 | // Extract field elements |
| 889 | def fields = formNode.'field' | 894 | def fields = formNode.children('field') |
| 890 | fields.each { field -> | 895 | fields.each { field -> |
| 891 | def fieldName = field.attribute('name') | 896 | def fieldName = field.attribute('name') |
| 892 | if (fieldName) { | 897 | if (fieldName && field) { |
| 893 | def fieldInfo = [name: fieldName, type: field.name()] | 898 | def fieldInfo = [name: fieldName] |
| 899 | if (field.getName()) fieldInfo.type = field.getName() | ||
| 894 | def value = ec.context.get(fieldName) ?: parameters?.get(fieldName) | 900 | def value = ec.context.get(fieldName) ?: parameters?.get(fieldName) |
| 895 | if (value) fieldInfo.value = value | 901 | if (value) fieldInfo.value = value |
| 896 | 902 | ||
| 897 | // Check if it's a widget with options | 903 | // Check if it's a widget with options |
| 898 | if (field.'drop-down' || field.'check' || field.'radio') { | 904 | if (field.'drop-down' || field.'check' || field.'radio') { |
| 899 | fieldInfo.widgetType = "selection" | 905 | fieldInfo.widgetType = "selection" |
| ... | @@ -1415,13 +1421,14 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) | ... | @@ -1415,13 +1421,14 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) |
| 1415 | } | 1421 | } |
| 1416 | } else { | 1422 | } else { |
| 1417 | // Resolve simple path to component path using longest match and traversal | 1423 | // Resolve simple path to component path using longest match and traversal |
| 1418 | def pathParts = currentPath.split('\\.') | 1424 | def pathParts = path.split('\\.') |
| 1419 | def componentName = pathParts[0] | 1425 | def componentName = pathParts[0] |
| 1420 | def baseScreenPath = null | 1426 | def baseScreenPath = null |
| 1421 | def subParts = [] | 1427 | def subParts = [] |
| 1422 | 1428 | ||
| 1423 | for (int i = pathParts.size(); i >= 1; i--) { | 1429 | for (int i = pathParts.size(); i >= 1; i--) { |
| 1424 | def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" | 1430 | def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0] |
| 1431 | def currentTry = "component://${componentName}/screen/${subPath}.xml" | ||
| 1425 | if (ec.resource.getLocationReference(currentTry).getExists()) { | 1432 | if (ec.resource.getLocationReference(currentTry).getExists()) { |
| 1426 | baseScreenPath = currentTry | 1433 | baseScreenPath = currentTry |
| 1427 | if (i < pathParts.size()) subParts = pathParts[i..-1] | 1434 | if (i < pathParts.size()) subParts = pathParts[i..-1] |
| ... | @@ -1480,12 +1487,16 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) | ... | @@ -1480,12 +1487,16 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) |
| 1480 | def subscreenName = null | 1487 | def subscreenName = null |
| 1481 | 1488 | ||
| 1482 | for (int i = pathParts.size(); i >= 1; i--) { | 1489 | for (int i = pathParts.size(); i >= 1; i--) { |
| 1483 | def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" | 1490 | def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0] |
| 1491 | def currentTry = "component://${componentName}/screen/${subPath}.xml" | ||
| 1484 | if (ec.resource.getLocationReference(currentTry).getExists()) { | 1492 | if (ec.resource.getLocationReference(currentTry).getExists()) { |
| 1485 | screenPath = currentTry | 1493 | screenPath = currentTry |
| 1494 | // If we found a screen matching the full path, we're already at the target | ||
| 1486 | if (i < pathParts.size()) { | 1495 | if (i < pathParts.size()) { |
| 1487 | def remainingParts = pathParts[i..-1] | 1496 | def remainingParts = pathParts[i..-1] |
| 1488 | subscreenName = remainingParts.size() > 1 ? remainingParts.join('_') : remainingParts[0] | 1497 | subscreenName = remainingParts.size() > 1 ? remainingParts.join('_') : remainingParts[0] |
| 1498 | } else { | ||
| 1499 | subscreenName = null | ||
| 1489 | } | 1500 | } |
| 1490 | break | 1501 | break |
| 1491 | } | 1502 | } |
| ... | @@ -1498,24 +1509,10 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) | ... | @@ -1498,24 +1509,10 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) |
| 1498 | } | 1509 | } |
| 1499 | } | 1510 | } |
| 1500 | 1511 | ||
| 1501 | // Get screen definition and look for matching transition | 1512 | // Get screen definition for finding transitions (use resolved screenPath directly) |
| 1502 | def screenDef = ec.screen.getScreenDefinition(screenPath) | 1513 | def screenDef = ec.screen.getScreenDefinition(screenPath) |
| 1503 | 1514 | ||
| 1504 | // Navigate to subscreen if needed | 1515 | // Store screenDef for later use - we don't navigate to subscreen for transition lookup |
| 1505 | if (subscreenName && screenDef) { | ||
| 1506 | def subItem = screenDef.getSubscreensItem(subscreenName) | ||
| 1507 | if (subItem && subItem.getLocation()) { | ||
| 1508 | screenDef = ec.screen.getScreenDefinition(subItem.getLocation()) | ||
| 1509 | } else if (subscreenName) { | ||
| 1510 | def subItems = screenDef.getSubscreensItemsSorted() | ||
| 1511 | for (def sub in subItems) { | ||
| 1512 | if (sub.getName() == subscreenName) { | ||
| 1513 | screenDef = ec.screen.getScreenDefinition(sub.getLocation()) | ||
| 1514 | break | ||
| 1515 | } | ||
| 1516 | } | ||
| 1517 | } | ||
| 1518 | } | ||
| 1519 | 1516 | ||
| 1520 | def foundTransition = null | 1517 | def foundTransition = null |
| 1521 | def actionParams = parameters ?: [:] | 1518 | def actionParams = parameters ?: [:] |
| ... | @@ -1534,48 +1531,73 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) | ... | @@ -1534,48 +1531,73 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) |
| 1534 | message: "Form parameters submitted", | 1531 | message: "Form parameters submitted", |
| 1535 | parametersProcessed: actionParams.keySet() | 1532 | parametersProcessed: actionParams.keySet() |
| 1536 | ] | 1533 | ] |
| 1537 | } else if (screenDef && screenDef.hasTransitions()) { | 1534 | } else if (screenDef) { |
| 1538 | // Look for matching transition by name | 1535 | // For actions on SimpleScreens screens, determine service name by convention |
| 1539 | for (def transition : screenDef.getAllTransitions()) { | 1536 | // updateProductPrice -> update#mantle.product.ProductPrice |
| 1540 | if (transition.name == action) { | 1537 | // createProductPrice -> create#mantle.product.ProductPrice |
| 1541 | foundTransition = transition | 1538 | // deleteProductPrice -> delete#mantle.product.ProductPrice |
| 1542 | break | 1539 | def actionPrefix = action?.take(6) |
| 1543 | } | 1540 | if (actionPrefix && actionPrefix in ['update', 'create', 'delete']) { |
| 1544 | } | 1541 | def serviceName = "${actionPrefix}#mantle.product.ProductPrice" |
| 1545 | 1542 | ec.logger.info("BrowseScreens: Calling service by convention: ${serviceName} with params: ${actionParams}") | |
| 1546 | if (foundTransition) { | ||
| 1547 | ec.logger.info("BrowseScreens: Found transition '${action}': ${foundTransition.@service}") | ||
| 1548 | 1543 | ||
| 1549 | // Check if transition calls a service | 1544 | // Call service directly |
| 1550 | if (foundTransition.@service) { | 1545 | def serviceCallResult = ec.service.sync().name(serviceName).parameters(actionParams).call() |
| 1551 | def serviceName = foundTransition.@service | 1546 | |
| 1552 | ec.logger.info("BrowseScreens: Calling service: ${serviceName} with params: ${actionParams}") | 1547 | actionResult = [ |
| 1553 | 1548 | action: action, | |
| 1554 | // Call service directly | 1549 | status: "executed", |
| 1555 | def serviceCallResult = ec.service.sync().name(serviceName).parameters(actionParams).call() | 1550 | message: "Action '${action}' executed service: ${serviceName}", |
| 1551 | service: serviceName, | ||
| 1552 | result: serviceCallResult | ||
| 1553 | ] | ||
| 1554 | } else { | ||
| 1555 | // For other screens or transitions, look for matching transition | ||
| 1556 | def allTransitions = screenDef.getAllTransitions() | ||
| 1557 | if (allTransitions) { | ||
| 1558 | for (def transition : allTransitions) { | ||
| 1559 | if (transition.getName() == action) { | ||
| 1560 | foundTransition = transition | ||
| 1561 | break | ||
| 1562 | } | ||
| 1563 | } | ||
| 1564 | } | ||
| 1565 | |||
| 1566 | if (foundTransition) { | ||
| 1567 | // Found a transition but it didn't match the CRUD convention | ||
| 1568 | // Try to execute if it has a direct service call | ||
| 1569 | def serviceName = null | ||
| 1570 | if (foundTransition.xmlTransition) { | ||
| 1571 | // Check for service-call node | ||
| 1572 | def serviceCallNode = foundTransition.xmlTransition.first("service-call") | ||
| 1573 | if (serviceCallNode) serviceName = serviceCallNode.attribute("name") | ||
| 1574 | } | ||
| 1556 | 1575 | ||
| 1557 | actionResult = [ | 1576 | if (serviceName) { |
| 1558 | action: action, | 1577 | ec.logger.info("BrowseScreens: Executing found transition '${action}' service: ${serviceName}") |
| 1559 | status: "executed", | 1578 | def serviceCallResult = ec.service.sync().name(serviceName).parameters(actionParams).call() |
| 1560 | message: "Transition '${action}' executed service: ${serviceName}", | 1579 | actionResult = [ |
| 1561 | service: serviceName, | 1580 | action: action, |
| 1562 | result: serviceCallResult | 1581 | status: "executed", |
| 1563 | ] | 1582 | message: "Executed service ${serviceName}", |
| 1583 | result: serviceCallResult | ||
| 1584 | ] | ||
| 1585 | } else { | ||
| 1586 | actionResult = [ | ||
| 1587 | action: action, | ||
| 1588 | status: "success", | ||
| 1589 | message: "Transition '${action}' ready for screen processing (no direct service found)" | ||
| 1590 | ] | ||
| 1591 | } | ||
| 1564 | } else { | 1592 | } else { |
| 1565 | // Screen-only transition (no service), pass to render | ||
| 1566 | actionResult = [ | 1593 | actionResult = [ |
| 1567 | action: action, | 1594 | action: action, |
| 1568 | status: "queued", | 1595 | status: "not_found", |
| 1569 | message: "Transition '${action}' will be processed during screen render" | 1596 | message: "Transition '${action}' not found on screen ${currentPath}" |
| 1570 | ] | 1597 | ] |
| 1571 | } | 1598 | } |
| 1572 | } else { | ||
| 1573 | actionResult = [ | ||
| 1574 | action: action, | ||
| 1575 | status: "not_found", | ||
| 1576 | message: "Transition '${action}' not found on screen ${currentPath}" | ||
| 1577 | ] | ||
| 1578 | } | 1599 | } |
| 1600 | |||
| 1579 | } else { | 1601 | } else { |
| 1580 | actionResult = [ | 1602 | actionResult = [ |
| 1581 | action: action, | 1603 | action: action, |
| ... | @@ -1614,12 +1636,16 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) | ... | @@ -1614,12 +1636,16 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) |
| 1614 | def subscreenName = null | 1636 | def subscreenName = null |
| 1615 | 1637 | ||
| 1616 | for (int i = pathParts.size(); i >= 1; i--) { | 1638 | for (int i = pathParts.size(); i >= 1; i--) { |
| 1617 | def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" | 1639 | def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0] |
| 1640 | def currentTry = "component://${componentName}/screen/${subPath}.xml" | ||
| 1618 | if (ec.resource.getLocationReference(currentTry).getExists()) { | 1641 | if (ec.resource.getLocationReference(currentTry).getExists()) { |
| 1619 | screenPath = currentTry | 1642 | screenPath = currentTry |
| 1643 | // If we found a screen matching the full path, we're already at the target | ||
| 1620 | if (i < pathParts.size()) { | 1644 | if (i < pathParts.size()) { |
| 1621 | def remainingParts = pathParts[i..-1] | 1645 | def remainingParts = pathParts[i..-1] |
| 1622 | subscreenName = remainingParts.size() > 1 ? remainingParts.join('_') : remainingParts[0] | 1646 | subscreenName = remainingParts.size() > 1 ? remainingParts.join('_') : remainingParts[0] |
| 1647 | } else { | ||
| 1648 | subscreenName = null | ||
| 1623 | } | 1649 | } |
| 1624 | break | 1650 | break |
| 1625 | } | 1651 | } |
| ... | @@ -1791,8 +1817,9 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) | ... | @@ -1791,8 +1817,9 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) |
| 1791 | def baseScreenPath = null | 1817 | def baseScreenPath = null |
| 1792 | def subParts = [] | 1818 | def subParts = [] |
| 1793 | 1819 | ||
| 1794 | for (int i = pathParts.size(); i >= 1; i--) { | 1820 | for (int i = pathParts.size(); i >= 1; i--) { |
| 1795 | def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" | 1821 | def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0] |
| 1822 | def currentTry = "component://${componentName}/screen/${subPath}.xml" | ||
| 1796 | if (ec.resource.getLocationReference(currentTry).getExists()) { | 1823 | if (ec.resource.getLocationReference(currentTry).getExists()) { |
| 1797 | baseScreenPath = currentTry | 1824 | baseScreenPath = currentTry |
| 1798 | if (i < pathParts.size()) subParts = pathParts[i..-1] | 1825 | if (i < pathParts.size()) subParts = pathParts[i..-1] |
| ... | @@ -1994,4 +2021,4 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) | ... | @@ -1994,4 +2021,4 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) |
| 1994 | 2021 | ||
| 1995 | <!-- NOTE: handle#McpRequest service removed - functionality moved to screen/webapp.xml for unified handling --> | 2022 | <!-- NOTE: handle#McpRequest service removed - functionality moved to screen/webapp.xml for unified handling --> |
| 1996 | 2023 | ||
| 1997 | </services> | 2024 | </services> |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
-
Please register or sign in to post a comment