b844cc47 by Ean Schuessler

Refactor wiki system - remove McpScreenDoc entity

- Remove McpScreenDoc entity and mapping layer
- Use screen paths directly as WikiPage.pagePath
- Fix historySeqId from string "01" to numeric "1"
- Fix DbResourceFile fileData to use CDATA for complex content
- Add wiki descriptions to BrowseScreens subscreens
- Update BrowseScreens to query WikiPage directly by screenPath
- Remove duplicate/corrupt data entries from McpScreenDocsData.xml
1 parent 3f89e412
......@@ -52,7 +52,7 @@
<!-- WikiPageHistory for version tracking -->
<moqui.resource.wiki.WikiPageHistory
wikiPageId="MCP_PROMPTS/get-started"
historySeqId="01"
historySeqId="1"
versionName="v1"
userId="EX_JOHN_DOE"
changeDateTime="2025-12-29 00:00:00.000"/>
......
<?xml version="1.0" encoding="UTF-8"?>
<!--
This software is in the public domain under CC0 1.0 Universal plus a
Grant of Patent License.
To the extent possible under law, the author(s) have dedicated all
copyright and related and neighboring rights to this software to the
public domain worldwide. This software is distributed without any warranty.
You should have received a copy of the CC0 Public Domain Dedication
along with this software. If not, see
<http://creativecommons.org/publicdomain/zero/1.0/>.
-->
<entity-facade-xml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/entity-facade-3.xsd">
<!-- MCP Screen Documentation Wiki Space -->
<moqui.resource.wiki.WikiSpace wikiSpaceId="MCP_SCREEN_DOCS"
description="MCP Screen Documentation - LLM instructions for specific screens"
restrictView="N"
restrictUpdate="Y"
rootPageLocation="dbresource://WikiSpace/MCP_SCREEN_DOCS.md"/>
<!-- WikiSpace Root Page -->
<moqui.resource.DbResource
resourceId="WIKI_MCP_SCREEN_DOCS"
parentResourceId=""
filename="MCP_SCREEN_DOCS.md"
isFile="Y"/>
<moqui.resource.DbResourceFile
resourceId="WIKI_MCP_SCREEN_DOCS"
mimeType="text/markdown"
versionName="v1"><![CDATA[# Moqui MCP Server
This server provides access to Moqui ERP through MCP.
## Getting Started
Use the following discovery tools to explore available functionality:
- `moqui_browse_screens`: Browse Moqui screen hierarchy
- `moqui_search_screens`: Search for screens by name to find their paths
- `moqui_get_screen_details`: Get input parameters for specific screens
## Common Screen Paths
### Catalog Operations
- `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.QuickSearch`: General order and customer search
### Customer Management
- `PopCommerce.PopCommerceRoot.Customer`: Manage customer accounts
- `PopCommerce.PopCommerceAdmin.QuickSearch`: Customer lookup
## Tips for LLM Clients
- All screens support parameterized queries for filtering results
- Use `moqui_render_screen` with the screen path to execute screens
- Screen paths use dot notation (e.g., `PopCommerce.Catalog.Product`)
- Check `moqui_get_screen_details` for required parameters before rendering
- Use `renderMode: "mcp"` for structured JSON output or `"text"` for human-readable format]]></moqui.resource.DbResourceFile>
<!-- Root Wiki Page -->
<moqui.resource.wiki.WikiPage
wikiPageId="MCP_SCREEN_DOCS/root"
wikiSpaceId="MCP_SCREEN_DOCS"
pagePath="root"
publishedVersionName="v1"
restrictView="N">
</moqui.resource.wiki.WikiPage>
<moqui.resource.wiki.WikiPageHistory
wikiPageId="MCP_SCREEN_DOCS/root"
historySeqId="1"
versionName="v1"
userId="EX_JOHN_DOE"
changeDateTime="2025-01-02 00:00:00.000"/>
<!-- Root Screen Documentation -->
<moqui.resource.DbResource
resourceId="WIKI_MCP_DOCS_ROOT"
parentResourceId="WIKI_MCP_SCREEN_DOCS"
filename="root.md"
isFile="Y"/>
<moqui.resource.DbResourceFile
resourceId="WIKI_MCP_DOCS_ROOT"
mimeType="text/markdown"
versionName="v1"><![CDATA[# Moqui MCP Server
This server provides access to Moqui ERP through MCP.
## Getting Started
Use the following discovery tools to explore available functionality:
- `moqui_browse_screens`: Browse Moqui screen hierarchy
- `moqui_search_screens`: Search for screens by name to find their paths
- `moqui_get_screen_details`: Get input parameters for specific screens
## Common Screen Paths
### Catalog Operations
- `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.QuickSearch`: General order and customer search
### Customer Management
- `PopCommerce.PopCommerceRoot.Customer`: Manage customer accounts
- `PopCommerce.PopCommerceAdmin.QuickSearch`: Customer lookup
## Tips for LLM Clients
- All screens support parameterized queries for filtering results
- Use `moqui_render_screen` with the screen path to execute screens
- Screen paths use dot notation (e.g., `PopCommerce.Catalog.Product`)
- Check `moqui_get_screen_details` for required parameters before rendering
- Use `renderMode: "mcp"` for structured JSON output or `"text"` for human-readable format]]></moqui.resource.DbResourceFile>
<!-- PopCommerce Root Screen Documentation -->
<moqui.resource.DbResource
resourceId="WIKI_MCP_DOCS_POPCOMM_ROOT"
parentResourceId="WIKI_MCP_SCREEN_DOCS"
filename="PopCommerce.PopCommerceRoot.md"
isFile="Y"/>
<moqui.resource.DbResourceFile
resourceId="WIKI_MCP_DOCS_POPCOMM_ROOT"
mimeType="text/markdown"
versionName="v1"><![CDATA[# PopCommerce Root
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
## Navigation
Use browse tools to explore the full catalog of PopCommerce screens starting from this root screen.]]></moqui.resource.DbResourceFile>
<!-- PopCommerce Root Wiki Page -->
<moqui.resource.wiki.WikiPage
wikiPageId="MCP_SCREEN_DOCS/PopCommerceRoot"
wikiSpaceId="MCP_SCREEN_DOCS"
pagePath="PopCommerce.PopCommerceRoot"
publishedVersionName="v1"
restrictView="N">
</moqui.resource.wiki.WikiPage>
<moqui.resource.wiki.WikiPageHistory
wikiPageId="MCP_SCREEN_DOCS/PopCommerceRoot"
historySeqId="1"
versionName="v1"
userId="EX_JOHN_DOE"
changeDateTime="2025-01-02 00:00:00.000"/>
<!-- SimpleScreens Root Documentation -->
<moqui.resource.DbResource
resourceId="WIKI_MCP_DOCS_SIMPLE_ROOT"
parentResourceId="WIKI_MCP_SCREEN_DOCS"
filename="SimpleScreens.Root.md"
isFile="Y"/>
<moqui.resource.DbResourceFile
resourceId="WIKI_MCP_DOCS_SIMPLE_ROOT"
mimeType="text/markdown"
versionName="v1"><![CDATA[# Simple Screens Root
Basic Moqui framework demonstration screens showing common patterns and examples.
## Screens
- **Hello World**: Simple text rendering example
- **Examples**: Various framework demonstration screens
- **Forms**: Form input and validation examples
- **Lists**: Data list and table examples
## Usage
These screens are primarily for learning Moqui screen development patterns.]]></moqui.resource.DbResourceFile>
<!-- SimpleScreens Root Wiki Page -->
<moqui.resource.wiki.WikiPage
wikiPageId="MCP_SCREEN_DOCS/SimpleRoot"
wikiSpaceId="MCP_SCREEN_DOCS"
pagePath="SimpleScreens.Root"
publishedVersionName="v1"
restrictView="N">
</moqui.resource.wiki.WikiPage>
<moqui.resource.wiki.WikiPageHistory
wikiPageId="MCP_SCREEN_DOCS/SimpleRoot"
historySeqId="1"
versionName="v1"
userId="EX_JOHN_DOE"
changeDateTime="2025-01-02 00:00:00.000"/>
</entity-facade-xml>
......@@ -83,10 +83,46 @@
if (!supportedVersions.contains(protocolVersion)) {
throw new Exception("Unsupported protocol version: ${protocolVersion}. Supported versions: ${supportedVersions.join(', ')}")
}
// Get current user context (if authenticated)
def userId = ec.user.userId
def userAccountId = userId ? userId : null
// Try to load root instructions from wiki
def instructions = null
try {
def wikiPage = ec.entity.find("moqui.resource.wiki.WikiPage")
.condition("wikiSpaceId", "MCP_SCREEN_DOCS")
.condition("pagePath", "root")
.useCache(true)
.one()
if (wikiPage) {
def wikiSpace = ec.entity.find("moqui.resource.wiki.WikiSpace")
.condition("wikiSpaceId", wikiPage.wikiSpaceId)
.one()
if (wikiSpace) {
def pageLocation = wikiSpace.rootPageLocation
if (!pageLocation.endsWith('/')) pageLocation += '/'
pageLocation += wikiPage.pagePath + '.md'
def pageRef = ec.resource.getLocationReference(pageLocation)
def wikiText = pageRef?.getText()
if (wikiText) {
instructions = wikiText
ec.logger.info("MCP Initialize: Loaded root instructions from wiki")
}
}
}
} catch (Exception e) {
ec.logger.debug("Could not load root instructions from wiki: ${e.message}")
}
// Fallback to hardcoded instructions if wiki not available
if (!instructions) {
instructions = "This server provides access to Moqui ERP through MCP. For common business queries: Use PopCommerce.PopCommerceAdmin.Catalog.Feature.FindFeature to search by features like color or size. Use PopCommerce.PopCommerceAdmin.Catalog.Product.FindProduct for product catalog, PopCommerce.PopCommerceAdmin.Order.FindOrder for order status, PopCommerce.PopCommerceRoot.Customer for customer management, PopCommerce.PopCommerceAdmin.Catalog.Product.EditPrices to check prices and PopCommerce.PopCommerceAdmin.QuickSearch for general searches. All screens support parameterized queries for filtering results."
}
// Build server capabilities - don't fetch actual tools/resources during init
// Tools and resources will be discovered via separate list requests per MCP spec
......@@ -107,7 +143,7 @@
capabilities: serverCapabilities,
serverInfo: serverInfo,
sessionId: sessionId,
instructions: "This server provides access to Moqui ERP through MCP. For common business queries: Use PopCommerce.PopCommerceAdmin.Catalog.Feature.FindFeature to search by features like color or size. Use PopCommerce.PopCommerceAdmin.Catalog.Product.FindProduct for product catalog, PopCommerce.PopCommerceAdmin.Order.FindOrder for order status, PopCommerce.PopCommerceRoot.Customer for customer management, PopCommerce.PopCommerceAdmin.Catalog.Product.EditPrices to check prices and PopCommerce.PopCommerceAdmin.QuickSearch for general searches. All screens support parameterized queries for filtering results."
instructions: instructions
]
ec.logger.info("MCP Initialize for user ${userId} (session ${sessionId}): capabilities negotiated")
......@@ -353,11 +389,43 @@
// Handle special moqui://mcp/instructions resource
if (uri == "moqui://mcp/instructions") {
// Try to load from wiki
def instructionsText = null
try {
def wikiPage = ec.entity.find("moqui.resource.wiki.WikiPage")
.condition("wikiSpaceId", "MCP_SCREEN_DOCS")
.condition("pagePath", "root")
.useCache(true)
.one()
if (wikiPage) {
def wikiSpace = ec.entity.find("moqui.resource.wiki.WikiSpace")
.condition("wikiSpaceId", wikiPage.wikiSpaceId)
.one()
if (wikiSpace) {
def pageLocation = wikiSpace.rootPageLocation
if (!pageLocation.endsWith('/')) pageLocation += '/'
pageLocation += wikiPage.pagePath + '.md'
def pageRef = ec.resource.getLocationReference(pageLocation)
instructionsText = pageRef?.getText()
}
}
} catch (Exception e) {
ec.logger.debug("Could not load instructions from wiki: ${e.message}")
}
// Fallback to hardcoded instructions
if (!instructionsText) {
instructionsText = "This server provides access to Moqui ERP through MCP. For common business queries: Use PopCommerce.PopCommerceAdmin.Catalog.Feature.FindFeature to search by features like color or size. Use PopCommerce.PopCommerceAdmin.Catalog.Product.FindProduct for product catalog, PopCommerce.PopCommerceAdmin.Order.FindOrder for order status, PopCommerce.PopCommerceRoot.Customer for customer management, PopCommerce.PopCommerceAdmin.Catalog.Product.EditPrices to check prices and PopCommerce.PopCommerceAdmin.QuickSearch for general searches. All screens support parameterized queries for filtering results."
}
result = [
content: [[
uri: "moqui://mcp/instructions",
mimeType: "text/plain",
text: "This server provides access to Moqui ERP through MCP. For common business queries: Use PopCommerce.PopCommerceAdmin.Catalog.Feature.FindFeature to search by features like color or size. Use PopCommerce.PopCommerceAdmin.Catalog.Product.FindProduct for product catalog, PopCommerce.PopCommerceAdmin.Order.FindOrder for order status, PopCommerce.PopCommerceRoot.Customer for customer management, PopCommerce.PopCommerceAdmin.Catalog.Product.EditPrices to check prices and PopCommerce.PopCommerceAdmin.QuickSearch for general searches. All screens support parameterized queries for filtering results."
text: instructionsText
]],
isError: false
]
......@@ -652,11 +720,69 @@ import groovy.json.JsonBuilder
ExecutionContext ec = context.ec
def startTime = System.currentTimeMillis()
// Set parameters in context
if (parameters) {
ec.context.putAll(parameters)
// Set parameters in context
if (parameters) {
ec.context.putAll(parameters)
}
// Helper function to get simple path from component path
def getSimplePath = { fullPath ->
if (!fullPath || fullPath == "root") return "root"
String cleanPath = fullPath
if (cleanPath.startsWith("component://")) cleanPath = cleanPath.substring(12)
if (cleanPath.endsWith(".xml")) cleanPath = cleanPath.substring(0, cleanPath.length() - 4)
List<String> parts = cleanPath.split('/').toList()
if (parts.size() > 1 && parts[1] == "screen") parts.remove(1)
return parts.join('.')
}
// Helper function to load wiki instructions for a screen
def getWikiInstructions = { screenPath ->
try {
def wikiPage = ec.entity.find("moqui.resource.wiki.WikiPage")
.condition("wikiSpaceId", "MCP_SCREEN_DOCS")
.condition("pagePath", screenPath)
.useCache(true)
.one()
if (!wikiPage) return null
def wikiSpace = ec.entity.find("moqui.resource.wiki.WikiSpace")
.condition("wikiSpaceId", wikiPage.wikiSpaceId)
.one()
if (!wikiSpace) return null
// Build the resource location for the page
def pageLocation = wikiSpace.rootPageLocation
if (!pageLocation.endsWith('/')) {
pageLocation += '/'
}
pageLocation += wikiPage.pagePath + '.md'
// Get the resource reference and text content
def pageRef = ec.resource.getLocationReference(pageLocation)
def wikiText = pageRef?.getText()
if (wikiText) {
return wikiText
}
} catch (Exception e) {
ec.logger.debug("Could not load wiki instructions for ${screenPath}: ${e.message}")
}
return null
}
// Resolve input screen path to simple path for lookup
def inputScreenPath = screenPath
if (screenPath.startsWith("component://")) {
inputScreenPath = getSimplePath(screenPath)
}
ec.logger.info("MCP Screen Execution: Looking up wiki docs for ${inputScreenPath}")
// Try to get wiki instructions
def wikiInstructions = getWikiInstructions(inputScreenPath)
// Check if action is being processed - use real screen rendering if so
def isActionExecution = parameters?.action != null
......@@ -833,6 +959,7 @@ def startTime = System.currentTimeMillis()
if (mcpData.forms) mcpResult.forms = mcpData.forms
if (mcpData.actions) mcpResult.actions = mcpData.actions
if (output) mcpResult.htmlPreview = output.take(2000) + (output.length() > 2000 ? "..." : "")
if (wikiInstructions) mcpResult.wikiInstructions = wikiInstructions
content << [
type: "text",
......@@ -840,9 +967,13 @@ def startTime = System.currentTimeMillis()
]
} else {
// Return raw output for other modes
def textOutput = output
if (wikiInstructions) {
textOutput = "--- Wiki Instructions ---\n\n${wikiInstructions}\n\n--- Screen Output ---\n\n${output}"
}
content << [
type: "text",
text: output,
text: textOutput,
screenPath: screenPath,
screenUrl: screenUrl,
executionTime: executionTime,
......@@ -1155,6 +1286,73 @@ def startTime = System.currentTimeMillis()
currentPath = currentPath.split("\\?")[0]
}
// Helper function to load wiki content
def loadWikiContent = { path ->
ec.logger.info("BrowseScreens: loadWikiContent CALLED for ${path}")
try {
def simplePath = (path == "root") ? "root" : path
if (simplePath.contains("?")) {
simplePath = simplePath.split("\\?")[0]
}
ec.logger.info("BrowseScreens: Looking up wiki instructions for ${simplePath}")
def wikiPage = ec.entity.find("moqui.resource.wiki.WikiPage")
.condition("wikiSpaceId", "MCP_SCREEN_DOCS")
.condition("pagePath", simplePath)
.useCache(true)
.one()
if (wikiPage) {
ec.logger.info("BrowseScreens: Found wikiPage: ${wikiPage.pagePath}")
def wikiSpace = ec.entity.find("moqui.resource.wiki.WikiSpace")
.condition("wikiSpaceId", wikiPage.wikiSpaceId)
.one()
if (wikiSpace) {
ec.logger.info("BrowseScreens: Found wikiSpace: ${wikiSpace.wikiSpaceId}")
def dbResource = ec.entity.find("moqui.resource.DbResource")
.condition("parentResourceId", "WIKI_MCP_SCREEN_DOCS")
.condition("filename", wikiPage.pagePath + ".md")
.one()
if (dbResource) {
ec.logger.info("BrowseScreens: Found dbResource: ${dbResource.resourceId}")
def dbResourceFile = ec.entity.find("moqui.resource.DbResourceFile")
.condition("resourceId", dbResource.resourceId)
.condition("versionName", wikiPage.publishedVersionName)
.one()
ec.logger.info("BrowseScreens: dbResourceFile query result: ${dbResourceFile ? 'found' : 'null'}")
if (!dbResourceFile) {
dbResourceFile = ec.entity.find("moqui.resource.DbResourceFile")
.condition("resourceId", dbResource.resourceId)
.one()
ec.logger.info("BrowseScreens: dbResourceFile fallback query result: ${dbResourceFile ? 'found' : 'null'}")
}
if (dbResourceFile) {
ec.logger.info("BrowseScreens: dbResourceFile.fileData: ${dbResourceFile.fileData ? 'exists, size=' + dbResourceFile.fileData.length : 'null'}")
}
if (dbResourceFile && dbResourceFile.fileData) {
def content = new String(dbResourceFile.fileData, "UTF-8")
ec.logger.info("BrowseScreens: Found wiki instructions for ${simplePath}, length: ${content?.length()}")
return content
}
} else {
ec.logger.warn("BrowseScreens: No dbResource found for ${wikiPage.pagePath}.md")
}
}
}
} catch (Exception e) {
ec.logger.warn("BrowseScreens: Error getting wiki instructions: ${e.message}")
}
return null
}
// Helper to convert full component path to simple path (PopCommerce/screen/Root.xml -> PopCommerce.Root)
def convertToSimplePath = { fullPath ->
if (!fullPath) return null
......@@ -1166,6 +1364,18 @@ def startTime = System.currentTimeMillis()
return parts.join('.')
}
// Helper to extract short description from wiki content
def getShortDescription = { wikiText ->
if (!wikiText) return null
def lines = wikiText.split('\n')
for (def line : lines) {
if (line.trim() && !line.trim().startsWith('#')) {
return line.trim().take(200)
}
}
return null
}
if (currentPath == "root") {
// Discover top-level applications
def aacvList = ec.entity.find("moqui.security.ArtifactAuthzCheckView")
......@@ -1194,9 +1404,11 @@ def startTime = System.currentTimeMillis()
for (def screenPath in rootScreens) {
def simplePath = convertToSimplePath(screenPath)
def wikiContent = loadWikiContent(simplePath)
def description = wikiContent ? getShortDescription(wikiContent) : "Application: ${simplePath}"
subscreens << [
path: simplePath,
description: "Application: ${simplePath}"
path: simplePath,
description: description
]
}
} else {
......@@ -1238,9 +1450,11 @@ def startTime = System.currentTimeMillis()
for (subItem in subItems) {
def subName = subItem.getName()
def subPath = currentPath + "." + subName
def wikiContent = loadWikiContent(subPath)
def description = wikiContent ? getShortDescription(wikiContent) : "Subscreen: ${subName}"
subscreens << [
path: subPath,
description: "Subscreen: ${subName}"
description: description
]
}
}
......@@ -1375,11 +1589,18 @@ def startTime = System.currentTimeMillis()
}
}
// Try to get wiki instructions for screen
def wikiInstructions = null
ec.logger.info("BrowseScreens: About to check wiki instructions, currentPath='${currentPath}', isRoot=${currentPath == 'root'}")
wikiInstructions = loadWikiContent(currentPath)
// Render current screen if not root browsing
def renderedContent = null
def renderError = null
def actualRenderMode = renderMode ?: "mcp"
if (currentPath != "root") {
try {
ec.logger.info("BrowseScreens: Rendering screen ${currentPath} with mode=${actualRenderMode}")
......@@ -1467,7 +1688,11 @@ def startTime = System.currentTimeMillis()
if (renderedContent) {
resultMap.renderedContent = renderedContent
}
if (wikiInstructions) {
resultMap.wikiInstructions = wikiInstructions
}
if (renderError) {
resultMap.renderError = renderError
}
......