Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
85c0f69
Configuration validation and UX improvements
onbuyuka Dec 16, 2025
4cdb287
Configuration validation and UX improvements
onbuyuka Dec 16, 2025
c24e47f
Configuration validation and UX improvements
onbuyuka Dec 16, 2025
4e7b464
Configuration validation and UX improvements
onbuyuka Dec 17, 2025
19d00c8
Configuration validation and UX improvements
onbuyuka Dec 18, 2025
a1d955b
Apply suggestions from code review
onbuyuka Dec 23, 2025
fa35834
Merge branch 'main' of https://github.com/microsoft/BCApps into featu…
onbuyuka Jan 6, 2026
9326d32
Merge branch 'features/615692-MCPVerification' of https://github.com/…
onbuyuka Jan 6, 2026
5b88e6c
Update
onbuyuka Jan 7, 2026
939560e
Update
onbuyuka Jan 7, 2026
c2ac3f9
Merge branch 'main' of https://github.com/microsoft/BCApps into featu…
onbuyuka Jan 12, 2026
5b68dd3
Update
onbuyuka Jan 12, 2026
9c42225
Update
onbuyuka Jan 13, 2026
3887106
Merge branch 'main' of https://github.com/microsoft/BCApps into featu…
onbuyuka Jan 15, 2026
393277e
Update
onbuyuka Jan 15, 2026
8e3aa2e
Merge
onbuyuka Jan 15, 2026
519d0bb
Add new warning type for no read yes modify tools
onbuyuka Jan 15, 2026
395a1e3
Add new warning type for no read yes modify tools
onbuyuka Jan 15, 2026
ee100dc
Merge
onbuyuka Jan 16, 2026
1cd153f
Update
onbuyuka Jan 16, 2026
e04fe95
Add MCP Config Warning permission to PermissionSet
onbuyuka Jan 16, 2026
f546d84
Merge branch 'main' of https://github.com/microsoft/BCApps into featu…
onbuyuka Jan 20, 2026
0e9d729
Update
onbuyuka Jan 22, 2026
c33da5e
Update
onbuyuka Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ permissionset 8350 "MCP - Objects"
Permissions = table "MCP API Publisher Group" = X,
table "MCP Configuration" = X,
table "MCP Configuration Tool" = X,
table "MCP Entra Application" = X;
}
table "MCP Config Warning" = X,
table "MCP Entra Application" = X,
table "MCP System Tool" = X;
}
4 changes: 4 additions & 0 deletions src/System Application/App/MCP/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
{
"from": 8350,
"to": 8360
},
{
"from": 8365,
"to": 8370
}
],
"target": "OnPrem",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,26 @@ codeunit 8350 "MCP Config"
MCPConfigImplementation.EnableDiscoverReadOnlyObjects(ConfigId, Enable);
end;

/// <summary>
/// Finds warnings for the specified MCP configuration, such as missing objects or missing parent objects.
/// </summary>
/// <param name="ConfigId">The SystemId (GUID) of the configuration to find warnings for.</param>
/// <param name="MCPConfigWarning">A temporary record variable to hold the found warnings.</param>
/// <returns>True if any warnings were found; otherwise, false.</returns>
procedure FindWarningsForConfiguration(ConfigId: Guid; var MCPConfigWarning: Record "MCP Config Warning"): Boolean
begin
exit(MCPConfigImplementation.FindWarningsForConfiguration(ConfigId, MCPConfigWarning));
end;

/// <summary>
/// Applies the recommended action for the specified warning.
/// </summary>
/// <param name="MCPConfigWarning">The warning record to apply the recommended action for.</param>
procedure ApplyRecommendedAction(var MCPConfigWarning: Record "MCP Config Warning")
begin
MCPConfigImplementation.ApplyRecommendedAction(MCPConfigWarning);
end;

/// <summary>
/// Creates a new API tool for the specified configuration and API page.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ codeunit 8351 "MCP Config Implementation"
MCPConfigurationAuditCreatedLbl: Label 'MCP Configuration %1 created by user %2 in company %3', Comment = '%1 - configuration name, %2 - user security ID, %3 - company name', Locked = true;
MCPConfigurationAuditModifiedLbl: Label 'MCP Configuration %1 modified by user %2 in company %3', Comment = '%1 - configuration name, %2 - user security ID, %3 - company name', Locked = true;
MCPConfigurationAuditDeletedLbl: Label 'MCP Configuration %1 deleted by user %2 in company %3', Comment = '%1 - configuration name, %2 - user security ID, %3 - company name', Locked = true;
InvalidConfigurationWarningLbl: Label 'The configuration is invalid and may not work as expected. Do you want to review warnings before activating?';
ConfigValidLbl: Label 'No warnings found. The configuration is valid.';
ConnectionStringLbl: Label '%1 Connection String', Comment = '%1 - configuration name';
MCPUrlProdLbl: Label 'https://mcp.businesscentral.dynamics.com', Locked = true;
MCPUrlTIELbl: Label 'https://mcp.businesscentral.dynamics-tie.com', Locked = true;
Expand Down Expand Up @@ -266,6 +268,90 @@ codeunit 8351 "MCP Config Implementation"
begin
exit(MCPConfiguration.Name = '');
end;

internal procedure IsConfigurationActive(ConfigId: Guid): Boolean
var
MCPConfiguration: Record "MCP Configuration";
begin
if MCPConfiguration.GetBySystemId(ConfigId) then
exit(MCPConfiguration.Active);
exit(false);
end;

internal procedure ValidateConfiguration(var MCPConfiguration: Record "MCP Configuration"; OnActivate: Boolean)
var
MCPConfigurationWarning: Record "MCP Config Warning";
begin
// Raise warning if any issues found
if not FindWarningsForConfiguration(MCPConfiguration.SystemId, MCPConfigurationWarning) then begin
if not OnActivate then
Message(ConfigValidLbl);
exit;
end;

if OnActivate then
if not Confirm(InvalidConfigurationWarningLbl) then
exit;

MCPConfiguration.Active := false;
Page.Run(Page::"MCP Config Warning List", MCPConfigurationWarning);
end;

internal procedure FindWarningsForConfiguration(ConfigId: Guid; var MCPConfigurationWarning: Record "MCP Config Warning"): Boolean
var
IMCPConfigWarning: Interface "MCP Config Warning";
MCPConfigWarningType: Enum "MCP Config Warning Type";
WarningImplementations: List of [Integer];
WarningImplementation: Integer;
EntryNo: Integer;
begin
if MCPConfigurationWarning.FindLast() then
EntryNo := MCPConfigurationWarning."Entry No." + 1
else
EntryNo := 1;

WarningImplementations := MCPConfigWarningType.Ordinals();
foreach WarningImplementation in WarningImplementations do begin
IMCPConfigWarning := "MCP Config Warning Type".FromInteger(WarningImplementation);
IMCPConfigWarning.CheckForWarnings(ConfigId, MCPConfigurationWarning, EntryNo);
end;

exit(not MCPConfigurationWarning.IsEmpty());
end;

internal procedure GetWarningMessage(MCPConfigWarning: Record "MCP Config Warning"): Text
var
IMCPConfigWarning: Interface "MCP Config Warning";
begin
IMCPConfigWarning := MCPConfigWarning."Warning Type";
exit(IMCPConfigWarning.WarningMessage(MCPConfigWarning));
end;

internal procedure GetRecommendedAction(MCPConfigWarning: Record "MCP Config Warning"): Text
var
IMCPConfigWarning: Interface "MCP Config Warning";
begin
IMCPConfigWarning := MCPConfigWarning."Warning Type";
exit(IMCPConfigWarning.RecommendedAction(MCPConfigWarning));
end;

internal procedure ApplyRecommendedActions(var MCPConfigWarning: Record "MCP Config Warning")
begin
if not MCPConfigWarning.FindSet() then
exit;

repeat
ApplyRecommendedAction(MCPConfigWarning);
until MCPConfigWarning.Next() = 0;
end;

internal procedure ApplyRecommendedAction(var MCPConfigWarning: Record "MCP Config Warning")
var
IMCPConfigWarning: Interface "MCP Config Warning";
begin
IMCPConfigWarning := MCPConfigWarning."Warning Type";
IMCPConfigWarning.ApplyRecommendedAction(MCPConfigWarning);
end;
#endregion

#region Tools
Expand Down Expand Up @@ -503,7 +589,7 @@ codeunit 8351 "MCP Config Implementation"
until PageMetadata.Next() = 0;
end;

local procedure CheckAPIToolExists(ConfigId: Guid; PageId: Integer): Boolean
internal procedure CheckAPIToolExists(ConfigId: Guid; PageId: Integer): Boolean
var
MCPConfigurationTool: Record "MCP Configuration Tool";
begin
Expand All @@ -529,6 +615,27 @@ codeunit 8351 "MCP Config Implementation"
exit(CopyStr(AllObjWithCaption."Object Name", 1, 100));
exit('');
end;

internal procedure LoadSystemTools(var MCPSystemTool: Record "MCP System Tool")
var
MCPUtilities: Codeunit "MCP Utilities";
SystemTools: Dictionary of [Text, Text];
ToolName: Text;
begin
MCPSystemTool.Reset();
MCPSystemTool.DeleteAll();

SystemTools := MCPUtilities.GetSystemToolsInDynamicMode();
foreach ToolName in SystemTools.Keys() do
InsertSystemTool(MCPSystemTool, CopyStr(ToolName, 1, MaxStrLen(MCPSystemTool."Tool Name")), CopyStr(SystemTools.Get(ToolName), 1, MaxStrLen(MCPSystemTool."Tool Description")));
end;

local procedure InsertSystemTool(var MCPSystemTool: Record "MCP System Tool"; ToolName: Text[100]; ToolDescription: Text[250])
begin
MCPSystemTool."Tool Name" := ToolName;
MCPSystemTool."Tool Description" := ToolDescription;
MCPSystemTool.Insert();
end;
#endregion

#region Connection String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace System.MCP;

using System.Reflection;

codeunit 8353 "MCP Config Missing Object" implements "MCP Config Warning"
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

var
MCPConfigurationTool: Record "MCP Configuration Tool";
MissingObjectWarningLbl: Label '%1 (%2) referenced by this configuration no longer exists in the system.', Comment = '%1=Object type, %2=Object Id';
MissingObjectFixLbl: Label 'Remove this tool from the configuration.';

procedure CheckForWarnings(ConfigId: Guid; var MCPConfigWarning: Record "MCP Config Warning"; var EntryNo: Integer)
var
AllObj: Record AllObj;
begin
MCPConfigurationTool.SetRange(ID, ConfigId);
if MCPConfigurationTool.FindSet() then
repeat
AllObj.SetRange("Object Type", AllObj."Object Type"::Page);
AllObj.SetRange("Object ID", MCPConfigurationTool."Object ID");
if AllObj.IsEmpty() then begin
MCPConfigWarning."Entry No." := EntryNo;
MCPConfigWarning."Config Id" := ConfigId;
MCPConfigWarning."Tool Id" := MCPConfigurationTool.SystemId;
MCPConfigWarning."Warning Type" := MCPConfigWarning."Warning Type"::"Missing Object";
MCPConfigWarning.Insert();
EntryNo += 1;
end;
until MCPConfigurationTool.Next() = 0;
end;

procedure WarningMessage(MCPConfigWarning: Record "MCP Config Warning"): Text
begin
if MCPConfigurationTool.GetBySystemId(MCPConfigWarning."Tool Id") then
exit(StrSubstNo(MissingObjectWarningLbl, MCPConfigurationTool."Object Type", MCPConfigurationTool."Object Id"));
end;

procedure RecommendedAction(MCPConfigWarning: Record "MCP Config Warning"): Text
begin
exit(MissingObjectFixLbl);
end;

procedure ApplyRecommendedAction(var MCPConfigWarning: Record "MCP Config Warning")
begin
if MCPConfigurationTool.GetBySystemId(MCPConfigWarning."Tool Id") then
MCPConfigurationTool.Delete();
MCPConfigWarning.Delete();
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace System.MCP;

using System.Reflection;

codeunit 8354 "MCP Config Missing Parent" implements "MCP Config Warning"
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

var
MissingParentWarningLbl: Label 'This API page is missing parent page(s): %1', Comment = '%1 = comma-separated list of missing parent page IDs';
MissingParentFixLbl: Label 'Add the parent API pages to the configuration.';

procedure CheckForWarnings(ConfigId: Guid; var MCPConfigWarning: Record "MCP Config Warning"; var EntryNo: Integer)
var
MCPConfigurationTool: Record "MCP Configuration Tool";
PageMetadata: Record "Page Metadata";
MCPUtilities: Codeunit "MCP Utilities";
PageIdVersions: Dictionary of [Integer, Text];
ParentMCPTools: Dictionary of [Integer, List of [Integer]];
ParentPageIds: List of [Integer];
MissingParentIds: List of [Integer];
PageId: Integer;
ParentPageId: Integer;
MissingParentsText: Text;
begin
// Build dictionary of page IDs and API versions from configuration tools
MCPConfigurationTool.SetRange(ID, ConfigId);
MCPConfigurationTool.SetRange("Object Type", MCPConfigurationTool."Object Type"::Page);
if not MCPConfigurationTool.FindSet() then
exit;

repeat
if PageMetadata.Get(MCPConfigurationTool."Object ID") then
if PageMetadata.PageType = PageMetadata.PageType::API then
PageIdVersions.Add(MCPConfigurationTool."Object ID", PageMetadata.APIVersion);
until MCPConfigurationTool.Next() = 0;

// Get parent mappings from platform
ParentMCPTools := MCPUtilities.GetParentMCPTools(PageIdVersions);

// Check each page with parents for missing parent tools
foreach PageId in ParentMCPTools.Keys() do begin
ParentPageIds := ParentMCPTools.Get(PageId);
Clear(MissingParentIds);

// Check if each parent exists in the configuration
foreach ParentPageId in ParentPageIds do
if not PageIdVersions.ContainsKey(ParentPageId) then
MissingParentIds.Add(ParentPageId);

// Create warning if there are missing parents
if MissingParentIds.Count() > 0 then begin
// Get the tool record to retrieve its SystemId
MCPConfigurationTool.Get(ConfigId, MCPConfigurationTool."Object Type"::Page, PageId);

MissingParentsText := FormatPageIdList(MissingParentIds);
MCPConfigWarning."Entry No." := EntryNo;
MCPConfigWarning."Config Id" := ConfigId;
MCPConfigWarning."Tool Id" := MCPConfigurationTool.SystemId;
MCPConfigWarning."Warning Type" := MCPConfigWarning."Warning Type"::"Missing Parent Object";
MCPConfigWarning."Additional Info" := CopyStr(MissingParentsText, 1, MaxStrLen(MCPConfigWarning."Additional Info"));
MCPConfigWarning.Insert();
EntryNo += 1;
end;
end;
end;

procedure WarningMessage(MCPConfigWarning: Record "MCP Config Warning"): Text
begin
exit(StrSubstNo(MissingParentWarningLbl, MCPConfigWarning."Additional Info"));
end;

procedure RecommendedAction(MCPConfigWarning: Record "MCP Config Warning"): Text
begin
exit(MissingParentFixLbl);
end;

procedure ApplyRecommendedAction(var MCPConfigWarning: Record "MCP Config Warning")
var
MCPConfigImplementation: Codeunit "MCP Config Implementation";
PageIdList: List of [Text];
PageIdText: Text;
PageId: Integer;
begin
if MCPConfigWarning."Additional Info" = '' then
exit;

// Parse comma-separated page IDs and add each as a tool
PageIdList := MCPConfigWarning."Additional Info".Split(',');
foreach PageIdText in PageIdList do
if Evaluate(PageId, PageIdText.Trim()) then
if not MCPConfigImplementation.CheckAPIToolExists(MCPConfigWarning."Config Id", PageId) then
MCPConfigImplementation.CreateAPITool(MCPConfigWarning."Config Id", PageId, false);

MCPConfigWarning.Delete();
end;

local procedure FormatPageIdList(PageIds: List of [Integer]): Text
var
PageId: Integer;
Result: TextBuilder;
begin
foreach PageId in PageIds do begin
Result.Append(Format(PageId));
Result.Append(', ');
end;
exit(Result.ToText().TrimEnd(', '));
end;
}
Loading
Loading