d365fo-mcp 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +20 -2
- package/README.md +6 -6
- package/bridge/D365MetadataBridge/Models/Models.cs +31 -8
- package/bridge/D365MetadataBridge/Protocol/BridgeProtocol.cs +80 -5
- package/bridge/D365MetadataBridge/Protocol/RequestDispatcher.cs +24 -6
- package/bridge/D365MetadataBridge/Services/CrossReferenceService.cs +31 -15
- package/bridge/D365MetadataBridge/Services/MetadataReadService.cs +59 -4
- package/bridge/D365MetadataBridge/Services/MetadataWriteService.cs +600 -195
- package/dist/bridge/bridgeAdapter.d.ts +52 -14
- package/dist/bridge/bridgeAdapter.js +144 -61
- package/dist/bridge/bridgeClient.d.ts +30 -3
- package/dist/bridge/bridgeClient.js +101 -44
- package/dist/bridge/bridgeFailure.d.ts +82 -0
- package/dist/bridge/bridgeFailure.js +96 -0
- package/dist/bridge/bridgeReadiness.d.ts +90 -0
- package/dist/bridge/bridgeReadiness.js +124 -0
- package/dist/bridge/bridgeTypes.d.ts +6 -0
- package/dist/bridge/debouncedRefresh.d.ts +16 -2
- package/dist/bridge/debouncedRefresh.js +74 -16
- package/dist/bridge/index.d.ts +2 -0
- package/dist/bridge/index.js +1 -0
- package/dist/cli/commands/doctor.d.ts +16 -0
- package/dist/cli/commands/doctor.js +117 -0
- package/dist/cli/commands/session.d.ts +9 -0
- package/dist/cli/commands/session.js +185 -0
- package/dist/cli/commands/setup.d.ts +0 -2
- package/dist/cli/commands/setup.js +1 -1
- package/dist/cli/context.d.ts +0 -2
- package/dist/cli/context.js +1 -1
- package/dist/cli/envFile.d.ts +0 -6
- package/dist/cli/envFile.js +0 -11
- package/dist/cli/exec.d.ts +0 -2
- package/dist/cli/exec.js +0 -7
- package/dist/cli/index.js +10 -0
- package/dist/cli/mcpJson.d.ts +0 -1
- package/dist/cli/mcpJson.js +1 -1
- package/dist/cli/npmRegistry.d.ts +0 -1
- package/dist/cli/npmRegistry.js +1 -1
- package/dist/cli/session/analyze.d.ts +138 -0
- package/dist/cli/session/analyze.js +340 -0
- package/dist/cli/session/copilotChatLog.d.ts +31 -0
- package/dist/cli/session/copilotChatLog.js +125 -0
- package/dist/cli/session/sessionLog.d.ts +58 -0
- package/dist/cli/session/sessionLog.js +50 -0
- package/dist/cli/settingsStore.js +4 -0
- package/dist/cli/xppConfig.d.ts +0 -2
- package/dist/cli/xppConfig.js +0 -3
- package/dist/config/configFile.d.ts +2 -7
- package/dist/config/configFile.js +9 -10
- package/dist/config/settings.d.ts +18 -4
- package/dist/config/settings.js +116 -4
- package/dist/database/download.d.ts +14 -0
- package/dist/database/download.js +38 -4
- package/dist/index.js +67 -25
- package/dist/knowledge/formPatterns/catalog/topLevel/simpleListDetails.d.ts +0 -1
- package/dist/knowledge/formPatterns/catalog/topLevel/simpleListDetails.js +0 -4
- package/dist/knowledge/formPatterns/methodStubs.js +3 -2
- package/dist/metadata/enhancedParser.d.ts +0 -8
- package/dist/metadata/enhancedParser.js +3 -0
- package/dist/metadata/formPatternMiner.js +3 -0
- package/dist/metadata/labelParser.d.ts +0 -12
- package/dist/metadata/labelParser.js +13 -3
- package/dist/metadata/modelDescriptor.d.ts +0 -2
- package/dist/metadata/modelDescriptor.js +0 -4
- package/dist/metadata/symbolIndex.d.ts +133 -15
- package/dist/metadata/symbolIndex.js +439 -154
- package/dist/metadata/types.d.ts +6 -112
- package/dist/metadata/xmlParser.js +1 -1
- package/dist/metadata/xppDeclaration.d.ts +0 -11
- package/dist/metadata/xppDeclaration.js +2 -2
- package/dist/middleware/rateLimiter.js +1 -1
- package/dist/prompts/systemInstructions.js +2 -2
- package/dist/scripts/build-database.js +454 -136
- package/dist/scripts/build-fts.js +456 -138
- package/dist/scripts/extract-metadata.js +370 -141
- package/dist/server/mcpServer.d.ts +2 -2
- package/dist/server/mcpServer.js +13 -12
- package/dist/server/serverMode.d.ts +58 -6
- package/dist/server/serverMode.js +88 -7
- package/dist/server/toolAnnotations.d.ts +4 -2
- package/dist/server/toolAnnotations.js +4 -5
- package/dist/server/toolSchemas/buildD365foProject.js +2 -3
- package/dist/server/toolSchemas/d365foFile.d.ts +22 -19
- package/dist/server/toolSchemas/d365foFile.js +45 -103
- package/dist/server/toolSchemas/generateObject.d.ts +14 -211
- package/dist/server/toolSchemas/generateObject.js +24 -116
- package/dist/server/toolSchemas/getKnowledge.js +7 -5
- package/dist/server/toolSchemas/getObjectInfo.d.ts +25 -1
- package/dist/server/toolSchemas/getObjectInfo.js +20 -6
- package/dist/server/toolSchemas/getWorkspaceInfo.js +1 -1
- package/dist/server/toolSchemas/index.d.ts +63 -342
- package/dist/server/toolSchemas/index.js +0 -6
- package/dist/server/toolSchemas/labels.d.ts +17 -59
- package/dist/server/toolSchemas/labels.js +20 -64
- package/dist/server/toolSchemas/prepare.d.ts +4 -0
- package/dist/server/toolSchemas/prepare.js +4 -0
- package/dist/server/toolSchemas/runBpCheck.d.ts +18 -0
- package/dist/server/toolSchemas/runBpCheck.js +14 -2
- package/dist/server/toolSchemas/search.d.ts +0 -2
- package/dist/server/toolSchemas/search.js +4 -18
- package/dist/server/toolSchemas/updateSymbolIndex.js +5 -4
- package/dist/server/transport.js +1 -1
- package/dist/tools/{analyzeCode.d.ts → analysis/analyzeCode.d.ts} +1 -1
- package/dist/tools/{analyzeCode.js → analysis/analyzeCode.js} +7 -5
- package/dist/tools/{analyzeCompleteness.d.ts → analysis/analyzeCompleteness.d.ts} +1 -1
- package/dist/tools/{batchSearch.d.ts → analysis/batchSearch.d.ts} +1 -1
- package/dist/tools/{batchSearch.js → analysis/batchSearch.js} +6 -3
- package/dist/tools/{extensionSearch.d.ts → analysis/extensionSearch.d.ts} +1 -1
- package/dist/tools/{extensionSearch.js → analysis/extensionSearch.js} +19 -2
- package/dist/tools/{findReferences.d.ts → analysis/findReferences.d.ts} +2 -2
- package/dist/tools/{findReferences.js → analysis/findReferences.js} +6 -4
- package/dist/tools/analysis/labelSearchHistory.d.ts +25 -0
- package/dist/tools/analysis/labelSearchHistory.js +65 -0
- package/dist/tools/{prefixDiagnostics.d.ts → analysis/prefixDiagnostics.d.ts} +4 -9
- package/dist/tools/{prefixDiagnostics.js → analysis/prefixDiagnostics.js} +11 -50
- package/dist/tools/{search.d.ts → analysis/search.d.ts} +2 -2
- package/dist/tools/{search.js → analysis/search.js} +72 -13
- package/dist/tools/analysis/searchLabels.d.ts +47 -0
- package/dist/tools/analysis/searchLabels.js +225 -0
- package/dist/tools/{searchUnified.d.ts → analysis/searchUnified.d.ts} +2 -2
- package/dist/tools/{searchUnified.js → analysis/searchUnified.js} +5 -2
- package/dist/tools/{validateCode.d.ts → analysis/validateCode.d.ts} +1 -1
- package/dist/tools/{validateCode.js → analysis/validateCode.js} +9 -7
- package/dist/tools/analysis/validateFormPattern.d.ts +99 -0
- package/dist/tools/{validateFormPattern.js → analysis/validateFormPattern.js} +111 -9
- package/dist/tools/{validateObjectNaming.d.ts → analysis/validateObjectNaming.d.ts} +1 -1
- package/dist/tools/{validateObjectNaming.js → analysis/validateObjectNaming.js} +198 -46
- package/dist/tools/{validateXpp.d.ts → analysis/validateXpp.d.ts} +3 -0
- package/dist/tools/{validateXpp.js → analysis/validateXpp.js} +239 -6
- package/dist/tools/d365foFile.js +132 -5
- package/dist/tools/generateObject.d.ts +20 -7
- package/dist/tools/generateObject.js +59 -17
- package/dist/tools/{analyzeExtensionPoints.d.ts → knowledge/analyzeExtensionPoints.d.ts} +1 -1
- package/dist/tools/{analyzeExtensionPoints.js → knowledge/analyzeExtensionPoints.js} +4 -4
- package/dist/tools/{analyzePatterns.d.ts → knowledge/analyzePatterns.d.ts} +1 -1
- package/dist/tools/{apiUsagePatterns.d.ts → knowledge/apiUsagePatterns.d.ts} +2 -2
- package/dist/tools/{apiUsagePatterns.js → knowledge/apiUsagePatterns.js} +1 -1
- package/dist/tools/{d365foErrorHelp.js → knowledge/d365foErrorHelp.js} +96 -3
- package/dist/tools/{extensionStrategyAdvisor.d.ts → knowledge/extensionStrategyAdvisor.d.ts} +1 -1
- package/dist/tools/{extensionStrategyAdvisor.js → knowledge/extensionStrategyAdvisor.js} +1 -1
- package/dist/tools/{findCocExtensions.d.ts → knowledge/findCocExtensions.d.ts} +2 -2
- package/dist/tools/{findCocExtensions.js → knowledge/findCocExtensions.js} +4 -4
- package/dist/tools/{findEventHandlers.d.ts → knowledge/findEventHandlers.d.ts} +2 -2
- package/dist/tools/{findEventHandlers.js → knowledge/findEventHandlers.js} +1 -1
- package/dist/tools/{formPattern.d.ts → knowledge/formPattern.d.ts} +1 -1
- package/dist/tools/{formPattern.js → knowledge/formPattern.js} +9 -6
- package/dist/tools/{getFormPatterns.d.ts → knowledge/getFormPatterns.d.ts} +2 -2
- package/dist/tools/{getFormPatterns.js → knowledge/getFormPatterns.js} +8 -5
- package/dist/tools/{getKnowledge.d.ts → knowledge/getKnowledge.d.ts} +7 -3
- package/dist/tools/{getKnowledge.js → knowledge/getKnowledge.js} +25 -6
- package/dist/tools/{getTablePatterns.d.ts → knowledge/getTablePatterns.d.ts} +1 -1
- package/dist/tools/{getTablePatterns.js → knowledge/getTablePatterns.js} +6 -3
- package/dist/tools/{methodSignature.d.ts → knowledge/methodSignature.d.ts} +1 -1
- package/dist/tools/{methodSignature.js → knowledge/methodSignature.js} +29 -14
- package/dist/tools/{objectPatterns.d.ts → knowledge/objectPatterns.d.ts} +1 -1
- package/dist/tools/{objectPatterns.js → knowledge/objectPatterns.js} +5 -3
- package/dist/tools/{xppKnowledge.d.ts → knowledge/xppKnowledge.d.ts} +7 -0
- package/dist/tools/{xppKnowledge.js → knowledge/xppKnowledge.js} +120 -16
- package/dist/tools/labels.d.ts +2 -0
- package/dist/tools/labels.js +160 -8
- package/dist/tools/{prepare.d.ts → prepare/prepare.d.ts} +14 -1
- package/dist/tools/prepare/prepare.js +133 -0
- package/dist/tools/{prepareChange.d.ts → prepare/prepareChange.d.ts} +1 -1
- package/dist/tools/{prepareChange.js → prepare/prepareChange.js} +20 -8
- package/dist/tools/{prepareCreate.d.ts → prepare/prepareCreate.d.ts} +1 -1
- package/dist/tools/{prepareCreate.js → prepare/prepareCreate.js} +23 -10
- package/dist/tools/{classInfo.d.ts → readers/classInfo.d.ts} +1 -1
- package/dist/tools/{classInfo.js → readers/classInfo.js} +3 -3
- package/dist/tools/{completion.d.ts → readers/completion.d.ts} +1 -1
- package/dist/tools/{completion.js → readers/completion.js} +7 -7
- package/dist/tools/{configKeyInfo.d.ts → readers/configKeyInfo.d.ts} +1 -1
- package/dist/tools/{configKeyInfo.js → readers/configKeyInfo.js} +1 -1
- package/dist/tools/{dataEntityInfo.d.ts → readers/dataEntityInfo.d.ts} +1 -1
- package/dist/tools/{dataEntityInfo.js → readers/dataEntityInfo.js} +1 -1
- package/dist/tools/{edtInfo.d.ts → readers/edtInfo.d.ts} +1 -1
- package/dist/tools/{edtInfo.js → readers/edtInfo.js} +7 -4
- package/dist/tools/{enumInfo.d.ts → readers/enumInfo.d.ts} +1 -1
- package/dist/tools/{enumInfo.js → readers/enumInfo.js} +16 -11
- package/dist/tools/{extensionInfo.d.ts → readers/extensionInfo.d.ts} +2 -2
- package/dist/tools/{extensionInfo.js → readers/extensionInfo.js} +8 -6
- package/dist/tools/{formInfo.d.ts → readers/formInfo.d.ts} +1 -1
- package/dist/tools/{formInfo.js → readers/formInfo.js} +37 -22
- package/dist/tools/{getLabelInfo.d.ts → readers/getLabelInfo.d.ts} +1 -1
- package/dist/tools/{getLabelInfo.js → readers/getLabelInfo.js} +11 -9
- package/dist/tools/{getMethod.d.ts → readers/getMethod.d.ts} +1 -1
- package/dist/tools/{getMethod.js → readers/getMethod.js} +5 -3
- package/dist/tools/{getMethodSource.d.ts → readers/getMethodSource.d.ts} +2 -2
- package/dist/tools/{getMethodSource.js → readers/getMethodSource.js} +3 -3
- package/dist/tools/{getObjectInfo.d.ts → readers/getObjectInfo.d.ts} +6 -3
- package/dist/tools/readers/getObjectInfo.js +251 -0
- package/dist/tools/readers/getWorkspaceInfo.d.ts +14 -0
- package/dist/tools/readers/getWorkspaceInfo.js +292 -0
- package/dist/tools/{macroInfo.d.ts → readers/macroInfo.d.ts} +1 -1
- package/dist/tools/{macroInfo.js → readers/macroInfo.js} +1 -1
- package/dist/tools/{mapInfo.d.ts → readers/mapInfo.d.ts} +1 -1
- package/dist/tools/{mapInfo.js → readers/mapInfo.js} +1 -1
- package/dist/tools/{menuItemInfo.d.ts → readers/menuItemInfo.d.ts} +2 -2
- package/dist/tools/{menuItemInfo.js → readers/menuItemInfo.js} +2 -2
- package/dist/tools/{objectInfoRegistry.d.ts → readers/objectInfoRegistry.d.ts} +7 -8
- package/dist/tools/{objectInfoRegistry.js → readers/objectInfoRegistry.js} +7 -15
- package/dist/tools/readers/objectXml.d.ts +27 -0
- package/dist/tools/readers/objectXml.js +58 -0
- package/dist/tools/{queryInfo.d.ts → readers/queryInfo.d.ts} +1 -1
- package/dist/tools/{queryInfo.js → readers/queryInfo.js} +11 -8
- package/dist/tools/{reportInfo.d.ts → readers/reportInfo.d.ts} +1 -1
- package/dist/tools/{reportInfo.js → readers/reportInfo.js} +40 -13
- package/dist/tools/{securityArtifactInfo.d.ts → readers/securityArtifactInfo.d.ts} +1 -1
- package/dist/tools/{securityArtifactInfo.js → readers/securityArtifactInfo.js} +2 -2
- package/dist/tools/{securityCoverageInfo.d.ts → readers/securityCoverageInfo.d.ts} +1 -1
- package/dist/tools/{securityCoverageInfo.js → readers/securityCoverageInfo.js} +1 -1
- package/dist/tools/{securityInfo.d.ts → readers/securityInfo.d.ts} +1 -1
- package/dist/tools/{securityInfo.js → readers/securityInfo.js} +4 -2
- package/dist/tools/{securityPolicyInfo.d.ts → readers/securityPolicyInfo.d.ts} +1 -1
- package/dist/tools/{serviceInfo.d.ts → readers/serviceInfo.d.ts} +1 -1
- package/dist/tools/{serviceInfo.js → readers/serviceInfo.js} +1 -1
- package/dist/tools/{tableExtensionInfo.d.ts → readers/tableExtensionInfo.d.ts} +2 -2
- package/dist/tools/{tableExtensionInfo.js → readers/tableExtensionInfo.js} +4 -4
- package/dist/tools/{tableInfo.d.ts → readers/tableInfo.d.ts} +2 -2
- package/dist/tools/{tableInfo.js → readers/tableInfo.js} +24 -10
- package/dist/tools/{viewInfo.d.ts → readers/viewInfo.d.ts} +1 -1
- package/dist/tools/{viewInfo.js → readers/viewInfo.js} +15 -10
- package/dist/tools/{buildProject.d.ts → sdlc/buildProject.d.ts} +16 -2
- package/dist/tools/{buildProject.js → sdlc/buildProject.js} +301 -82
- package/dist/tools/{dbSync.d.ts → sdlc/dbSync.d.ts} +1 -1
- package/dist/tools/{dbSync.js → sdlc/dbSync.js} +10 -7
- package/dist/tools/{reviewWorkspaceChanges.js → sdlc/reviewWorkspaceChanges.js} +4 -2
- package/dist/tools/sdlc/runBpCheck.d.ts +80 -0
- package/dist/tools/sdlc/runBpCheck.js +530 -0
- package/dist/tools/{sysTestRunner.js → sdlc/sysTestRunner.js} +8 -5
- package/dist/tools/sdlc/undoLastModification.d.ts +31 -0
- package/dist/tools/{undoLastModification.js → sdlc/undoLastModification.js} +45 -10
- package/dist/tools/{updateSymbolIndex.d.ts → sdlc/updateSymbolIndex.d.ts} +19 -2
- package/dist/tools/{updateSymbolIndex.js → sdlc/updateSymbolIndex.js} +48 -17
- package/dist/tools/{verifyD365Project.d.ts → sdlc/verifyD365Project.d.ts} +2 -2
- package/dist/tools/{verifyD365Project.js → sdlc/verifyD365Project.js} +65 -35
- package/dist/tools/{codeGen.js → smart/codeGen.js} +5 -5
- package/dist/tools/{generateFindMethods.d.ts → smart/generateFindMethods.d.ts} +1 -1
- package/dist/tools/{generateFindMethods.js → smart/generateFindMethods.js} +1 -1
- package/dist/tools/{generateRelationXpp.d.ts → smart/generateRelationXpp.d.ts} +1 -1
- package/dist/tools/{generateSmart.d.ts → smart/generateSmart.d.ts} +1 -1
- package/dist/tools/{generateSmart.js → smart/generateSmart.js} +7 -4
- package/dist/tools/{generateSmartForm.d.ts → smart/generateSmartForm.d.ts} +1 -1
- package/dist/tools/{generateSmartForm.js → smart/generateSmartForm.js} +24 -22
- package/dist/tools/{generateSmartReport.d.ts → smart/generateSmartReport.d.ts} +2 -2
- package/dist/tools/{generateSmartReport.js → smart/generateSmartReport.js} +14 -14
- package/dist/tools/{generateSmartTable.d.ts → smart/generateSmartTable.d.ts} +9 -3
- package/dist/tools/{generateSmartTable.js → smart/generateSmartTable.js} +51 -14
- package/dist/tools/{suggestEdt.d.ts → smart/suggestEdt.d.ts} +1 -1
- package/dist/tools/{suggestImplementation.d.ts → smart/suggestImplementation.d.ts} +1 -1
- package/dist/tools/{d365foFileOpSpecs.d.ts → specs/d365foFileOpSpecs.d.ts} +16 -0
- package/dist/tools/{d365foFileOpSpecs.js → specs/d365foFileOpSpecs.js} +105 -4
- package/dist/tools/specs/generateObjectOpSpecs.d.ts +42 -0
- package/dist/tools/specs/generateObjectOpSpecs.js +235 -0
- package/dist/tools/{getFormPatternSpec.js → specs/getFormPatternSpec.js} +8 -5
- package/dist/tools/specs/labelsOpSpecs.d.ts +27 -0
- package/dist/tools/specs/labelsOpSpecs.js +64 -0
- package/dist/tools/specs/opSpecs.d.ts +50 -0
- package/dist/tools/specs/opSpecs.js +195 -0
- package/dist/tools/toolHandler.d.ts +1 -0
- package/dist/tools/toolHandler.js +140 -335
- package/dist/tools/{compileLabels.js → write/compileLabels.js} +1 -1
- package/dist/tools/{createD365File.d.ts → write/createD365File.d.ts} +34 -84
- package/dist/tools/{createD365File.js → write/createD365File.js} +348 -1012
- package/dist/tools/{createLabel.d.ts → write/createLabel.d.ts} +1 -1
- package/dist/tools/{createLabel.js → write/createLabel.js} +79 -22
- package/dist/tools/write/inlineIndexUpsert.d.ts +28 -0
- package/dist/tools/write/inlineIndexUpsert.js +43 -0
- package/dist/tools/write/inlineWriteVerification.d.ts +72 -0
- package/dist/tools/write/inlineWriteVerification.js +123 -0
- package/dist/tools/write/inlineXppValidation.d.ts +24 -0
- package/dist/tools/write/inlineXppValidation.js +115 -0
- package/dist/tools/{modifyD365File.d.ts → write/modifyD365File.d.ts} +107 -19
- package/dist/tools/{modifyD365File.js → write/modifyD365File.js} +818 -317
- package/dist/tools/{renameLabel.d.ts → write/renameLabel.d.ts} +1 -1
- package/dist/tools/{renameLabel.js → write/renameLabel.js} +5 -5
- package/dist/tools/{resolveReferences.d.ts → write/resolveReferences.d.ts} +5 -3
- package/dist/tools/{resolveReferences.js → write/resolveReferences.js} +114 -10
- package/dist/tools/{writeAnchorGuard.d.ts → write/writeAnchorGuard.d.ts} +33 -3
- package/dist/tools/write/writeAnchorGuard.js +76 -0
- package/dist/tools/{createTablePropertyHonesty.d.ts → xml/createTablePropertyHonesty.d.ts} +20 -0
- package/dist/tools/{createTablePropertyHonesty.js → xml/createTablePropertyHonesty.js} +72 -2
- package/dist/tools/{dataEntityViewExtensionXml.js → xml/dataEntityViewExtensionXml.js} +1 -3
- package/dist/tools/xml/dataEntityXml.d.ts +14 -0
- package/dist/tools/{dataEntityXml.js → xml/dataEntityXml.js} +40 -7
- package/dist/tools/{edtExtensionXml.js → xml/edtExtensionXml.js} +1 -3
- package/dist/tools/xml/formXml.d.ts +24 -0
- package/dist/tools/xml/formXml.js +40 -0
- package/dist/tools/{generateD365Xml.d.ts → xml/generateD365Xml.d.ts} +1 -4
- package/dist/tools/{generateD365Xml.js → xml/generateD365Xml.js} +40 -168
- package/dist/tools/{generateTableFields.d.ts → xml/generateTableFields.d.ts} +2 -2
- package/dist/tools/{generateTableFields.js → xml/generateTableFields.js} +9 -39
- package/dist/tools/{generateTableRelation.d.ts → xml/generateTableRelation.d.ts} +1 -1
- package/dist/tools/{generateTableRelation.js → xml/generateTableRelation.js} +1 -1
- package/dist/tools/{mapXml.d.ts → xml/mapXml.d.ts} +0 -8
- package/dist/tools/{mapXml.js → xml/mapXml.js} +12 -14
- package/dist/tools/{menuItemExtensionXml.js → xml/menuItemExtensionXml.js} +1 -3
- package/dist/tools/{queryViewXml.d.ts → xml/queryViewXml.d.ts} +0 -15
- package/dist/tools/{queryViewXml.js → xml/queryViewXml.js} +5 -4
- package/dist/tools/{repairFormControls.d.ts → xml/repairFormControls.d.ts} +1 -1
- package/dist/tools/{repairFormControls.js → xml/repairFormControls.js} +12 -8
- package/dist/tools/xml/securityDutyRoleXml.d.ts +63 -0
- package/dist/tools/xml/securityDutyRoleXml.js +117 -0
- package/dist/tools/xml/securityPrivilegeXml.d.ts +2 -0
- package/dist/tools/{securityPrivilegeXml.js → xml/securityPrivilegeXml.js} +27 -5
- package/dist/tools/{serviceXml.d.ts → xml/serviceXml.d.ts} +0 -20
- package/dist/tools/{serviceXml.js → xml/serviceXml.js} +3 -2
- package/dist/tools/xml/tableXml.d.ts +49 -0
- package/dist/tools/xml/tableXml.js +163 -0
- package/dist/types/context.d.ts +8 -0
- package/dist/utils/atomicFileWrite.d.ts +39 -0
- package/dist/utils/atomicFileWrite.js +105 -0
- package/dist/utils/axEnumProperties.d.ts +72 -0
- package/dist/utils/axEnumProperties.js +120 -0
- package/dist/utils/axFieldTypes.d.ts +53 -0
- package/dist/utils/axFieldTypes.js +130 -0
- package/dist/utils/axFormDesignProperties.d.ts +0 -2
- package/dist/utils/axFormDesignProperties.js +1 -1
- package/dist/utils/axTablePropertyOrder.d.ts +0 -6
- package/dist/utils/axTablePropertyOrder.js +3 -3
- package/dist/utils/blobDownloadMarker.d.ts +39 -0
- package/dist/utils/blobDownloadMarker.js +52 -0
- package/dist/utils/buildMarker.d.ts +47 -0
- package/dist/utils/buildMarker.js +102 -0
- package/dist/utils/callDedup.d.ts +0 -1
- package/dist/utils/callDedup.js +1 -1
- package/dist/utils/configManager.d.ts +50 -0
- package/dist/utils/configManager.js +143 -26
- package/dist/utils/crossModelWriteGuard.d.ts +38 -2
- package/dist/utils/crossModelWriteGuard.js +77 -3
- package/dist/utils/edtExtensionValidator.d.ts +0 -5
- package/dist/utils/edtExtensionValidator.js +1 -1
- package/dist/utils/effectivePrefix.d.ts +79 -0
- package/dist/utils/effectivePrefix.js +114 -0
- package/dist/utils/extractManifest.d.ts +0 -1
- package/dist/utils/extractManifest.js +1 -1
- package/dist/utils/fieldControlTypes.js +11 -4
- package/dist/utils/formExtensionShapeValidator.d.ts +0 -5
- package/dist/utils/formExtensionShapeValidator.js +1 -1
- package/dist/utils/fsExtensionScanner.js +1 -1
- package/dist/utils/fuzzyMatching.d.ts +0 -5
- package/dist/utils/fuzzyMatching.js +1 -1
- package/dist/utils/indexStaleness.d.ts +4 -0
- package/dist/utils/indexStaleness.js +24 -0
- package/dist/utils/indexedXmlLookup.d.ts +60 -7
- package/dist/utils/indexedXmlLookup.js +127 -5
- package/dist/utils/labelReference.d.ts +7 -0
- package/dist/utils/labelReference.js +15 -0
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.js +0 -3
- package/dist/utils/metadataResolver.d.ts +0 -39
- package/dist/utils/metadataResolver.js +2 -42
- package/dist/utils/modelClassifier.d.ts +10 -37
- package/dist/utils/modelClassifier.js +12 -49
- package/dist/utils/modelPrefixInference.js +72 -8
- package/dist/utils/objectFileLookup.d.ts +15 -0
- package/dist/utils/objectFileLookup.js +144 -0
- package/dist/utils/objectNaming.d.ts +31 -0
- package/dist/utils/objectNaming.js +87 -0
- package/dist/utils/operationLocks.js +78 -22
- package/dist/utils/packagesRoot.d.ts +23 -0
- package/dist/utils/packagesRoot.js +55 -0
- package/dist/utils/pathContainment.js +85 -9
- package/dist/utils/payloadBudget.d.ts +70 -0
- package/dist/utils/payloadBudget.js +115 -0
- package/dist/utils/phaseTimer.d.ts +23 -0
- package/dist/utils/phaseTimer.js +50 -0
- package/dist/utils/progressReporter.d.ts +37 -0
- package/dist/utils/progressReporter.js +53 -0
- package/dist/utils/richContext.js +6 -0
- package/dist/utils/smartXmlBuilder.d.ts +4 -1
- package/dist/utils/smartXmlBuilder.js +13 -44
- package/dist/utils/symbolLookup.d.ts +0 -2
- package/dist/utils/symbolLookup.js +0 -0
- package/dist/utils/terminalUi.d.ts +0 -7
- package/dist/utils/terminalUi.js +8 -2
- package/dist/utils/toolMetrics.d.ts +2 -0
- package/dist/utils/toolMetrics.js +29 -0
- package/dist/utils/toolProgressMessage.js +14 -4
- package/dist/utils/workspaceDetectionStatus.d.ts +49 -0
- package/dist/utils/workspaceDetectionStatus.js +91 -0
- package/dist/utils/workspaceDetector.d.ts +6 -0
- package/dist/utils/workspaceDetector.js +30 -12
- package/dist/utils/xml.d.ts +22 -0
- package/dist/utils/xml.js +21 -0
- package/dist/utils/xmlEscape.d.ts +48 -0
- package/dist/utils/xmlEscape.js +76 -0
- package/dist/utils/xppDocGen.js +14 -5
- package/dist/utils/xppFormat.d.ts +41 -1
- package/dist/utils/xppFormat.js +182 -27
- package/dist/validation/formPatternValidator.js +1 -1
- package/dist/{tools → workspace}/createdArtifactLedger.js +0 -0
- package/dist/workspace/projectFile.d.ts +115 -0
- package/dist/workspace/projectFile.js +609 -0
- package/dist/workspace/projectMembership.d.ts +94 -0
- package/dist/workspace/projectMembership.js +210 -0
- package/dist/workspace/projectSelector.d.ts +53 -0
- package/dist/workspace/projectSelector.js +118 -0
- package/dist/workspace/workspaceScanner.d.ts +0 -5
- package/dist/workspace/workspaceScanner.js +20 -7
- package/dist/workspace/workspaceUtils.d.ts +0 -9
- package/dist/workspace/workspaceUtils.js +1 -22
- package/package.json +11 -4
- package/dist/prompts/index.d.ts +0 -6
- package/dist/prompts/index.js +0 -6
- package/dist/server/toolSchemas/batchGetInfo.d.ts +0 -32
- package/dist/server/toolSchemas/batchGetInfo.js +0 -35
- package/dist/server/toolSchemas/getMethod.d.ts +0 -30
- package/dist/server/toolSchemas/getMethod.js +0 -34
- package/dist/server/toolSchemas/suggestEdt.d.ts +0 -29
- package/dist/server/toolSchemas/suggestEdt.js +0 -29
- package/dist/tools/batchGetInfo.d.ts +0 -50
- package/dist/tools/batchGetInfo.js +0 -52
- package/dist/tools/dataEntityXml.d.ts +0 -97
- package/dist/tools/getObjectInfo.js +0 -88
- package/dist/tools/index.d.ts +0 -11
- package/dist/tools/index.js +0 -11
- package/dist/tools/prepare.js +0 -47
- package/dist/tools/runBpCheck.d.ts +0 -26
- package/dist/tools/runBpCheck.js +0 -296
- package/dist/tools/searchLabels.d.ts +0 -26
- package/dist/tools/searchLabels.js +0 -119
- package/dist/tools/securityPrivilegeXml.d.ts +0 -21
- package/dist/tools/undoLastModification.d.ts +0 -9
- package/dist/tools/validateFormPattern.d.ts +0 -51
- package/dist/tools/writeAnchorGuard.js +0 -44
- /package/dist/tools/{analyzeCompleteness.js → analysis/analyzeCompleteness.js} +0 -0
- /package/dist/tools/{analyzePatterns.js → knowledge/analyzePatterns.js} +0 -0
- /package/dist/tools/{d365foErrorHelp.d.ts → knowledge/d365foErrorHelp.d.ts} +0 -0
- /package/dist/tools/{securityPolicyInfo.js → readers/securityPolicyInfo.js} +0 -0
- /package/dist/tools/{reviewWorkspaceChanges.d.ts → sdlc/reviewWorkspaceChanges.d.ts} +0 -0
- /package/dist/tools/{sysTestRunner.d.ts → sdlc/sysTestRunner.d.ts} +0 -0
- /package/dist/tools/{codeGen.d.ts → smart/codeGen.d.ts} +0 -0
- /package/dist/tools/{generateRelationXpp.js → smart/generateRelationXpp.js} +0 -0
- /package/dist/tools/{suggestEdt.js → smart/suggestEdt.js} +0 -0
- /package/dist/tools/{suggestImplementation.js → smart/suggestImplementation.js} +0 -0
- /package/dist/tools/{getFormPatternSpec.d.ts → specs/getFormPatternSpec.d.ts} +0 -0
- /package/dist/tools/{compileLabels.d.ts → write/compileLabels.d.ts} +0 -0
- /package/dist/tools/{dataEntityViewExtensionXml.d.ts → xml/dataEntityViewExtensionXml.d.ts} +0 -0
- /package/dist/tools/{edtExtensionXml.d.ts → xml/edtExtensionXml.d.ts} +0 -0
- /package/dist/tools/{generateMetadata.d.ts → xml/generateMetadata.d.ts} +0 -0
- /package/dist/tools/{generateMetadata.js → xml/generateMetadata.js} +0 -0
- /package/dist/tools/{menuItemExtensionXml.d.ts → xml/menuItemExtensionXml.d.ts} +0 -0
- /package/dist/{tools → workspace}/createdArtifactLedger.d.ts +0 -0
|
@@ -34,8 +34,8 @@ PowerShell / any terminal command **WILL HANG** in VS 2022 / VS 2026 MCP integra
|
|
|
34
34
|
| Edit an existing object | `d365fo_file(action="modify")` (applies immediately — confirm in chat first) |
|
|
35
35
|
| Revert the last write | `undo_last_modification` |
|
|
36
36
|
| Search objects | `search` — multiple via `search(queries[])`, custom-only via `search(scope="extensions")` |
|
|
37
|
-
| Read any object's metadata | `get_object_info(objectType, name, options?)` — objectType ∈ class/table/form/query/view/enum/edt/report/data-entity/menu-item/service/map/config-key/security-policy/macro. 2+ known names: `
|
|
38
|
-
| Method signature for CoC | `
|
|
37
|
+
| Read any object's metadata | `get_object_info(objectType, name, options?)` — objectType ∈ class/table/form/query/view/enum/edt/report/data-entity/menu-item/service/map/config-key/security-policy/macro. 2+ known names: `get_object_info(objects=[{objectType,objectName},…])` — ONE call, never a loop |
|
|
38
|
+
| Method signature for CoC | `get_object_info(objectType="class", name, options={method, include:"signature"})` (already returned by `prepare(mode="change")`) |
|
|
39
39
|
| Validate X++ before write | `validate_code(mode="syntax", code)` — offline BP check, <50 ms |
|
|
40
40
|
| X++ rules & patterns | `get_knowledge(kind="knowledge", topic)` — select grammar, CoC, BP rules, SysOperation, workflow, … |
|
|
41
41
|
| Create a NEW form | `object_patterns(domain="form", action="analyze", recommend={...})` → `object_patterns(domain="form", action="spec", pattern)` → `generate_object(mode="scaffold", objectType="form", cloneFrom=referenceForm, tableMapping={...})` → `object_patterns(domain="form", action="validate", xml)` |
|
|
@@ -43,6 +43,7 @@ PowerShell / any terminal command **WILL HANG** in VS 2022 / VS 2026 MCP integra
|
|
|
43
43
|
| Resolve label / EDT / class refs | `validate_code(mode="references", code)` |
|
|
44
44
|
| Build / BP / Sync | `build_d365fo_project` / `run_bp_check` / `trigger_db_sync` |
|
|
45
45
|
| Error diagnosis | `get_knowledge(kind="error", errorText)` |
|
|
46
|
+
| Parameters for a `d365fo_file` operation / `generate_object` mode | `get_knowledge(kind="op-spec", topic="add-index" \| "table" \| "scaffold:form")` — those two tools keep their parameters OUT of the tool schema; look the contract up once for the operation you picked, then nest the values in `params` (`properties` for `action="create"`) |
|
|
46
47
|
|
|
47
48
|
## Key Rules
|
|
48
49
|
|
|
@@ -80,6 +81,23 @@ PowerShell / any terminal command **WILL HANG** in VS 2022 / VS 2026 MCP integra
|
|
|
80
81
|
11. **The post-write diff must be additive or narrowly targeted** — verify via `review_workspace_changes` (or re-read with `get_*_info`) that no unrelated XML nodes (`<DataSources>`, `<Controls>`, methods, pattern metadata) disappeared. If they did, the edit failed: `undo_last_modification`.
|
|
81
82
|
12. **An example form named by the user is a pattern contract** — keep its pattern family and required scaffolding (datasources, ActionPane/Tab/grid/QuickFilter); missing pattern elements are a failed generation even if the XML is well-formed.
|
|
82
83
|
|
|
84
|
+
### Spending tool calls
|
|
85
|
+
|
|
86
|
+
13. **Issue independent read-only calls together, in one step.** Every tool call re-reads the whole conversation, so a turn costs the round trip, not the tool. `get_object_info`, `search`, `labels`, `get_knowledge`, `object_patterns` and `find_references` have no side effects and never need to wait for each other — five lookups are one step, not five.
|
|
87
|
+
14. **Use the plural form when there is one** — `get_object_info(objects[])`, `run_bp_check(objects[])`, `verify_d365fo_project(objects[])`, `search(queries[])`. All three `objects[]` forms take `{objectType, objectName}`; `queries[]` takes `{query}`. Note `get_object_info`'s SINGLE-object form still spells it `name`, not `objectName` — take the key from the tool's schema, don't assume.
|
|
88
|
+
15. **Plan the reads before the first one.** Decide which objects the change touches, then fetch them in a single step instead of discovering them one call at a time.
|
|
89
|
+
|
|
90
|
+
### Reading D365FO objects
|
|
91
|
+
|
|
92
|
+
16. **Use `get_object_info`, not `read_file`, for anything under `PackagesLocalDirectory`.** Raw AOT XML is verbose and stays in context for the rest of the session; `get_object_info` returns the same facts structured and an order of magnitude smaller. Read the raw file only when you need its literal bytes.
|
|
93
|
+
17. **Never hand-edit AOT XML with text replacement** (rule 3) — whitespace and element ordering are load-bearing, so the match fails or the write corrupts the object. `d365fo_file(action="modify")` exists for exactly this.
|
|
94
|
+
18. **Do not call `update_symbol_index` after `d365fo_file` create/modify** — the index is already refreshed. It is for files changed OUTSIDE the server. Sole exception: a brand-new AxEdt/AxEnum you are about to name in a `generate_object` `fieldsHint`.
|
|
95
|
+
19. **Ask for the smallest result set that answers the question** — `labels(action="search")` returns 10 one-line hits; raise `maxResults` or set `verbose` only when that is genuinely not enough.
|
|
96
|
+
|
|
97
|
+
### Finishing
|
|
98
|
+
|
|
99
|
+
20. **Keep the closing summary to what changed and what to do next.** Long recaps are the most expensive single output of a session and are re-read by nothing.
|
|
100
|
+
|
|
83
101
|
## Full Instructions
|
|
84
102
|
|
|
85
103
|
The complete X++ rules, query grammar, CoC authoring rules, and workflow details are delivered via the MCP prompt `xpp_system_instructions`. If that prompt is not loaded, request it or consult [src/prompts/systemInstructions.ts](../src/prompts/systemInstructions.ts) directly.
|
package/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**23 AI tools that know every X++ class, table, form, and EDT in your D365FO codebase**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/d365fo-mcp)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
[](https://nodejs.org/)
|
|
10
10
|
[](https://www.typescriptlang.org/)
|
|
11
|
-
[](docs/TESTING.md)
|
|
12
12
|
<!-- coverage-badge:start -->
|
|
13
13
|
[](eval/COVERAGE.md) [](eval/COVERAGE.md)
|
|
14
14
|
<!-- coverage-badge:end -->
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
AI assistants excel at C#, Python, and JavaScript. X++ is different: your D365FO codebase is private, deeply customized, and invisible to every model — so AI confidently generates code that doesn't compile.
|
|
31
31
|
|
|
32
|
-
This server pre-indexes your entire D365FO installation (580 000+ symbols across standard, ISV, and custom models) and exposes it as
|
|
32
|
+
This server pre-indexes your entire D365FO installation (580 000+ symbols across standard, ISV, and custom models) and exposes it as 23 specialized MCP tools. Every signature, every CoC wrapper, every label, every form pattern — verified against your real metadata **before** the AI writes a single line.
|
|
33
33
|
|
|
34
34
|

|
|
35
35
|
|
|
@@ -115,12 +115,12 @@ Deployment guide: [docs/SETUP_AZURE.md](docs/SETUP_AZURE.md) — includes CI/CD
|
|
|
115
115
|
|
|
116
116
|
| Getting started | Reference | Operations |
|
|
117
117
|
|-----------------|-----------|------------|
|
|
118
|
-
| [Quick Start](docs/QUICK_START.md) — connect or install | [All
|
|
118
|
+
| [Quick Start](docs/QUICK_START.md) — connect or install | [All 23 tools](docs/MCP_TOOLS.md) | [Azure deployment](docs/SETUP_AZURE.md) |
|
|
119
119
|
| [Setup scenarios A–F](docs/SETUP.md) | [`.mcp.json` reference](docs/MCP_CONFIG.md) | [DevOps pipelines](docs/SETUP_AZURE.md#azure-devops-pipelines) |
|
|
120
120
|
| [Claude Code setup](docs/SETUP.md#claude-code-cli) | [Configuration](docs/CONFIGURATION.md) | [Testing](docs/TESTING.md) |
|
|
121
121
|
| [Usage examples](docs/USAGE_EXAMPLES.md) — real tool chains | [Architecture](docs/ARCHITECTURE.md) | [Custom / ISV models](docs/CUSTOM_EXTENSIONS.md) |
|
|
122
|
-
| | | [Coverage](eval/COVERAGE.md) — what the badge counts |
|
|
123
|
-
| | | [Eval loop](docs/AGENT_EVAL_LOOP.md) — the self-improvement harness |
|
|
122
|
+
| [Changelog](CHANGELOG.md) | [Knowledge authoring](docs/KNOWLEDGE_AUTHORING.md) | [Coverage](eval/COVERAGE.md) — what the badge counts |
|
|
123
|
+
| [Backlog](docs/BACKLOG.md) — deferred work | [New tool checklist](docs/NEW_TOOL_CHECKLIST.md) | [Eval loop](docs/AGENT_EVAL_LOOP.md) — the self-improvement harness |
|
|
124
124
|
|
|
125
125
|
## License
|
|
126
126
|
|
|
@@ -781,23 +781,46 @@ namespace D365MetadataBridge.Models
|
|
|
781
781
|
/// <summary>
|
|
782
782
|
/// Deserializes a typed parameter from the Params dictionary.
|
|
783
783
|
/// Values arrive as JsonElement from System.Text.Json — convert to the target type.
|
|
784
|
+
///
|
|
785
|
+
/// The old `catch { return null; }` made a WRONGLY SHAPED parameter
|
|
786
|
+
/// indistinguishable from an absent one, and the dispatcher's
|
|
787
|
+
/// `?? throw new ArgumentException("Missing: fields")` then told the caller the key
|
|
788
|
+
/// was missing while it sat right there in the request. That is the param-shape
|
|
789
|
+
/// contract bug this project keeps re-living — e.g. constraints sent as
|
|
790
|
+
/// [{fieldName, relatedFieldName}] when this side reads [{field, relatedField}], or
|
|
791
|
+
/// index fields sent as [{fieldName}] when this side reads a flat string[]. A
|
|
792
|
+
/// failure now names the parameter and shows what actually arrived.
|
|
793
|
+
///
|
|
794
|
+
/// JsonOptions.Default is used so the batch path resolves property names exactly the
|
|
795
|
+
/// way the single-op BridgeRequest.GetParam<T> does; deserializing without it was
|
|
796
|
+
/// a second, quieter way for the two paths to disagree.
|
|
784
797
|
/// </summary>
|
|
785
798
|
public T? GetTypedParam<T>(string key) where T : class
|
|
786
799
|
{
|
|
787
800
|
if (Params == null || !Params.TryGetValue(key, out var raw) || raw == null)
|
|
788
801
|
return null;
|
|
802
|
+
// Already the correct type (in-process caller, not a JSON round trip).
|
|
803
|
+
if (raw is T typed) return typed;
|
|
804
|
+
|
|
805
|
+
var json = raw is System.Text.Json.JsonElement je
|
|
806
|
+
? je.GetRawText()
|
|
807
|
+
: System.Text.Json.JsonSerializer.Serialize(raw, Protocol.JsonOptions.Default);
|
|
789
808
|
try
|
|
790
809
|
{
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
810
|
+
// A JSON `null` deserializes to null here — same as an absent key, which is
|
|
811
|
+
// what the caller's "Missing: <key>" message correctly describes.
|
|
812
|
+
return System.Text.Json.JsonSerializer.Deserialize<T>(json, Protocol.JsonOptions.Default);
|
|
813
|
+
}
|
|
814
|
+
catch (System.Text.Json.JsonException ex)
|
|
815
|
+
{
|
|
816
|
+
throw new System.ArgumentException(
|
|
817
|
+
$"Parameter '{key}' has the wrong shape: {ex.Message} Received: {Preview(json)}");
|
|
798
818
|
}
|
|
799
|
-
catch { return null; }
|
|
800
819
|
}
|
|
820
|
+
|
|
821
|
+
/// <summary>Caps the echoed payload — a batch op can carry a large array.</summary>
|
|
822
|
+
private static string Preview(string json) =>
|
|
823
|
+
json.Length <= 300 ? json : json.Substring(0, 300) + "…(truncated)";
|
|
801
824
|
}
|
|
802
825
|
|
|
803
826
|
public class BatchOperationResult
|
|
@@ -48,18 +48,19 @@ namespace D365MetadataBridge.Protocol
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/// <summary>
|
|
51
|
-
/// Helper to extract a boolean parameter from Params
|
|
51
|
+
/// Helper to extract a boolean parameter from Params.
|
|
52
|
+
/// Reads through ParamCoercion so this path and HandleBatchModify cannot disagree
|
|
53
|
+
/// about what a given value means.
|
|
52
54
|
/// </summary>
|
|
53
55
|
public bool? GetBoolParam(string name)
|
|
54
56
|
{
|
|
55
57
|
if (Params == null || Params.Value.ValueKind != JsonValueKind.Object)
|
|
56
58
|
return null;
|
|
57
59
|
|
|
58
|
-
if (Params.Value.TryGetProperty(name, out var prop)
|
|
59
|
-
|
|
60
|
-
return prop.GetBoolean();
|
|
60
|
+
if (!Params.Value.TryGetProperty(name, out var prop))
|
|
61
|
+
return null;
|
|
61
62
|
|
|
62
|
-
return
|
|
63
|
+
return ParamCoercion.ToBool(prop, name);
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/// <summary>
|
|
@@ -116,6 +117,80 @@ namespace D365MetadataBridge.Protocol
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
/// <summary>
|
|
121
|
+
/// Parameter coercion shared by BOTH dispatch paths.
|
|
122
|
+
///
|
|
123
|
+
/// The single-op path reads its params straight off the request's JsonElement; the
|
|
124
|
+
/// batch path gets them as Dictionary<string, object>, whose values System.Text.Json
|
|
125
|
+
/// boxes as JsonElement. JsonElement does not implement IConvertible, so the batch
|
|
126
|
+
/// path's Convert.ToBoolean(value) threw InvalidCastException ("Object must implement
|
|
127
|
+
/// IConvertible") for EVERY operation carrying a bool — mandatory, allowDuplicates,
|
|
128
|
+
/// alternateKey, extendBaseFieldGroup — and HandleBatchModify's per-op catch reported
|
|
129
|
+
/// that cast as the operation's reason for failing. Booleans are read by JSON kind, in
|
|
130
|
+
/// one place, so the two paths cannot drift apart again.
|
|
131
|
+
/// </summary>
|
|
132
|
+
public static class ParamCoercion
|
|
133
|
+
{
|
|
134
|
+
/// <summary>
|
|
135
|
+
/// Boolean from a raw (boxed) batch parameter value.
|
|
136
|
+
/// </summary>
|
|
137
|
+
public static bool? ToBool(object? value, string paramName)
|
|
138
|
+
{
|
|
139
|
+
switch (value)
|
|
140
|
+
{
|
|
141
|
+
case null: return null;
|
|
142
|
+
case bool b: return b;
|
|
143
|
+
case JsonElement el: return ToBool(el, paramName);
|
|
144
|
+
case string s: return ParseBoolText(s, paramName, s);
|
|
145
|
+
default:
|
|
146
|
+
throw new ArgumentException(
|
|
147
|
+
$"Parameter '{paramName}' must be a boolean — got {value.GetType().Name} '{value}'.");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// <summary>
|
|
152
|
+
/// Boolean from a JSON value. Absent/null yields null so the caller's own default
|
|
153
|
+
/// applies; a value that is present but not readable as a boolean throws rather
|
|
154
|
+
/// than degrading to false — a silent false writes a field that is not mandatory,
|
|
155
|
+
/// or an index that allows duplicates, and reports success either way.
|
|
156
|
+
///
|
|
157
|
+
/// The string spellings are accepted because the AxTable XML value is No/Yes and
|
|
158
|
+
/// callers reach for that form (#27); it is a typo, not a shape, that fails here.
|
|
159
|
+
/// </summary>
|
|
160
|
+
public static bool? ToBool(JsonElement el, string paramName)
|
|
161
|
+
{
|
|
162
|
+
switch (el.ValueKind)
|
|
163
|
+
{
|
|
164
|
+
case JsonValueKind.True: return true;
|
|
165
|
+
case JsonValueKind.False: return false;
|
|
166
|
+
case JsonValueKind.Null:
|
|
167
|
+
case JsonValueKind.Undefined: return null;
|
|
168
|
+
case JsonValueKind.String: return ParseBoolText(el.GetString(), paramName, el.GetRawText());
|
|
169
|
+
case JsonValueKind.Number when el.TryGetInt32(out var n) && (n == 0 || n == 1):
|
|
170
|
+
return n == 1;
|
|
171
|
+
default:
|
|
172
|
+
throw new ArgumentException(
|
|
173
|
+
$"Parameter '{paramName}' must be a boolean — got {el.ValueKind} {el.GetRawText()}. " +
|
|
174
|
+
"Accepted: true/false, \"true\"/\"false\", \"Yes\"/\"No\", 1/0.");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private static bool? ParseBoolText(string? text, string paramName, string received)
|
|
179
|
+
{
|
|
180
|
+
switch (text?.Trim().ToLowerInvariant())
|
|
181
|
+
{
|
|
182
|
+
case null:
|
|
183
|
+
case "": return null;
|
|
184
|
+
case "true": case "yes": case "1": return true;
|
|
185
|
+
case "false": case "no": case "0": return false;
|
|
186
|
+
default:
|
|
187
|
+
throw new ArgumentException(
|
|
188
|
+
$"Parameter '{paramName}' must be a boolean — got {received}. " +
|
|
189
|
+
"Accepted: true/false, \"true\"/\"false\", \"Yes\"/\"No\", 1/0.");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
119
194
|
/// <summary>
|
|
120
195
|
/// JSON-RPC style response sent to the Node.js MCP server
|
|
121
196
|
/// </summary>
|
|
@@ -461,9 +461,12 @@ namespace D365MetadataBridge.Protocol
|
|
|
461
461
|
// path inside AddField; both are absent for table/table-extension.
|
|
462
462
|
// This is the single-op RPC that BridgeClient.addField() calls —
|
|
463
463
|
// it must forward the same parameters as the batch-modify case below.
|
|
464
|
+
// `type`/`extendedDataType` are the spellings the create path's
|
|
465
|
+
// fields[] uses for the same two things; accepting only the
|
|
466
|
+
// add-field spelling silently defaulted them (String, no EDT).
|
|
464
467
|
return _writeService!.AddField(tableName, fieldName,
|
|
465
|
-
request.GetStringParam("fieldType") ?? "String",
|
|
466
|
-
request.GetStringParam("edt"),
|
|
468
|
+
request.GetStringParam("fieldType") ?? request.GetStringParam("type") ?? "String",
|
|
469
|
+
request.GetStringParam("edt") ?? request.GetStringParam("extendedDataType"),
|
|
467
470
|
request.GetBoolParam("mandatory") ?? false,
|
|
468
471
|
request.GetStringParam("label"),
|
|
469
472
|
request.GetStringParam("dataField"),
|
|
@@ -951,7 +954,12 @@ namespace D365MetadataBridge.Protocol
|
|
|
951
954
|
|
|
952
955
|
// Extract string helper
|
|
953
956
|
string? S(string key) => p.TryGetValue(key, out var v) ? v?.ToString() : null;
|
|
954
|
-
|
|
957
|
+
// System.Text.Json boxes every params value as JsonElement, which does
|
|
958
|
+
// not implement IConvertible — Convert.ToBoolean(v) therefore threw
|
|
959
|
+
// InvalidCastException on every batch op carrying a bool, and the catch
|
|
960
|
+
// below reported that cast as the operation's failure. Same coercion as
|
|
961
|
+
// the single-op arms' request.GetBoolParam.
|
|
962
|
+
bool? B(string key) => p.TryGetValue(key, out var v) ? ParamCoercion.ToBool(v, key) : null;
|
|
955
963
|
|
|
956
964
|
switch (op.Operation.ToLowerInvariant())
|
|
957
965
|
{
|
|
@@ -972,10 +980,11 @@ namespace D365MetadataBridge.Protocol
|
|
|
972
980
|
case "add-field":
|
|
973
981
|
// dataField/dataSource select the data-entity-extension mapped-field
|
|
974
982
|
// path inside AddField; both are absent for table/table-extension.
|
|
983
|
+
// Same `type`/`extendedDataType` aliasing as the single-op RPC.
|
|
975
984
|
writeResult = _writeService.AddField(objectName,
|
|
976
985
|
S("fieldName") ?? throw new ArgumentException("Missing: fieldName"),
|
|
977
|
-
S("fieldType") ?? "String",
|
|
978
|
-
S("edt"),
|
|
986
|
+
S("fieldType") ?? S("type") ?? "String",
|
|
987
|
+
S("edt") ?? S("extendedDataType"),
|
|
979
988
|
B("mandatory") ?? false,
|
|
980
989
|
S("label"),
|
|
981
990
|
S("dataField"),
|
|
@@ -1096,9 +1105,18 @@ namespace D365MetadataBridge.Protocol
|
|
|
1096
1105
|
case "addenumvalue":
|
|
1097
1106
|
case "add-enum-value":
|
|
1098
1107
|
{
|
|
1108
|
+
// A value that would not parse used to fall through as 0 —
|
|
1109
|
+
// so add-enum-value(value:"three") wrote element 0, a
|
|
1110
|
+
// duplicate of the enum's first member, and reported success.
|
|
1099
1111
|
int enumVal = 0;
|
|
1100
1112
|
if (p.TryGetValue("enumValue", out var ev) || p.TryGetValue("value", out ev))
|
|
1101
|
-
|
|
1113
|
+
{
|
|
1114
|
+
var evText = ev?.ToString();
|
|
1115
|
+
if (!string.IsNullOrWhiteSpace(evText) && !int.TryParse(evText, out enumVal))
|
|
1116
|
+
throw new ArgumentException(
|
|
1117
|
+
$"add-enum-value: value '{evText}' is not an integer — nothing was written. " +
|
|
1118
|
+
"Enum values are integers 0-250; omit the parameter to take the next free position.");
|
|
1119
|
+
}
|
|
1102
1120
|
writeResult = _writeService.AddEnumValue(objectName,
|
|
1103
1121
|
S("enumValueName") ?? S("valueName") ?? throw new ArgumentException("Missing: enumValueName"),
|
|
1104
1122
|
enumVal, S("label"), S("countryRegionCodes"));
|
|
@@ -10,9 +10,36 @@ namespace D365MetadataBridge.Services
|
|
|
10
10
|
/// <summary>
|
|
11
11
|
/// Provides cross-reference queries using the DYNAMICSXREFDB SQL database.
|
|
12
12
|
/// This replaces the FTS5 text-search approach with real compiler-resolved references.
|
|
13
|
+
///
|
|
14
|
+
/// ## Query failures are errors, never empty results
|
|
15
|
+
///
|
|
16
|
+
/// Every method here answers one question — "what else touches this?" — and the answer
|
|
17
|
+
/// drives whether it is safe to change something. A failed query used to be returned as
|
|
18
|
+
/// a SUCCESSFUL response carrying count=0 plus an `error` field, which reads to any
|
|
19
|
+
/// caller that does not inspect the extra field as the strongest possible clearance:
|
|
20
|
+
/// "nothing references this, go ahead". A down or unreachable xref database therefore
|
|
21
|
+
/// produced false negatives on exactly the question the service exists to answer, and
|
|
22
|
+
/// the more broken the database, the safer everything looked.
|
|
23
|
+
///
|
|
24
|
+
/// So failures throw. RequestDispatcher.HandleXref turns them into a JSON-RPC error, and
|
|
25
|
+
/// the TS adapters already treat a rejected call as "no answer from the bridge" and fall
|
|
26
|
+
/// back to their own scan instead of trusting a zero.
|
|
13
27
|
/// </summary>
|
|
14
28
|
public class CrossReferenceService
|
|
15
29
|
{
|
|
30
|
+
/// <summary>
|
|
31
|
+
/// Rethrows a failed xref query as an error the dispatcher will surface, with the
|
|
32
|
+
/// query's subject in the message so the caller can see what was NOT answered.
|
|
33
|
+
/// </summary>
|
|
34
|
+
private static Exception QueryFailed(string operation, string subject, Exception cause)
|
|
35
|
+
{
|
|
36
|
+
Console.Error.WriteLine($"[CrossRefService] {operation}({subject}) failed: {cause.Message}");
|
|
37
|
+
return new InvalidOperationException(
|
|
38
|
+
$"Cross-reference query {operation}('{subject}') failed: {cause.Message}. " +
|
|
39
|
+
"This is NOT an empty result — the cross-reference database did not answer, so nothing " +
|
|
40
|
+
"can be concluded about references to this object.", cause);
|
|
41
|
+
}
|
|
42
|
+
|
|
16
43
|
private readonly string _connectionString;
|
|
17
44
|
|
|
18
45
|
public CrossReferenceService(string server, string database)
|
|
@@ -209,8 +236,7 @@ namespace D365MetadataBridge.Services
|
|
|
209
236
|
}
|
|
210
237
|
catch (SqlException ex)
|
|
211
238
|
{
|
|
212
|
-
|
|
213
|
-
return new { objectPath, count = 0, references = new List<ReferenceInfoModel>(), error = ex.Message };
|
|
239
|
+
throw QueryFailed("findReferences", objectPath, ex);
|
|
214
240
|
}
|
|
215
241
|
|
|
216
242
|
return new { objectPath, count = references.Count, references };
|
|
@@ -335,15 +361,7 @@ namespace D365MetadataBridge.Services
|
|
|
335
361
|
}
|
|
336
362
|
catch (Exception ex)
|
|
337
363
|
{
|
|
338
|
-
|
|
339
|
-
return new
|
|
340
|
-
{
|
|
341
|
-
baseClassName,
|
|
342
|
-
count = 0,
|
|
343
|
-
extensions = new List<ExtensionClassDetailModel>(),
|
|
344
|
-
error = ex.Message,
|
|
345
|
-
_source = "C# bridge (DYNAMICSXREFDB)"
|
|
346
|
-
};
|
|
364
|
+
throw QueryFailed("findExtensionClasses", baseClassName, ex);
|
|
347
365
|
}
|
|
348
366
|
}
|
|
349
367
|
|
|
@@ -447,8 +465,7 @@ namespace D365MetadataBridge.Services
|
|
|
447
465
|
}
|
|
448
466
|
catch (Exception ex)
|
|
449
467
|
{
|
|
450
|
-
|
|
451
|
-
return new { targetName, count = 0, handlers = results, error = ex.Message, _source = "C# bridge (DYNAMICSXREFDB)" };
|
|
468
|
+
throw QueryFailed("findEventSubscribers", targetName, ex);
|
|
452
469
|
}
|
|
453
470
|
}
|
|
454
471
|
|
|
@@ -574,8 +591,7 @@ namespace D365MetadataBridge.Services
|
|
|
574
591
|
}
|
|
575
592
|
catch (SqlException ex)
|
|
576
593
|
{
|
|
577
|
-
|
|
578
|
-
return new { apiName, count = 0, callers = new List<ApiUsageCallerModel>(), error = ex.Message, _source = "C# bridge (DYNAMICSXREFDB)" };
|
|
594
|
+
throw QueryFailed("findApiUsageCallers", apiName, ex);
|
|
579
595
|
}
|
|
580
596
|
|
|
581
597
|
// Group by caller class for pattern summary
|
|
@@ -125,13 +125,36 @@ namespace D365MetadataBridge.Services
|
|
|
125
125
|
{
|
|
126
126
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
|
127
127
|
var factory = new MetadataProviderFactory();
|
|
128
|
+
var previous = _provider;
|
|
128
129
|
_provider = CreatePrimaryProvider(factory, _packagesPath, _referencePackagesPath);
|
|
130
|
+
// Hand the new provider over BEFORE releasing the old one, so nothing is
|
|
131
|
+
// holding a provider that is about to be disposed.
|
|
129
132
|
OnProviderRefreshed?.Invoke(_provider);
|
|
133
|
+
// Every write auto-refreshes (HandleWrite refreshAfterSuccess), so a long
|
|
134
|
+
// authoring session builds one DiskProvider per write. Dropping the old one
|
|
135
|
+
// on the floor kept its file handles and per-package metadata caches alive
|
|
136
|
+
// over the whole PackagesLocalDirectory until GC felt like it — the bridge
|
|
137
|
+
// grew until it stopped answering, which is the state the client sees as a
|
|
138
|
+
// wedged process.
|
|
139
|
+
DisposeProvider(previous);
|
|
130
140
|
sw.Stop();
|
|
131
141
|
Console.Error.WriteLine($"[MetadataService] Provider refreshed in {sw.ElapsedMilliseconds}ms");
|
|
132
142
|
return new { refreshed = true, elapsedMs = sw.ElapsedMilliseconds };
|
|
133
143
|
}
|
|
134
144
|
|
|
145
|
+
/// <summary>
|
|
146
|
+
/// Releases a superseded provider. Whether DiskProvider implements IDisposable
|
|
147
|
+
/// varies across metamodel versions, so this asks rather than assumes; a failure
|
|
148
|
+
/// to release is logged and swallowed because the replacement is already live and
|
|
149
|
+
/// the refresh itself succeeded.
|
|
150
|
+
/// </summary>
|
|
151
|
+
private static void DisposeProvider(IMetadataProvider? provider)
|
|
152
|
+
{
|
|
153
|
+
if (provider is not IDisposable disposable) return;
|
|
154
|
+
try { disposable.Dispose(); }
|
|
155
|
+
catch (Exception ex) { Console.Error.WriteLine($"[WARN] Failed to dispose superseded metadata provider: {ex.Message}"); }
|
|
156
|
+
}
|
|
157
|
+
|
|
135
158
|
/// <summary>
|
|
136
159
|
/// Asks IMetadataProvider to read back an object that was just written to disk.
|
|
137
160
|
/// Returns field/method counts and a success flag — proves the XML is well-formed
|
|
@@ -144,11 +167,23 @@ namespace D365MetadataBridge.Services
|
|
|
144
167
|
switch (objectType.ToLowerInvariant())
|
|
145
168
|
{
|
|
146
169
|
case "table":
|
|
147
|
-
case "table-extension":
|
|
148
170
|
if (!_provider.Tables.Exists(objectName)) return new { valid = false, reason = $"Table '{objectName}' not found by IMetadataProvider after refresh" };
|
|
149
171
|
var t = _provider.Tables.Read(objectName);
|
|
150
172
|
return new { valid = true, objectType, objectName, fieldCount = t?.Fields?.Count ?? 0, methodCount = t?.Methods?.Count ?? 0, indexCount = t?.Indexes?.Count ?? 0 };
|
|
151
173
|
|
|
174
|
+
// Extensions live in their OWN provider collection, keyed by the dotted
|
|
175
|
+
// "Base.ModelExtension" name. Tables/Forms are keyed by plain names, so
|
|
176
|
+
// looking an extension up there always misses — a false negative on every
|
|
177
|
+
// extension write, regardless of refresh.
|
|
178
|
+
case "table-extension":
|
|
179
|
+
{
|
|
180
|
+
var tx = _provider.TableExtensions.Read(objectName);
|
|
181
|
+
if (tx == null) return new { valid = false, reason = $"TableExtension '{objectName}' not found by IMetadataProvider after refresh" };
|
|
182
|
+
int txMethods = 0;
|
|
183
|
+
try { dynamic dtx = tx; if (dtx?.Methods != null) foreach (var _ in dtx.Methods) txMethods++; } catch { }
|
|
184
|
+
return new { valid = true, objectType, objectName, fieldCount = tx.Fields?.Count ?? 0, methodCount = txMethods, indexCount = tx.Indexes?.Count ?? 0 };
|
|
185
|
+
}
|
|
186
|
+
|
|
152
187
|
case "class":
|
|
153
188
|
case "class-extension":
|
|
154
189
|
if (!_provider.Classes.Exists(objectName)) return new { valid = false, reason = $"Class '{objectName}' not found by IMetadataProvider after refresh" };
|
|
@@ -167,10 +202,13 @@ namespace D365MetadataBridge.Services
|
|
|
167
202
|
return new { valid = true, objectType, objectName };
|
|
168
203
|
|
|
169
204
|
case "form":
|
|
170
|
-
case "form-extension":
|
|
171
205
|
if (!_provider.Forms.Exists(objectName)) return new { valid = false, reason = $"Form '{objectName}' not found by IMetadataProvider after refresh" };
|
|
172
206
|
return new { valid = true, objectType, objectName };
|
|
173
207
|
|
|
208
|
+
case "form-extension":
|
|
209
|
+
if (_provider.FormExtensions.Read(objectName) == null) return new { valid = false, reason = $"FormExtension '{objectName}' not found by IMetadataProvider after refresh" };
|
|
210
|
+
return new { valid = true, objectType, objectName };
|
|
211
|
+
|
|
174
212
|
case "query":
|
|
175
213
|
if (!_provider.Queries.Exists(objectName)) return new { valid = false, reason = $"Query '{objectName}' not found by IMetadataProvider after refresh" };
|
|
176
214
|
return new { valid = true, objectType, objectName };
|
|
@@ -235,7 +273,6 @@ namespace D365MetadataBridge.Services
|
|
|
235
273
|
switch (objectType.ToLowerInvariant())
|
|
236
274
|
{
|
|
237
275
|
case "table":
|
|
238
|
-
case "table-extension":
|
|
239
276
|
{
|
|
240
277
|
var prov = PickProvider(p => p.Tables.Exists(objectName));
|
|
241
278
|
if (prov == null) return null;
|
|
@@ -243,6 +280,17 @@ namespace D365MetadataBridge.Services
|
|
|
243
280
|
try { var mi = prov.Tables.GetModelInfo(objectName); if (mi?.Count > 0) model = mi.First().Name; } catch { }
|
|
244
281
|
return new { exists = true, objectType, objectName, model };
|
|
245
282
|
}
|
|
283
|
+
// Probed against TableExtensions/FormExtensions, not Tables/Forms — see
|
|
284
|
+
// the note in ValidateObject: the dotted extension name is never a key
|
|
285
|
+
// in the base collection, so probing there never resolves an extension.
|
|
286
|
+
case "table-extension":
|
|
287
|
+
{
|
|
288
|
+
var prov = PickProvider(p => p.TableExtensions.Read(objectName) != null);
|
|
289
|
+
if (prov == null) return null;
|
|
290
|
+
string? model = null;
|
|
291
|
+
try { var mi = prov.TableExtensions.GetModelInfo(objectName); if (mi?.Count > 0) model = mi.First().Name; } catch { }
|
|
292
|
+
return new { exists = true, objectType, objectName, model };
|
|
293
|
+
}
|
|
246
294
|
case "class":
|
|
247
295
|
case "class-extension":
|
|
248
296
|
{
|
|
@@ -269,7 +317,6 @@ namespace D365MetadataBridge.Services
|
|
|
269
317
|
return new { exists = true, objectType, objectName, model };
|
|
270
318
|
}
|
|
271
319
|
case "form":
|
|
272
|
-
case "form-extension":
|
|
273
320
|
{
|
|
274
321
|
var prov = PickProvider(p => p.Forms.Exists(objectName));
|
|
275
322
|
if (prov == null) return null;
|
|
@@ -277,6 +324,14 @@ namespace D365MetadataBridge.Services
|
|
|
277
324
|
try { var mi = prov.Forms.GetModelInfo(objectName); if (mi?.Count > 0) model = mi.First().Name; } catch { }
|
|
278
325
|
return new { exists = true, objectType, objectName, model };
|
|
279
326
|
}
|
|
327
|
+
case "form-extension":
|
|
328
|
+
{
|
|
329
|
+
var prov = PickProvider(p => p.FormExtensions.Read(objectName) != null);
|
|
330
|
+
if (prov == null) return null;
|
|
331
|
+
string? model = null;
|
|
332
|
+
try { var mi = prov.FormExtensions.GetModelInfo(objectName); if (mi?.Count > 0) model = mi.First().Name; } catch { }
|
|
333
|
+
return new { exists = true, objectType, objectName, model };
|
|
334
|
+
}
|
|
280
335
|
case "query":
|
|
281
336
|
{
|
|
282
337
|
var prov = PickProvider(p => p.Queries.Exists(objectName));
|