a06fd54e by Ean Schuessler

Implement universal autocomplete with transition metadata extraction

- Extract service name and in-map from transition XML
- Capture depends-on parameter attribute for field name mapping
- Parse in-map parameter mapping for proper service calls
- Support server-search autocomplete with term parameter
- Fallback to ScreenAsMcpTool for entity-find transitions
- Enhanced response format detection and error handling

This implements TODO 1-3 from AGENTS.md:
- TODO 1: Capture Depends-on Parameter Attribute
- TODO 2: Extract Transition Service Name
- TODO 3: Parse in-map Parameter Mapping

Files modified:
- DefaultScreenMacros.mcp.ftl: Extract transition metadata during screen render
- McpFieldOptionsService.groovy: Use extracted metadata for intelligent autocomplete
1 parent 4b34c970
......@@ -45,18 +45,29 @@ Use the following discovery tools to explore available functionality:
## Common Screen Paths
### Catalog Operations
- `/PopCommerce/PopCommerceAdmin/Catalog/dashboard`: Catalog overview and management
- `/PopCommerce/PopCommerceAdmin/Catalog/Product/FindProduct`: Search and browse products
- `/PopCommerce/PopCommerceAdmin/Catalog/Feature/FindFeature`: Search by features like color or size
- `/PopCommerce/PopCommerceAdmin/Catalog/Product/EditPrices`: View and update product prices
### Order Management
- `/PopCommerce/PopCommerceAdmin/Order/FindOrder`: Lookup order status and details
- `/PopCommerce/PopCommerceAdmin/Order/FindOrder`: Lookup and create sales/purchase orders
- `/PopCommerce/PopCommerceAdmin/QuickSearch`: General order and customer search
### Customer Management
- `/PopCommerce/PopCommerceRoot/Customer`: Manage customer accounts
### Party & Customer Management
- `/PopCommerce/PopCommerceAdmin/Party/FindParty`: Manage all parties (People, Organizations)
- `/PopCommerce/PopCommerceRoot/Customer`: Customer storefront account management
- `User`: Internal user account, notifications, and messages
- `/PopCommerce/PopCommerceAdmin/QuickSearch`: Customer lookup
### Facility & Accounting
- `/PopCommerce/PopCommerceAdmin/Facility/FindFacility`: Manage warehouses and inventory locations
- `/PopCommerce/PopCommerceAdmin/Accounting/dashboard`: Financial management and GL accounting
### System & Developer Tools
- `/tools/Tools`: Developer tools, entity data editing, and service references
- `/tools/System`: System administration, cache management, and security settings
## Tips for LLM Clients
- All screens support parameterized queries for filtering results
......@@ -107,18 +118,29 @@ Use the following discovery tools to explore available functionality:
## Common Screen Paths
### Catalog Operations
- `/PopCommerce/PopCommerceAdmin/Catalog/dashboard`: Catalog overview and management
- `/PopCommerce/PopCommerceAdmin/Catalog/Product/FindProduct`: Search and browse products
- `/PopCommerce/PopCommerceAdmin/Catalog/Feature/FindFeature`: Search by features like color or size
- `/PopCommerce/PopCommerceAdmin/Catalog/Product/EditPrices`: View and update product prices
### Order Management
- `/PopCommerce/PopCommerceAdmin/Order/FindOrder`: Lookup order status and details
- `/PopCommerce/PopCommerceAdmin/Order/FindOrder`: Lookup and create sales/purchase orders
- `/PopCommerce/PopCommerceAdmin/QuickSearch`: General order and customer search
### Customer Management
- `/PopCommerce/PopCommerceRoot/Customer`: Manage customer accounts
### Party & Customer Management
- `/PopCommerce/PopCommerceAdmin/Party/FindParty`: Manage all parties (People, Organizations)
- `/PopCommerce/PopCommerceRoot/Customer`: Customer storefront account management
- `User`: Internal user account, notifications, and messages
- `/PopCommerce/PopCommerceAdmin/QuickSearch`: Customer lookup
### Facility & Accounting
- `/PopCommerce/PopCommerceAdmin/Facility/FindFacility`: Manage warehouses and inventory locations
- `/PopCommerce/PopCommerceAdmin/Accounting/dashboard`: Financial management and GL accounting
### System & Developer Tools
- `/tools/Tools`: Developer tools, entity data editing, and service references
- `/tools/System`: System administration, cache management, and security settings
## Tips for LLM Clients
- All screens support parameterized queries for filtering results
......@@ -144,14 +166,15 @@ Main storefront and customer-facing screens for PopCommerce e-commerce component
## Key Screens
- **Customer Management**: Browse and manage customer accounts
- **Order Status**: View order history and track shipments
- **Product Catalog**: Browse products and categories
- **Shopping Cart**: Review and checkout items
- **Catalog & Products**: Browse the product catalog and view details
- **Customer Management**: Manage customer profiles and account settings
- **Order Management**: Track order history and status
- **Shopping Cart**: Review items and proceed to checkout
- **Messages**: Access `User/Messages` for internal communications
## Navigation
Use browse tools to explore the full catalog of PopCommerce screens starting from this root screen.]]></fileData>
Use browse tools to explore the full catalog of PopCommerce screens. For administrative tasks, use the `/PopCommerce/PopCommerceAdmin` hierarchy.]]></fileData>
</moqui.resource.DbResourceFile>
<!-- PopCommerce Root Wiki Page -->
......@@ -212,4 +235,126 @@ These screens are primarily for learning Moqui screen development patterns.]]></
userId="EX_JOHN_DOE"
changeDateTime="2025-01-02 00:00:00.000"/>
<!-- User Messages Documentation -->
<moqui.resource.DbResource
resourceId="WIKI_MCP_DOCS_USER_MESSAGES"
parentResourceId="WIKI_MCP_SCREEN_DOCS"
filename="User/Messages.md"
isFile="Y"/>
<moqui.resource.DbResourceFile
resourceId="WIKI_MCP_DOCS_USER_MESSAGES"
mimeType="text/markdown"
versionName="v1">
<fileData><![CDATA[# User Messages
Interface for sending and receiving messages between users and other parties.
## Key Actions
- **Create Message**: Send a new message. Requires `toPartyId`, `subject`, and `body`.
- **View Thread**: View the conversation history for a message.
- **Find Messages**: Search for existing messages by various criteria.
## Usage
Use this screen to communicate about orders, products, or general administrative tasks.]]></fileData>
</moqui.resource.DbResourceFile>
<moqui.resource.wiki.WikiPage
wikiPageId="MCP_SCREEN_DOCS/UserMessages"
wikiSpaceId="MCP_SCREEN_DOCS"
pagePath="User/Messages"
publishedVersionName="v1"
restrictView="N">
</moqui.resource.wiki.WikiPage>
<moqui.resource.wiki.WikiPageHistory
wikiPageId="MCP_SCREEN_DOCS/UserMessages"
historySeqId="1"
versionName="v1"
userId="EX_JOHN_DOE"
changeDateTime="2025-01-14 00:00:00.000"/>
<!-- Tools Documentation -->
<moqui.resource.DbResource
resourceId="WIKI_MCP_DOCS_TOOLS"
parentResourceId="WIKI_MCP_SCREEN_DOCS"
filename="tools/Tools.md"
isFile="Y"/>
<moqui.resource.DbResourceFile
resourceId="WIKI_MCP_DOCS_TOOLS"
mimeType="text/markdown"
versionName="v1">
<fileData><![CDATA[# Developer Tools
Central hub for developer and data management utilities.
## Key Subscreens
- **Entity**: Data editing, import/export, and performance stats. Use `tools/Tools/Entity/DataEdit/EntityList` to browse and edit any entity.
- **Service**: Service reference and testing tools.
- **AutoScreen**: Automatically generated screens for all entities.
- **GroovyShell**: Execute arbitrary Groovy code (use with extreme caution).
## Usage
Use these tools for low-level data manipulation or system exploration when standard administrative screens are insufficient.]]></fileData>
</moqui.resource.DbResourceFile>
<moqui.resource.wiki.WikiPage
wikiPageId="MCP_SCREEN_DOCS/Tools"
wikiSpaceId="MCP_SCREEN_DOCS"
pagePath="tools/Tools"
publishedVersionName="v1"
restrictView="N">
</moqui.resource.wiki.WikiPage>
<moqui.resource.wiki.WikiPageHistory
wikiPageId="MCP_SCREEN_DOCS/Tools"
historySeqId="1"
versionName="v1"
userId="EX_JOHN_DOE"
changeDateTime="2025-01-14 00:00:00.000"/>
<!-- System Documentation -->
<moqui.resource.DbResource
resourceId="WIKI_MCP_DOCS_SYSTEM"
parentResourceId="WIKI_MCP_SCREEN_DOCS"
filename="tools/System.md"
isFile="Y"/>
<moqui.resource.DbResourceFile
resourceId="WIKI_MCP_DOCS_SYSTEM"
mimeType="text/markdown"
versionName="v1">
<fileData><![CDATA[# System Administration
Core system management and monitoring screens.
## Key Subscreens
- **Security**: Manage users, user groups, and artifact permissions.
- **Cache**: View and clear system caches.
- **ServiceJob**: Manage scheduled and background service jobs.
- **LogViewer**: View system logs and hit statistics.
## Usage
Use these screens to monitor system health, manage user access, and troubleshoot operational issues.]]></fileData>
</moqui.resource.DbResourceFile>
<moqui.resource.wiki.WikiPage
wikiPageId="MCP_SCREEN_DOCS/System"
wikiSpaceId="MCP_SCREEN_DOCS"
pagePath="tools/System"
publishedVersionName="v1"
restrictView="N">
</moqui.resource.wiki.WikiPage>
<moqui.resource.wiki.WikiPageHistory
wikiPageId="MCP_SCREEN_DOCS/System"
historySeqId="1"
versionName="v1"
userId="EX_JOHN_DOE"
changeDateTime="2025-01-14 00:00:00.000"/>
</entity-facade-xml>
......
......@@ -147,14 +147,61 @@
<#if dropdownOptions?has_content>
<#assign fieldMeta = fieldMeta + {"type": "dropdown", "options": dropdownOptions?js_string!}>
<#else>
<#assign dynamicOptionNode = fieldSubNode["drop-down"]["dynamic-options"][0]!>
<#assign dropdownNode = fieldSubNode["drop-down"]!>
<#if dropdownNode?is_hash>
<#assign dynamicOptionNode = dropdownNode["dynamic-options"][0]!>
<#else>
<#assign dynamicOptionNode = dropdownNode[0]["dynamic-options"][0]!>
</#if>
<#if dynamicOptionNode?has_content>
<#-- Try to extract transition metadata for better autocomplete support -->
<#assign transitionMetadata = {}>
<#if dynamicOptionNode["@transition"]?has_content>
<#assign transitionNode = sri.getScreenDefinition().getTransitionItem(dynamicOptionNode["@transition"]!"")!>
<#if transitionNode?has_content>
<#-- Extract service name if present -->
<#assign serviceCallNode = transitionNode["service-call"][0]!>
<#if serviceCallNode?has_content && serviceCallNode["@name"]?has_content>
<#assign transitionMetadata = transitionMetadata + {"serviceName": (serviceCallNode["@name"]!"")}>
</#if>
<#-- Extract in-map parameter mapping -->
<#if serviceCallNode["@in-map"]?has_content>
<#assign transitionMetadata = transitionMetadata + {"inParameterMap": ((serviceCallNode["@in-map"]!"")?js_string)!""}>
<#elseif transitionNode["parameter"]?has_content>
<#assign paramNode = transitionNode["parameter"][0]!>
<#if paramNode?has_content>
<#assign transitionMetadata = transitionMetadata + {"inParameterMap": "[]"}>
</#if>
</#if>
</#if>
</#if>
<#-- Capture depends-on with parameter attribute -->
<#assign dependsOnList = []>
<#list dynamicOptionNode["depends-on"]! as depNode>
<#assign depField = depNode["@field"]!"">
<#assign depParameter = depNode["@parameter"]!depField>
<#assign dependsOnItem = depField + "|" + depParameter>
<#assign dependsOnList = dependsOnList + [dependsOnItem]>
</#list>
<#assign dependsOnJson = '[]'>
<#if dependsOnList?size gt 0>
<#assign dependsOnJson = '['>
<#list dependsOnList as dep>
<#if dep_index gt 0><#assign dependsOnJson = dependsOnJson + ', '></#if>
<#assign dependsOnJson = dependsOnJson + '"' + dep + '"'>
</#list>
<#assign dependsOnJson = dependsOnJson + ']'>
</#if>
<#-- Build dynamicOptions metadata -->
<#assign fieldMeta = fieldMeta + {"type": "dropdown", "dynamicOptions": {
"transition": (dynamicOptionNode["@transition"]!""),
"serverSearch": (dynamicOptionNode["@server-search"]! == "true"),
"minLength": (dynamicOptionNode["@min-length"]!"0"),
"parameterMap": (dynamicOptionNode["@parameter-map"]!"")?js_string!""
}}>
"parameterMap": ((dynamicOptionNode["@parameter-map"]!"")?js_string)!"",
"dependsOn": dependsOnJson
} + transitionMetadata}>
<#else>
<#assign fieldMeta = fieldMeta + {"type": "dropdown"}>
</#if>
......
......@@ -194,7 +194,8 @@
// Handle internal discovery/utility tools
def internalToolMappings = [
"moqui_search_screens": "McpServices.mcp#SearchScreens"
"moqui_search_screens": "McpServices.mcp#SearchScreens",
"moqui_get_screen_details": "McpServices.mcp#GetScreenDetails"
]
def targetServiceName = internalToolMappings[name]
......@@ -1929,6 +1930,20 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
]
],
[
name: "moqui_get_screen_details",
title: "Get Screen Details",
description: "Get screen field details including dropdown options. Use this to understand available fields and their options before submitting forms.",
inputSchema: [
type: "object",
properties: [
"path": [type: "string", description: "Screen path to analyze (e.g., 'PopCommerce/PopCommerceAdmin/Party/FindParty')"],
"fieldName": [type: "string", description: "Optional specific field name. If not provided, returns all fields."],
"parameters": [type: "object", description: "Optional parameters to set in context before rendering (for autocomplete contexts)."]
],
required: ["path"]
]
],
[
name: "prompts_list",
title: "List Prompts",
description: "List available MCP prompt templates.",
......@@ -1957,6 +1972,36 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
</actions>
</service>
<service verb="mcp" noun="GetScreenDetails" authenticate="true" allow-remote="true" transaction-timeout="60">
<description>Get screen field details including dropdown options. Use this to understand available fields and their options before submitting forms.</description>
<in-parameters>
<parameter name="path" required="true"><description>Screen path to analyze (e.g., '/PopCommerce/PopCommerceAdmin/Party/FindParty').</description></parameter>
<parameter name="fieldName"><description>Optional specific field name. If not provided, returns all fields.</description></parameter>
<parameter name="parameters" type="Map"><description>Optional parameters to set in context before rendering (for autocomplete contexts).</description></parameter>
</in-parameters>
<out-parameters>
<parameter name="result" type="Map"/>
</out-parameters>
<actions>
<script><![CDATA[
import org.moqui.context.ExecutionContext
import groovy.json.JsonBuilder
import org.moqui.mcp.McpFieldOptionsService
ExecutionContext ec = context.ec
def serviceResult = McpFieldOptionsService.service(path, fieldName, parameters, ec)
// Return in standard MCP format with content array
def resultJson = new JsonBuilder(serviceResult).toString()
result = [
content: [[type: "text", text: resultJson]],
isError: false
]
]]></script>
</actions>
</service>
<!-- NOTE: handle#McpRequest service removed - functionality moved to screen/webapp.xml for unified handling -->
</services>
\ No newline at end of file
......
......@@ -52,13 +52,13 @@ class UiNarrativeBuilder {
narrative.screen = describeScreen(screenDef, semanticState, isTerse)
narrative.actions = describeActions(screenDef, semanticState, currentPath, isTerse)
narrative.navigation = describeLinks(semanticState, currentPath, isTerse)
narrative.notes = describeNotes(semanticState, isTerse)
narrative.notes = describeNotes(semanticState, currentPath, isTerse)
return narrative
}
String describeScreen(ScreenDefinition screenDef, Map<String, Object> semanticState, boolean isTerse) {
def screenName = screenDef?.name ?: "Screen"
def screenName = screenDef?.getScreenName() ?: "Screen"
def sb = new StringBuilder()
sb.append("${screenName} displays ")
......@@ -201,7 +201,7 @@ class UiNarrativeBuilder {
return navigation
}
List<String> describeNotes(Map<String, Object> semanticState, boolean isTerse) {
List<String> describeNotes(Map<String, Object> semanticState, String currentPath, boolean isTerse) {
def notes = []
def data = semanticState?.data
......@@ -220,6 +220,29 @@ class UiNarrativeBuilder {
notes << "This screen has ${actions.size()} actions. Use semanticState.actions for complete list."
}
// Add note about moqui_get_screen_details for dropdown options
def formData = semanticState?.data
if (formData && formData.containsKey('formMetadata') && formData.formMetadata instanceof Map) {
def formMetadata = formData.formMetadata
def allFields = []
formMetadata.each { formName, formInfo ->
if (formInfo instanceof Map && formInfo.containsKey('fields')) {
def fields = formInfo.fields
if (fields instanceof Collection) {
def dynamicFields = fields.findAll { f -> f instanceof Map && f.containsKey('dynamicOptions') }
if (dynamicFields) {
def fieldNames = dynamicFields.collect { it.name }.take(3)
allFields.addAll(fieldNames)
}
}
}
}
if (allFields) {
def uniqueFields = allFields.unique().take(5)
notes << "Fields with autocomplete: ${uniqueFields.join(', ')}. Use moqui_get_screen_details(path='${currentPath}', fieldName='${uniqueFields[0]}') to get field-specific options."
}
}
def parameters = semanticState?.parameters
if (parameters && parameters.size() > 0) {
def requiredParams = parameters.findAll { k, v -> k.toString().toLowerCase().contains('id') }
......