Add ViewEntity support to MCP interface
- Add mantle.party.FindPartyView to McpBusinessServices artifact group for authorization - Enhance ResourcesList service to include ViewEntities with special descriptions - Improve ResourcesRead service with fallback entity discovery for ViewEntities - ViewEntities provide pre-joined data for LLM convenience, eliminating manual joins - Tested successfully: FindPartyView returns 100 records with contact info, addresses, emails, phones
Showing
2 changed files
with
61 additions
and
7 deletions
| ... | @@ -54,6 +54,7 @@ | ... | @@ -54,6 +54,7 @@ |
| 54 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.order.OrderHeader" artifactTypeEnumId="AT_ENTITY"/> | 54 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.order.OrderHeader" artifactTypeEnumId="AT_ENTITY"/> |
| 55 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.order.OrderItem" artifactTypeEnumId="AT_ENTITY"/> | 55 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.order.OrderItem" artifactTypeEnumId="AT_ENTITY"/> |
| 56 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.party.Party" artifactTypeEnumId="AT_ENTITY"/> | 56 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.party.Party" artifactTypeEnumId="AT_ENTITY"/> |
| 57 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.party.FindPartyView" artifactTypeEnumId="AT_ENTITY"/> | ||
| 57 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.account.Customer" artifactTypeEnumId="AT_ENTITY"/> | 58 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.account.Customer" artifactTypeEnumId="AT_ENTITY"/> |
| 58 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="UserAccount" artifactTypeEnumId="AT_ENTITY"/> | 59 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="UserAccount" artifactTypeEnumId="AT_ENTITY"/> |
| 59 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.ledger.FinancialAccount" artifactTypeEnumId="AT_ENTITY"/> | 60 | <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.ledger.FinancialAccount" artifactTypeEnumId="AT_ENTITY"/> | ... | ... |
| ... | @@ -542,14 +542,33 @@ | ... | @@ -542,14 +542,33 @@ |
| 542 | return userAccessibleEntities != null && userAccessibleEntities.contains(entityName.toString()) | 542 | return userAccessibleEntities != null && userAccessibleEntities.contains(entityName.toString()) |
| 543 | } | 543 | } |
| 544 | 544 | ||
| 545 | // Add all permitted entities - let Moqui artifact security handle filtering | 545 | // Add all permitted entities including ViewEntities for LLM convenience |
| 546 | for (entityName in allEntityNames) { | 546 | def allEntityNames = ec.entity.getAllEntityNames() |
| 547 | def allViewNames = [] as Set<String> | ||
| 548 | |||
| 549 | // Get ViewEntities by checking entity definitions for view entities | ||
| 550 | def entityInfoList = ec.entity.getAllEntityInfo(0, true) // includeViewEntities=true | ||
| 551 | for (entityInfo in entityInfoList) { | ||
| 552 | if (entityInfo.isViewEntity) { | ||
| 553 | allViewNames.add(entityInfo.entityName) | ||
| 554 | } | ||
| 555 | } | ||
| 556 | |||
| 557 | // Combine real entities and ViewEntities | ||
| 558 | def allAccessibleEntities = allEntityNames + allViewNames | ||
| 559 | |||
| 560 | for (entityName in allAccessibleEntities) { | ||
| 547 | if (userHasEntityPermission(entityName)) { | 561 | if (userHasEntityPermission(entityName)) { |
| 562 | def description = "Moqui entity: ${entityName}" | ||
| 563 | if (entityName.contains("View")) { | ||
| 564 | description = "Moqui ViewEntity: ${entityName} (pre-joined data for LLM convenience)" | ||
| 565 | } | ||
| 566 | |||
| 548 | ec.logger.info("MCP ResourcesList: Adding entity: ${entityName}") | 567 | ec.logger.info("MCP ResourcesList: Adding entity: ${entityName}") |
| 549 | availableResources << [ | 568 | availableResources << [ |
| 550 | uri: "entity://${entityName}", | 569 | uri: "entity://${entityName}", |
| 551 | name: entityName, | 570 | name: entityName, |
| 552 | description: "Moqui entity: ${entityName}", | 571 | description: description, |
| 553 | mimeType: "application/json" | 572 | mimeType: "application/json" |
| 554 | ] | 573 | ] |
| 555 | } | 574 | } |
| ... | @@ -592,13 +611,46 @@ | ... | @@ -592,13 +611,46 @@ |
| 592 | 611 | ||
| 593 | def startTime = System.currentTimeMillis() | 612 | def startTime = System.currentTimeMillis() |
| 594 | try { | 613 | try { |
| 595 | // Get entity definition for field descriptions | 614 | // Try to get entity definition - handle both real entities and view entities |
| 596 | def entityInfoList = ec.entity.getAllEntityInfo(0, false) | 615 | def entityDef = null |
| 597 | def entityDef = entityInfoList.find { it.entityName == entityName } | 616 | try { |
| 617 | // First try getAllEntityInfo for detailed info | ||
| 618 | def entityInfoList = ec.entity.getAllEntityInfo(-1, true) // all entities, include view entities | ||
| 619 | entityDef = entityInfoList.find { it.entityName == entityName } | ||
| 620 | |||
| 621 | if (!entityDef) { | ||
| 622 | // If not found in detailed list, try basic entity check | ||
| 623 | if (ec.entity.isEntityDefined(entityName)) { | ||
| 624 | // Create minimal entity definition for basic query | ||
| 625 | entityDef = [ | ||
| 626 | entityName: entityName, | ||
| 627 | packageName: entityName.split('\\.')[0], | ||
| 628 | description: "Entity: ${entityName}", | ||
| 629 | isViewEntity: entityName.contains('View'), | ||
| 630 | allFieldInfoList: [] | ||
| 631 | ] | ||
| 632 | } | ||
| 633 | } | ||
| 634 | } catch (Exception e) { | ||
| 635 | ec.logger.warn("ResourcesRead: Error getting entity info for ${entityName}: ${e.message}") | ||
| 636 | // Fallback: try basic entity check | ||
| 637 | if (ec.entity.isEntityDefined(entityName)) { | ||
| 638 | entityDef = [ | ||
| 639 | entityName: entityName, | ||
| 640 | packageName: entityName.split('\\.')[0], | ||
| 641 | description: "Entity: ${entityName}", | ||
| 642 | isViewEntity: entityName.contains('View'), | ||
| 643 | allFieldInfoList: [] | ||
| 644 | ] | ||
| 645 | } | ||
| 646 | } | ||
| 647 | |||
| 598 | if (!entityDef) { | 648 | if (!entityDef) { |
| 599 | throw new Exception("Entity not found: ${entityName}") | 649 | throw new Exception("Entity not found: ${entityName}") |
| 600 | } | 650 | } |
| 601 | 651 | ||
| 652 | ec.logger.info("ResourcesRead: Found entity ${entityName}, isViewEntity=${entityDef.isViewEntity}") | ||
| 653 | |||
| 602 | // Query entity data (limited to prevent large responses) | 654 | // Query entity data (limited to prevent large responses) |
| 603 | def entityList = ec.entity.find(entityName) | 655 | def entityList = ec.entity.find(entityName) |
| 604 | .limit(100) | 656 | .limit(100) |
| ... | @@ -638,7 +690,8 @@ | ... | @@ -638,7 +690,8 @@ |
| 638 | 690 | ||
| 639 | } catch (Exception e) { | 691 | } catch (Exception e) { |
| 640 | def executionTime = (System.currentTimeMillis() - startTime) / 1000.0 | 692 | def executionTime = (System.currentTimeMillis() - startTime) / 1000.0 |
| 641 | throw new Exception("Error reading resource ${uri}: ${e.message}") | 693 | ec.logger.warn("Error reading resource ${uri}: ${e.message}") |
| 694 | result = [error: "Error reading resource ${uri}: ${e.message}"] | ||
| 642 | } | 695 | } |
| 643 | ]]></script> | 696 | ]]></script> |
| 644 | </actions> | 697 | </actions> | ... | ... |
-
Please register or sign in to post a comment