Fix screen execution notification queuing - actually queue results instead of just logging
Showing
1 changed file
with
27 additions
and
532 deletions
| ... | @@ -758,535 +758,6 @@ try { | ... | @@ -758,535 +758,6 @@ try { |
| 758 | </actions> | 758 | </actions> |
| 759 | </service> | 759 | </service> |
| 760 | 760 | ||
| 761 | |||
| 762 | |||
| 763 | |||
| 764 | <!-- Screen-based MCP Services --> | ||
| 765 | |||
| 766 | <service verb="discover" noun="ScreensAsMcpTools" authenticate="false" allow-remote="true" transaction-timeout="60"> | ||
| 767 | <description>Discover screens accessible to user and convert them to MCP tools</description> | ||
| 768 | <in-parameters> | ||
| 769 | <parameter name="sessionId"/> | ||
| 770 | <parameter name="screenPathPattern" required="false"><description>Optional pattern to filter screen paths (supports wildcards)</description></parameter> | ||
| 771 | </in-parameters> | ||
| 772 | <out-parameters> | ||
| 773 | <parameter name="tools" type="List"/> | ||
| 774 | </out-parameters> | ||
| 775 | <actions> | ||
| 776 | <script><![CDATA[ | ||
| 777 | import org.moqui.context.ExecutionContext | ||
| 778 | import org.moqui.impl.context.UserFacadeImpl.UserInfo | ||
| 779 | import org.moqui.impl.screen.ScreenDefinition | ||
| 780 | |||
| 781 | ExecutionContext ec = context.ec | ||
| 782 | |||
| 783 | ec.logger.info("=== SCREEN DISCOVERY SERVICE CALLED ===") | ||
| 784 | |||
| 785 | def originalUsername = ec.user.username | ||
| 786 | def originalUserId = ec.user.userId | ||
| 787 | def userGroups = ec.user.getUserGroupIdSet().collect { it } | ||
| 788 | |||
| 789 | ec.logger.info("MCP Screen Discovery: Starting for user ${originalUsername} (${originalUserId}) with groups ${userGroups}") | ||
| 790 | |||
| 791 | def tools = [] | ||
| 792 | |||
| 793 | // Discover screens that user can actually access | ||
| 794 | def accessibleScreens = [] as Set<String> | ||
| 795 | adminUserInfo = null | ||
| 796 | try { | ||
| 797 | adminUserInfo = ec.user.pushUser("ADMIN") | ||
| 798 | |||
| 799 | // Get all user's accessible screens using ArtifactAuthzCheckView | ||
| 800 | def aacvList = ec.entity.find("moqui.security.ArtifactAuthzCheckView") | ||
| 801 | .condition("userGroupId", userGroups) | ||
| 802 | .condition("artifactTypeEnumId", "AT_XML_SCREEN") | ||
| 803 | .useCache(true) | ||
| 804 | .disableAuthz() | ||
| 805 | .list() | ||
| 806 | accessibleScreens = aacvList.collect { it.artifactName } as Set<String> | ||
| 807 | |||
| 808 | ec.logger.info("MCP Screen Discovery: Found ${accessibleScreens.size()} accessible screens for user ${originalUsername}") | ||
| 809 | |||
| 810 | } finally { | ||
| 811 | if (adminUserInfo != null) { | ||
| 812 | ec.user.popUser() | ||
| 813 | } | ||
| 814 | } | ||
| 815 | |||
| 816 | // Helper function to check if user has permission to a screen | ||
| 817 | def userHasScreenPermission = { screenName -> | ||
| 818 | return accessibleScreens.contains(screenName.toString()) | ||
| 819 | } | ||
| 820 | |||
| 821 | // Helper function to convert screen path to MCP tool name | ||
| 822 | def screenPathToToolName = { screenPath -> | ||
| 823 | // Clean Encoding: strip component:// and .xml, replace / with _ | ||
| 824 | // Preserves hyphens for readability. | ||
| 825 | // component://moqui-mcp-2/screen/McpTestScreen.xml -> screen_moqui-mcp-2_screen_McpTestScreen | ||
| 826 | def cleanPath = screenPath | ||
| 827 | if (cleanPath.startsWith("component://")) cleanPath = cleanPath.substring(12) | ||
| 828 | if (cleanPath.endsWith(".xml")) cleanPath = cleanPath.substring(0, cleanPath.length() - 4) | ||
| 829 | |||
| 830 | return "screen_" + cleanPath.replace('/', '_') | ||
| 831 | } | ||
| 832 | |||
| 833 | // Helper function to convert screen path to MCP tool name with subscreen support | ||
| 834 | def screenPathToToolNameWithSubscreens = { screenPath, parentScreenPath = null -> | ||
| 835 | // If we have a parent screen path, this is a subscreen - use dot notation | ||
| 836 | if (parentScreenPath) { | ||
| 837 | def parentCleanPath = parentScreenPath | ||
| 838 | if (parentCleanPath.startsWith("component://")) parentCleanPath = parentCleanPath.substring(12) | ||
| 839 | if (parentCleanPath.endsWith(".xml")) parentCleanPath = parentCleanPath.substring(0, parentCleanPath.length() - 4) | ||
| 840 | |||
| 841 | // Extract subscreen name from the full screen path | ||
| 842 | def subscreenName = screenPath.split("/")[-1] | ||
| 843 | if (subscreenName.endsWith(".xml")) subscreenName = subscreenName.substring(0, subscreenName.length() - 4) | ||
| 844 | |||
| 845 | return "screen_" + parentCleanPath.replace('/', '_') + "." + subscreenName | ||
| 846 | } | ||
| 847 | |||
| 848 | // Regular screen path conversion for main screens | ||
| 849 | return screenPathToToolName(screenPath) | ||
| 850 | } | ||
| 851 | |||
| 852 | // Helper function to create MCP tool from screen | ||
| 853 | def createScreenTool = { screenPath, title, description, parameters = [:] -> | ||
| 854 | def toolName = screenPathToToolName(screenPath) | ||
| 855 | return [ | ||
| 856 | name: toolName, | ||
| 857 | title: title, | ||
| 858 | description: title, // Use title as description | ||
| 859 | inputSchema: [ | ||
| 860 | type: "object", | ||
| 861 | properties: parameters, | ||
| 862 | required: [] | ||
| 863 | ] | ||
| 864 | ] | ||
| 865 | } | ||
| 866 | |||
| 867 | // Helper function to recursively process screens and create tools directly | ||
| 868 | def processScreenWithSubscreens | ||
| 869 | processScreenWithSubscreens = { screenPath, parentScreenPath = null, processedScreens = null, toolsAccumulator = null, parentToolName = null, level = 1 -> | ||
| 870 | ec.logger.info("MCP Screen Discovery: Processing screen ${screenPath} (parent: ${parentScreenPath}, parentToolName: ${parentToolName}, level: ${level})") | ||
| 871 | |||
| 872 | // Initialize processedScreens and toolsAccumulator if null | ||
| 873 | if (processedScreens == null) processedScreens = [] as Set<String> | ||
| 874 | if (toolsAccumulator == null) toolsAccumulator = [] | ||
| 875 | |||
| 876 | if (processedScreens.contains(screenPath)) { | ||
| 877 | ec.logger.info("MCP Screen Discovery: Already processed ${screenPath}, skipping") | ||
| 878 | return | ||
| 879 | } | ||
| 880 | |||
| 881 | processedScreens.add(screenPath) | ||
| 882 | |||
| 883 | try { | ||
| 884 | // Skip problematic patterns early | ||
| 885 | if (screenPath.contains("/error/") || screenPath.contains("/system/")) { | ||
| 886 | ec.logger.info("MCP Screen Discovery: Skipping system screen ${screenPath}") | ||
| 887 | return | ||
| 888 | } | ||
| 889 | |||
| 890 | // Determine if this is a subscreen | ||
| 891 | def isSubscreen = parentScreenPath != null | ||
| 892 | |||
| 893 | // Try to get screen definition | ||
| 894 | def screenDefinition = null | ||
| 895 | def title = screenPath.split("/")[-1] | ||
| 896 | def description = "Moqui screen: ${screenPath}" | ||
| 897 | |||
| 898 | try { | ||
| 899 | screenDefinition = ec.screen.getScreenDefinition(screenPath) | ||
| 900 | if (screenDefinition?.screenNode?.attribute('default-menu-title')) { | ||
| 901 | title = screenDefinition.screenNode.attribute('default-menu-title') | ||
| 902 | description = "Moqui screen: ${screenPath} (${title})" | ||
| 903 | } | ||
| 904 | } catch (Exception e) { | ||
| 905 | ec.logger.info("MCP Screen Discovery: No screen definition for ${screenPath}, using basic info") | ||
| 906 | } | ||
| 907 | |||
| 908 | // Get screen parameters from transitions | ||
| 909 | def parameters = [:] | ||
| 910 | try { | ||
| 911 | def screenInfo = ec.screen.getScreenInfo(screenPath) | ||
| 912 | if (screenInfo?.transitionInfoByName) { | ||
| 913 | for (transitionEntry in screenInfo.transitionInfoByName) { | ||
| 914 | def transitionInfo = transitionEntry.value | ||
| 915 | if (transitionInfo?.ti) { | ||
| 916 | transitionInfo.ti.getPathParameterList()?.each { param -> | ||
| 917 | parameters[param] = [ | ||
| 918 | type: "string", | ||
| 919 | description: "Path parameter for transition: ${param}" | ||
| 920 | ] | ||
| 921 | } | ||
| 922 | transitionInfo.ti.getRequestParameterList()?.each { param -> | ||
| 923 | parameters[param.name] = [ | ||
| 924 | type: "string", | ||
| 925 | description: "Request parameter: ${param.name}" | ||
| 926 | ] | ||
| 927 | } | ||
| 928 | } | ||
| 929 | } | ||
| 930 | } | ||
| 931 | } catch (Exception e) { | ||
| 932 | ec.logger.debug("Could not extract parameters from screen ${screenPath}: ${e.message}") | ||
| 933 | } | ||
| 934 | |||
| 935 | // Create tool with proper naming | ||
| 936 | def toolName | ||
| 937 | if (isSubscreen && parentToolName) { | ||
| 938 | // Use the passed hierarchical parent tool name | ||
| 939 | def subscreenName = screenPath.split("/")[-1] | ||
| 940 | if (subscreenName.endsWith(".xml")) subscreenName = subscreenName.substring(0, subscreenName.length() - 4) | ||
| 941 | |||
| 942 | // Use dot for first level subscreens (level 1), underscore for deeper levels (level 2+) | ||
| 943 | def separator = (level == 1) ? "." : "_" | ||
| 944 | toolName = parentToolName + separator + subscreenName | ||
| 945 | ec.logger.info("MCP Screen Discovery: Creating subscreen tool ${toolName} for ${screenPath} (parentToolName: ${parentToolName}, level: ${level}, separator: ${separator})") | ||
| 946 | } else if (isSubscreen && parentScreenPath) { | ||
| 947 | toolName = screenPathToToolNameWithSubscreens(screenPath, parentScreenPath) | ||
| 948 | ec.logger.info("MCP Screen Discovery: Creating subscreen tool ${toolName} for ${screenPath} (parent: ${parentScreenPath})") | ||
| 949 | } else { | ||
| 950 | toolName = screenPathToToolName(screenPath) | ||
| 951 | ec.logger.info("MCP Screen Discovery: Creating main screen tool ${toolName} for ${screenPath}") | ||
| 952 | } | ||
| 953 | |||
| 954 | def tool = [ | ||
| 955 | name: toolName, | ||
| 956 | title: title, | ||
| 957 | description: title, // Use title as description instead of redundant path | ||
| 958 | inputSchema: [ | ||
| 959 | type: "object", | ||
| 960 | properties: parameters, | ||
| 961 | required: [] | ||
| 962 | ] | ||
| 963 | ] | ||
| 964 | |||
| 965 | ec.logger.info("MCP Screen Discovery: Adding accessible screen tool ${toolName} for ${screenPath}") | ||
| 966 | toolsAccumulator << tool | ||
| 967 | |||
| 968 | // Recursively process subscreens | ||
| 969 | try { | ||
| 970 | def screenInfoList = ec.screen.getScreenInfoList(screenPath, 1) | ||
| 971 | def screenInfo = screenInfoList?.first() | ||
| 972 | ec.logger.info("MCP Screen Discovery: SCREENINFO for ${screenPath}: ${screenInfo}") | ||
| 973 | if (screenInfo?.subscreenInfoByName) { | ||
| 974 | ec.logger.info("MCP Screen Discovery: Found ${screenInfo.subscreenInfoByName.size()} subscreens for ${screenPath}: ${screenInfo.subscreenInfoByName.keySet()}") | ||
| 975 | for (subScreenEntry in screenInfo.subscreenInfoByName) { | ||
| 976 | ec.logger.info("MCP Screen Discovery: ===== Processing subscreen ${subScreenEntry.key} from parent ${screenPath} =====") | ||
| 977 | def subScreenInfo = subScreenEntry.value | ||
| 978 | def subScreenPathList = subScreenInfo?.screenPath | ||
| 979 | ec.logger.info("MCP Screen Discovery: Processing subscreen entry - key: ${subScreenEntry.key}, location: ${subScreenPathList}, full info: ${subScreenInfo}") | ||
| 980 | |||
| 981 | // TODO: Fix these hard coded discoveries | ||
| 982 | // Special debug for Catalog.xml | ||
| 983 | if (screenPath.contains("Catalog.xml")) { | ||
| 984 | ec.logger.info("MCP Screen Discovery: *** CATALOG DEBUG *** Processing ${subScreenEntry.key} from Catalog.xml") | ||
| 985 | } | ||
| 986 | |||
| 987 | // Special handling for known Catalog.xml subscreens that point to SimpleScreens | ||
| 988 | def knownLocations = [ | ||
| 989 | "dashboard": "component://SimpleScreens/screen/SimpleScreens/Catalog/dashboard.xml", | ||
| 990 | "Category": "component://SimpleScreens/screen/SimpleScreens/Catalog/Category.xml", | ||
| 991 | "Feature": "component://SimpleScreens/screen/SimpleScreens/Catalog/Feature.xml", | ||
| 992 | "FeatureGroup": "component://SimpleScreens/screen/SimpleScreens/Catalog/FeatureGroup.xml", | ||
| 993 | "Product": "component://SimpleScreens/screen/SimpleScreens/Catalog/Product.xml", | ||
| 994 | "Search": "component://SimpleScreens/screen/SimpleScreens/Catalog/Search.xml" | ||
| 995 | ] | ||
| 996 | |||
| 997 | // Try to get the actual subscreen location from multiple sources | ||
| 998 | def actualSubScreenPath = null | ||
| 999 | |||
| 1000 | // First, try known locations for Catalog.xml subscreens | ||
| 1001 | if (screenPath.contains("Catalog.xml") && knownLocations[subScreenEntry.key]) { | ||
| 1002 | actualSubScreenPath = knownLocations[subScreenEntry.key] | ||
| 1003 | ec.logger.info("MCP Screen Discovery: Using known location for ${subScreenEntry.key}: ${actualSubScreenPath}") | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | // Then try to get location from subScreenInfo object (most reliable) | ||
| 1007 | if (!actualSubScreenPath && subScreenInfo?.screenPath) { | ||
| 1008 | // subScreenInfo.screenPath might be a list or string | ||
| 1009 | if (subScreenInfo.screenPath instanceof List) { | ||
| 1010 | // For automatic discovery screens, the path might be constructed differently | ||
| 1011 | def pathList = subScreenInfo.screenPath | ||
| 1012 | ec.logger.info("MCP Screen Discovery: SubScreenInfo path list for ${subScreenEntry.key}: ${pathList}") | ||
| 1013 | |||
| 1014 | // Try to find a valid screen path from the list | ||
| 1015 | for (path in pathList) { | ||
| 1016 | if (path && path.toString().contains(".xml")) { | ||
| 1017 | actualSubScreenPath = path.toString() | ||
| 1018 | break | ||
| 1019 | } | ||
| 1020 | } | ||
| 1021 | } else { | ||
| 1022 | actualSubScreenPath = subScreenInfo.screenPath.toString() | ||
| 1023 | } | ||
| 1024 | ec.logger.info("MCP Screen Discovery: SubScreenInfo location for ${subScreenEntry.key}: ${actualSubScreenPath}") | ||
| 1025 | } | ||
| 1026 | |||
| 1027 | |||
| 1028 | } catch (Exception childrenException) { | ||
| 1029 | ec.logger.info("MCP Screen Discovery: children() approach failed: ${childrenException.message}") | ||
| 1030 | // Fallback: iterate through node values | ||
| 1031 | subscreensNode.each { child -> | ||
| 1032 | if (child.name() == "subscreens-item") { | ||
| 1033 | subscreenItems << child | ||
| 1034 | } | ||
| 1035 | } | ||
| 1036 | } | ||
| 1037 | } | ||
| 1038 | |||
| 1039 | ec.logger.info("MCP Screen Discovery: Found ${subscreenItems.size()} subscreen-item elements: ${subscreenItems*.attributes()}") | ||
| 1040 | def subscreenItem = subscreenItems.find { | ||
| 1041 | it.attribute('name') == subScreenEntry.key | ||
| 1042 | } | ||
| 1043 | ec.logger.info("MCP Screen Discovery: Looking for subscreen item with name '${subScreenEntry.key}', found: ${subscreenItem?.attributes()}") | ||
| 1044 | if (subscreenItem?.attribute('location')) { | ||
| 1045 | actualSubScreenPath = subscreenItem.attribute('location') | ||
| 1046 | ec.logger.info("MCP Screen Discovery: Found actual subscreen location for ${subScreenEntry.key}: ${actualSubScreenPath}") | ||
| 1047 | } else { | ||
| 1048 | ec.logger.info("MCP Screen Discovery: Subscreen item found but no location attribute") | ||
| 1049 | } | ||
| 1050 | } | ||
| 1051 | } else { | ||
| 1052 | ec.logger.info("MCP Screen Discovery: Parent screen def has no screenNode") | ||
| 1053 | } | ||
| 1054 | } catch (Exception e) { | ||
| 1055 | ec.logger.info("MCP Screen Discovery: Could not get subscreen location for ${subScreenEntry.key}: ${e.message}") | ||
| 1056 | e.printStackTrace() | ||
| 1057 | } | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | // Fallback: try to construct from screenPathList if we couldn't get the actual location | ||
| 1061 | if (!actualSubScreenPath && subScreenPathList) { | ||
| 1062 | // The first element should be subscreen name | ||
| 1063 | def subscreenName = subScreenEntry.key | ||
| 1064 | // Try to construct a reasonable path based on CURRENT screen being processed (not always the original parent) | ||
| 1065 | def currentScreenPath = screenPath // This is the current screen whose subscreens we're processing | ||
| 1066 | |||
| 1067 | // Generic fallback: construct based on current screen path | ||
| 1068 | def lastSlash = currentScreenPath.lastIndexOf('/') | ||
| 1069 | if (lastSlash > 0) { | ||
| 1070 | def basePath = currentScreenPath.substring(0, lastSlash + 1) | ||
| 1071 | actualSubScreenPath = basePath + subscreenName + ".xml" | ||
| 1072 | } | ||
| 1073 | ec.logger.info("MCP Screen Discovery: Constructed fallback subscreen location for ${subScreenEntry.key}: ${actualSubScreenPath}") | ||
| 1074 | } | ||
| 1075 | |||
| 1076 | if (actualSubScreenPath && !processedScreens.contains(actualSubScreenPath)) { | ||
| 1077 | ec.logger.info("MCP Screen Discovery: Processing subscreen path: ${actualSubScreenPath} ${screenPath}") | ||
| 1078 | processScreenWithSubscreens(actualSubScreenPath, screenPath, processedScreens, toolsAccumulator, toolName, level + 1) | ||
| 1079 | } else if (!actualSubScreenPath) { | ||
| 1080 | ec.logger.info("MCP Screen Discovery: Subscreen entry ${subScreenEntry.key} has no location, trying automatic discovery") | ||
| 1081 | // For screens without explicit location (like Product.xml), try automatic discovery | ||
| 1082 | // The subscreen location is typically based on parent screen location + subscreen name | ||
| 1083 | def lastSlash = screenPath.lastIndexOf('/') | ||
| 1084 | if (lastSlash > 0) { | ||
| 1085 | def basePath = screenPath.substring(0, lastSlash + 1) | ||
| 1086 | def autoSubScreenPath = basePath + subScreenEntry.key + ".xml" | ||
| 1087 | ec.logger.info("MCP Screen Discovery: Trying automatic subscreen discovery for ${subScreenEntry.key} at ${autoSubScreenPath}") | ||
| 1088 | |||
| 1089 | processScreenWithSubscreens(autoSubScreenPath, screenPath, processedScreens, toolsAccumulator, toolName, level + 1) | ||
| 1090 | } | ||
| 1091 | } else if (processedScreens.contains(actualSubScreenPath)) { | ||
| 1092 | ec.logger.info("MCP Screen Discovery: Subscreen ${actualSubScreenPath} already processed, skipping") | ||
| 1093 | } | ||
| 1094 | } | ||
| 1095 | } else { | ||
| 1096 | ec.logger.info("MCP Screen Discovery: No subscreens found for ${screenPath}") | ||
| 1097 | } | ||
| 1098 | } catch (Exception e) { | ||
| 1099 | ec.logger.info("MCP Screen Discovery: Could not get subscreens for ${screenPath}: ${e.message}") | ||
| 1100 | ec.logger.error("MCP Screen Discovery: Subscreen discovery error details:", e) | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | } catch (Exception e) { | ||
| 1104 | ec.logger.warn("Error processing screen ${screenPath}: ${e.message}") | ||
| 1105 | } | ||
| 1106 | } | ||
| 1107 | |||
| 1108 | // Process all accessible screens recursively and create tools directly | ||
| 1109 | def processedScreens = [] as Set<String> | ||
| 1110 | ec.logger.info("MCP Screen Discovery: Starting recursive processing from ${accessibleScreens.size()} base screens") | ||
| 1111 | for (screenPath in accessibleScreens) { | ||
| 1112 | ec.logger.info("MCP Screen Discovery: SCREEN PATH ${screenPath}") | ||
| 1113 | processScreenWithSubscreens(screenPath, null, processedScreens, tools, null, 0) | ||
| 1114 | } | ||
| 1115 | ec.logger.info("MCP Screen Discovery: Recursive processing found ${tools.size()} total tools") | ||
| 1116 | |||
| 1117 | // Note: All screens have already been processed by processScreenWithSubscreens above | ||
| 1118 | // The recursive approach handles both parent screens and their subscreens in a single pass | ||
| 1119 | // No need for additional processing here | ||
| 1120 | |||
| 1121 | |||
| 1122 | ec.logger.info("MCP Screen Discovery: Created ${tools.size()} screen tools for user ${originalUsername}") | ||
| 1123 | |||
| 1124 | result.tools = tools | ||
| 1125 | |||
| 1126 | ]]></script> | ||
| 1127 | </actions> | ||
| 1128 | </service> | ||
| 1129 | |||
| 1130 | |||
| 1131 | |||
| 1132 | <service verb="convert" noun="ScreenToMcpTool" authenticate="false"> | ||
| 1133 | <description>Convert a screen path to MCP tool format</description> | ||
| 1134 | <in-parameters> | ||
| 1135 | <parameter name="screenPath" required="true"/> | ||
| 1136 | </in-parameters> | ||
| 1137 | <out-parameters> | ||
| 1138 | <parameter name="tool" type="Map"/> | ||
| 1139 | </out-parameters> | ||
| 1140 | <actions> | ||
| 1141 | <script><![CDATA[ | ||
| 1142 | import org.moqui.context.ExecutionContext | ||
| 1143 | |||
| 1144 | ExecutionContext ec = context.ec | ||
| 1145 | |||
| 1146 | ec.logger.info("=== SCREEN TO MCP TOOL: ${screenPath} ===") | ||
| 1147 | |||
| 1148 | tool = null | ||
| 1149 | try { | ||
| 1150 | // Try to get screen definition | ||
| 1151 | def screenDef = null | ||
| 1152 | try { | ||
| 1153 | ec.logger.info("SCREEN TO MCP: Getting screen definition for ${screenPath}") | ||
| 1154 | screenDef = ec.screen.getScreenDefinition(screenPath) | ||
| 1155 | ec.logger.info("SCREEN TO MCP: Got screen definition: ${screenDef ? 'YES' : 'NO'}") | ||
| 1156 | } catch (Exception e) { | ||
| 1157 | ec.logger.warn("SCREEN TO MCP: Error getting screen definition: ${e.message}") | ||
| 1158 | // Screen might not exist or be accessible | ||
| 1159 | return | ||
| 1160 | } | ||
| 1161 | |||
| 1162 | if (!screenDef) { | ||
| 1163 | return | ||
| 1164 | } | ||
| 1165 | |||
| 1166 | // Extract screen information | ||
| 1167 | // Clean Encoding | ||
| 1168 | def cleanPath = screenPath | ||
| 1169 | if (cleanPath.startsWith("component://")) cleanPath = cleanPath.substring(12) | ||
| 1170 | if (cleanPath.endsWith(".xml")) cleanPath = cleanPath.substring(0, cleanPath.length() - 4) | ||
| 1171 | |||
| 1172 | def screenName = cleanPath.replace('/', '_') | ||
| 1173 | def title = screenPath.split("/")[-1] | ||
| 1174 | def description = "Moqui screen: ${screenPath}" | ||
| 1175 | |||
| 1176 | // Safely get screen description - screen XML doesn't have description elements | ||
| 1177 | try { | ||
| 1178 | if (screenDef?.screenNode?.attribute('default-menu-title')) { | ||
| 1179 | description = screenDef.screenNode.attribute('default-menu-title') | ||
| 1180 | } | ||
| 1181 | } catch (Exception e) { | ||
| 1182 | ec.logger.debug("Could not get screen title: ${e.message}") | ||
| 1183 | } | ||
| 1184 | |||
| 1185 | // Get screen parameters from transitions and forms | ||
| 1186 | def parameters = [:] | ||
| 1187 | def required = [] | ||
| 1188 | |||
| 1189 | try { | ||
| 1190 | // Get transitions for parameter discovery | ||
| 1191 | def transitions = screenDef.getTransitionMap() | ||
| 1192 | transitions.each { transitionName, transition -> | ||
| 1193 | transition.getPathParameterList().each { param -> | ||
| 1194 | parameters[param] = [ | ||
| 1195 | type: "string", | ||
| 1196 | description: "Path parameter: ${param}" | ||
| 1197 | ] | ||
| 1198 | required << param | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | // Get single service parameters if transition calls a service | ||
| 1202 | def serviceName = transition.getSingleServiceName() | ||
| 1203 | if (serviceName) { | ||
| 1204 | try { | ||
| 1205 | def serviceDef = ec.service.getServiceDefinition(serviceName) | ||
| 1206 | if (serviceDef) { | ||
| 1207 | def inParamNames = serviceDef.getInParameterNames() | ||
| 1208 | for (paramName in inParamNames) { | ||
| 1209 | def paramNode = serviceDef.getInParameter(paramName) | ||
| 1210 | def paramType = paramNode?.attribute('type') ?: 'String' | ||
| 1211 | def paramDesc = paramNode.first("description")?.text ?: "Parameter from service ${serviceName}" | ||
| 1212 | |||
| 1213 | // Convert Moqui type to JSON Schema type | ||
| 1214 | def typeMap = [ | ||
| 1215 | "text-short": "string", | ||
| 1216 | "text-medium": "string", | ||
| 1217 | "text-long": "string", | ||
| 1218 | "text-very-long": "string", | ||
| 1219 | "id": "string", | ||
| 1220 | "id-long": "string", | ||
| 1221 | "number-integer": "integer", | ||
| 1222 | "number-decimal": "number", | ||
| 1223 | "number-float": "number", | ||
| 1224 | "date": "string", | ||
| 1225 | "date-time": "string", | ||
| 1226 | "date-time-nano": "string", | ||
| 1227 | "boolean": "boolean", | ||
| 1228 | "text-indicator": "boolean" | ||
| 1229 | ] | ||
| 1230 | def jsonSchemaType = typeMap[paramType] ?: "string" | ||
| 1231 | |||
| 1232 | parameters[paramName] = [ | ||
| 1233 | type: jsonSchemaType, | ||
| 1234 | description: paramDesc | ||
| 1235 | ] | ||
| 1236 | |||
| 1237 | if (paramNode?.attribute('required') == "true") { | ||
| 1238 | required << paramName | ||
| 1239 | } | ||
| 1240 | } | ||
| 1241 | } | ||
| 1242 | } catch (Exception e) { | ||
| 1243 | ec.logger.debug("Error getting service definition for ${serviceName}: ${e.message}") | ||
| 1244 | } | ||
| 1245 | } | ||
| 1246 | } | ||
| 1247 | } catch (Exception e) { | ||
| 1248 | ec.logger.debug("Error getting transitions for screen ${screenPath}: ${e.message}") | ||
| 1249 | } | ||
| 1250 | |||
| 1251 | // Build MCP tool | ||
| 1252 | tool = [ | ||
| 1253 | name: "screen_${screenName}", | ||
| 1254 | title: title, | ||
| 1255 | description: title, // Use title as description | ||
| 1256 | inputSchema: [ | ||
| 1257 | type: "object", | ||
| 1258 | properties: parameters, | ||
| 1259 | required: required.unique() | ||
| 1260 | ] | ||
| 1261 | ] | ||
| 1262 | |||
| 1263 | // Add screen metadata | ||
| 1264 | tool.screenPath = screenPath | ||
| 1265 | tool.toolType = "screen" | ||
| 1266 | |||
| 1267 | // Add screen structure metadata | ||
| 1268 | try { | ||
| 1269 | def screenInfo = ec.screen.getScreenInfo(screenPath) | ||
| 1270 | if (screenInfo) { | ||
| 1271 | tool.screenInfo = [ | ||
| 1272 | name: screenInfo.name, | ||
| 1273 | level: screenInfo.level, | ||
| 1274 | hasTransitions: screenInfo.transitions > 0, | ||
| 1275 | hasForms: screenInfo.forms > 0, | ||
| 1276 | subscreens: screenInfo.subscreens | ||
| 1277 | ] | ||
| 1278 | } | ||
| 1279 | } catch (Exception e) { | ||
| 1280 | ec.logger.debug("Could not get screen info for metadata: ${e.message}") | ||
| 1281 | } | ||
| 1282 | |||
| 1283 | } catch (Exception e) { | ||
| 1284 | ec.logger.warn("Error converting screen ${screenPath} to MCP tool: ${e.message}") | ||
| 1285 | } | ||
| 1286 | ]]></script> | ||
| 1287 | </actions> | ||
| 1288 | </service> | ||
| 1289 | |||
| 1290 | <service verb="execute" noun="ScreenAsMcpTool" authenticate="true" allow-remote="true" transaction-timeout="120"> | 761 | <service verb="execute" noun="ScreenAsMcpTool" authenticate="true" allow-remote="true" transaction-timeout="120"> |
| 1291 | <description>Execute a screen as an MCP tool</description> | 762 | <description>Execute a screen as an MCP tool</description> |
| 1292 | <in-parameters> | 763 | <in-parameters> |
| ... | @@ -1656,7 +1127,27 @@ def startTime = System.currentTimeMillis() | ... | @@ -1656,7 +1127,27 @@ def startTime = System.currentTimeMillis() |
| 1656 | isError: false | 1127 | isError: false |
| 1657 | ] | 1128 | ] |
| 1658 | 1129 | ||
| 1130 | // Queue result as notification for real-time delivery | ||
| 1131 | try { | ||
| 1132 | def servlet = ec.getWeb()?.getServletContext()?.getAttribute("enhancedMcpServlet") | ||
| 1133 | if (servlet && sessionId) { | ||
| 1134 | def notification = [ | ||
| 1135 | method: "notifications/tool_result", | ||
| 1136 | params: [ | ||
| 1137 | toolName: "screen_" + screenPath.replace("/", "_").replace(".", "_"), | ||
| 1138 | result: result, | ||
| 1139 | executionTime: executionTime, | ||
| 1140 | timestamp: System.currentTimeMillis() | ||
| 1141 | ] | ||
| 1142 | ] | ||
| 1143 | servlet.queueNotification(sessionId, notification) | ||
| 1659 | ec.logger.info("MCP Screen Execution: Queued result as notification for screen ${screenPath} in ${executionTime}s") | 1144 | ec.logger.info("MCP Screen Execution: Queued result as notification for screen ${screenPath} in ${executionTime}s") |
| 1145 | } else { | ||
| 1146 | ec.logger.warn("MCP Screen Execution: No servlet or sessionId available for notification queuing") | ||
| 1147 | } | ||
| 1148 | } catch (Exception e) { | ||
| 1149 | ec.logger.warn("MCP Screen Execution: Failed to queue notification: ${e.message}") | ||
| 1150 | } | ||
| 1660 | ]]></script> | 1151 | ]]></script> |
| 1661 | </actions> | 1152 | </actions> |
| 1662 | </service> | 1153 | </service> |
| ... | @@ -2006,10 +1497,10 @@ def startTime = System.currentTimeMillis() | ... | @@ -2006,10 +1497,10 @@ def startTime = System.currentTimeMillis() |
| 2006 | } | 1497 | } |
| 2007 | ec.logger.info("list#Tools: Creating subscreen tool ${toolName} for ${screenPath} (parentToolName: ${parentToolName}, level: ${level})") | 1498 | ec.logger.info("list#Tools: Creating subscreen tool ${toolName} for ${screenPath} (parentToolName: ${parentToolName}, level: ${level})") |
| 2008 | } else if (isSubscreen && parentScreenPath) { | 1499 | } else if (isSubscreen && parentScreenPath) { |
| 2009 | toolName = screenPathToToolNameWithSubscreens(screenPath, parentScreenPath) | 1500 | toolName = parentToolName + screenPathToToolNameWithSubscreens(screenPath, parentScreenPath) |
| 2010 | ec.logger.info("list#Tools: Creating subscreen tool ${toolName} for ${screenPath} (parent: ${parentScreenPath})") | 1501 | ec.logger.info("list#Tools: Creating subscreen tool ${toolName} for ${screenPath} (parent: ${parentScreenPath})") |
| 2011 | } else { | 1502 | } else { |
| 2012 | toolName = screenPathToToolName(screenPath) | 1503 | toolName = parentToolName + screenPathToToolName(screenPath) |
| 2013 | ec.logger.info("list#Tools: Creating main screen tool ${toolName} for ${screenPath}") | 1504 | ec.logger.info("list#Tools: Creating main screen tool ${toolName} for ${screenPath}") |
| 2014 | } | 1505 | } |
| 2015 | 1506 | ||
| ... | @@ -2121,6 +1612,7 @@ def startTime = System.currentTimeMillis() | ... | @@ -2121,6 +1612,7 @@ def startTime = System.currentTimeMillis() |
| 2121 | } | 1612 | } |
| 2122 | 1613 | ||
| 2123 | if (actualSubScreenPath) { | 1614 | if (actualSubScreenPath) { |
| 1615 | ec.logger.info("list#Tools: Adding subscreen ${actualSubScreenPath} ${screenPath} ${toolName} ${level+1}") | ||
| 2124 | processScreenWithSubscreens(actualSubScreenPath, screenPath, processedScreens, toolsAccumulator, toolName, level + 1) | 1616 | processScreenWithSubscreens(actualSubScreenPath, screenPath, processedScreens, toolsAccumulator, toolName, level + 1) |
| 2125 | } else if (!actualSubScreenPath) { | 1617 | } else if (!actualSubScreenPath) { |
| 2126 | // For screens without explicit location, try automatic discovery | 1618 | // For screens without explicit location, try automatic discovery |
| ... | @@ -2128,6 +1620,7 @@ def startTime = System.currentTimeMillis() | ... | @@ -2128,6 +1620,7 @@ def startTime = System.currentTimeMillis() |
| 2128 | if (lastSlash > 0) { | 1620 | if (lastSlash > 0) { |
| 2129 | def basePath = screenPath.substring(0, lastSlash + 1) | 1621 | def basePath = screenPath.substring(0, lastSlash + 1) |
| 2130 | def autoSubScreenPath = basePath + subScreenEntry.key + ".xml" | 1622 | def autoSubScreenPath = basePath + subScreenEntry.key + ".xml" |
| 1623 | ec.logger.info("list#Tools: Constructed fallback path for ${subScreenEntry.key}: ${actualSubScreenPath}") | ||
| 2131 | processScreenWithSubscreens(autoSubScreenPath, screenPath, processedScreens, toolsAccumulator, toolName, level + 1) | 1624 | processScreenWithSubscreens(autoSubScreenPath, screenPath, processedScreens, toolsAccumulator, toolName, level + 1) |
| 2132 | } | 1625 | } |
| 2133 | } | 1626 | } |
| ... | @@ -2146,7 +1639,9 @@ def startTime = System.currentTimeMillis() | ... | @@ -2146,7 +1639,9 @@ def startTime = System.currentTimeMillis() |
| 2146 | def processedScreens = [] as Set<String> | 1639 | def processedScreens = [] as Set<String> |
| 2147 | ec.logger.info("list#Tools: Starting recursive processing from ${allScreens.size()} base screens") | 1640 | ec.logger.info("list#Tools: Starting recursive processing from ${allScreens.size()} base screens") |
| 2148 | for (screenPath in allScreens) { | 1641 | for (screenPath in allScreens) { |
| 2149 | processScreenWithSubscreens(screenPath, null, processedScreens, tools, null, 0) | 1642 | def parentToolPath = 'screen_' + screenPath.split('/')[-3..-3].join('_').replace('.xml', '') + '_' |
| 1643 | ec.logger.info("TOPSCREEN: ${parentToolPath}") | ||
| 1644 | processScreenWithSubscreens(screenPath, null, processedScreens, tools, parentToolPath, 0) | ||
| 2150 | } | 1645 | } |
| 2151 | ec.logger.info("list#Tools: Recursive processing found ${tools.size()} total tools") | 1646 | ec.logger.info("list#Tools: Recursive processing found ${tools.size()} total tools") |
| 2152 | 1647 | ... | ... |
-
Please register or sign in to post a comment