From a71a1b036433dfd733fdde43adb9fcd60ea5a098 Mon Sep 17 00:00:00 2001
From: Onat Buyukakkus <55088871+onbuyuka@users.noreply.github.com>
Date: Thu, 29 Jan 2026 21:02:10 +0100
Subject: [PATCH 1/4] Add Export/Import functionality for MCP Configurations
---
.../Codeunits/MCPConfig.Codeunit.al | 22 ++
.../MCPConfigImplementation.Codeunit.al | 214 ++++++++++++++++--
.../Configuration/Pages/MCPConfigCard.Page.al | 2 +-
.../Configuration/Pages/MCPConfigList.Page.al | 35 ++-
.../Configuration/Pages/MCPCopyConfig.Page.al | 25 ++
.../Test/MCP/src/MCPConfigTest.Codeunit.al | 76 +++++++
6 files changed, 359 insertions(+), 15 deletions(-)
diff --git a/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfig.Codeunit.al b/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfig.Codeunit.al
index d114f9161c..d6ae1dc9eb 100644
--- a/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfig.Codeunit.al
+++ b/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfig.Codeunit.al
@@ -230,4 +230,26 @@ codeunit 8350 "MCP Config"
begin
MCPConfigImplementation.DeleteEntraApplication(Name);
end;
+
+ ///
+ /// Exports the specified MCP configuration and its tools to a JSON stream.
+ ///
+ /// The SystemId (GUID) of the configuration to export.
+ /// The output stream to write the JSON to.
+ procedure ExportConfiguration(ConfigId: Guid; var OutStream: OutStream)
+ begin
+ MCPConfigImplementation.ExportConfiguration(ConfigId, OutStream);
+ end;
+
+ ///
+ /// Imports an MCP configuration and its tools from a JSON stream.
+ ///
+ /// The input stream containing the JSON configuration.
+ /// The name for the imported configuration.
+ /// The description for the imported configuration.
+ /// The SystemId (GUID) of the imported configuration.
+ procedure ImportConfiguration(var InStream: InStream; NewName: Text[100]; NewDescription: Text[250]): Guid
+ begin
+ exit(MCPConfigImplementation.ImportConfiguration(InStream, NewName, NewDescription));
+ end;
}
\ No newline at end of file
diff --git a/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al b/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al
index 99380e840e..78b1054bd5 100644
--- a/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al
+++ b/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al
@@ -47,6 +47,12 @@ codeunit 8351 "MCP Config Implementation"
VSCodeAppNameLbl: Label 'VS Code', Locked = true;
VSCodeAppDescriptionLbl: Label 'Visual Studio Code';
VSCodeClientIdLbl: Label 'aebc6443-996d-45c2-90f0-388ff96faa56', Locked = true;
+ ExportFileNameTxt: Label 'MCPConfig_%1_%2.json', Locked = true, Comment = '%1 = config name, %2 = date';
+ ExportTitleTxt: Label 'Export Configuration';
+ ImportTitleTxt: Label 'Import Configuration';
+ JsonFilterTxt: Label 'JSON Files (*.json)|*.json';
+ InvalidJsonErr: Label 'The selected file is not a valid configuration file.';
+ ConfigNameExistsMsg: Label 'A configuration with the name ''%1'' already exists. Please provide a different name.', Comment = '%1 = configuration name';
#region Configurations
internal procedure GetConfigurationIdByName(Name: Text[100]): Guid
@@ -251,19 +257,6 @@ codeunit 8351 "MCP Config Implementation"
MCPConfiguration.Insert();
end;
- internal procedure CreateVSCodeEntraApplication()
- var
- MCPEntraApplication: Record "MCP Entra Application";
- begin
- if MCPEntraApplication.Get(VSCodeAppNameLbl) then
- exit;
-
- MCPEntraApplication.Name := VSCodeAppNameLbl;
- MCPEntraApplication.Description := VSCodeAppDescriptionLbl;
- Evaluate(MCPEntraApplication."Client ID", VSCodeClientIdLbl);
- MCPEntraApplication.Insert();
- end;
-
internal procedure IsDefaultConfiguration(MCPConfiguration: Record "MCP Configuration"): Boolean
begin
exit(MCPConfiguration.Name = '');
@@ -639,6 +632,19 @@ codeunit 8351 "MCP Config Implementation"
#endregion
#region Connection String
+ internal procedure CreateVSCodeEntraApplication()
+ var
+ MCPEntraApplication: Record "MCP Entra Application";
+ begin
+ if MCPEntraApplication.Get(VSCodeAppNameLbl) then
+ exit;
+
+ MCPEntraApplication.Name := VSCodeAppNameLbl;
+ MCPEntraApplication.Description := VSCodeAppDescriptionLbl;
+ Evaluate(MCPEntraApplication."Client ID", VSCodeClientIdLbl);
+ MCPEntraApplication.Insert();
+ end;
+
internal procedure ShowConnectionString(ConfigurationName: Text[100])
var
MCPConnectionString: Page "MCP Connection String";
@@ -724,6 +730,188 @@ codeunit 8351 "MCP Config Implementation"
end;
#endregion
+ #region Export/Import
+ internal procedure ExportConfigurationToFile(ConfigId: Guid; ConfigName: Text[100])
+ var
+ TempBlob: Codeunit "Temp Blob";
+ OutStream: OutStream;
+ InStream: InStream;
+ FileName: Text;
+ begin
+ TempBlob.CreateOutStream(OutStream, TextEncoding::UTF8);
+ ExportConfiguration(ConfigId, OutStream);
+ TempBlob.CreateInStream(InStream, TextEncoding::UTF8);
+ FileName := StrSubstNo(ExportFileNameTxt, ConfigName, Format(Today(), 0, '--'));
+ DownloadFromStream(InStream, ExportTitleTxt, '', JsonFilterTxt, FileName);
+ end;
+
+ internal procedure ImportConfigurationFromFile()
+ var
+ MCPConfiguration: Record "MCP Configuration";
+ TempBlob: Codeunit "Temp Blob";
+ MCPCopyConfig: Page "MCP Copy Config";
+ InStream: InStream;
+ OutStream: OutStream;
+ FileName: Text;
+ ConfigName: Text[100];
+ ConfigDescription: Text[250];
+ begin
+ if not UploadIntoStream(ImportTitleTxt, '', JsonFilterTxt, FileName, InStream) then
+ exit;
+
+ TempBlob.CreateOutStream(OutStream, TextEncoding::UTF8);
+ CopyStream(OutStream, InStream);
+ TempBlob.CreateInStream(InStream, TextEncoding::UTF8);
+
+ if not GetConfigFromJson(InStream, ConfigName, ConfigDescription) then
+ Error(InvalidJsonErr);
+
+ MCPConfiguration.SetRange(Name, ConfigName);
+ if not MCPConfiguration.IsEmpty() then begin
+ MCPCopyConfig.SetConfigName(ConfigName);
+ MCPCopyConfig.SetConfigDescription(ConfigDescription);
+ MCPCopyConfig.SetInstructionMessage(StrSubstNo(ConfigNameExistsMsg, ConfigName));
+ MCPCopyConfig.LookupMode := true;
+ if MCPCopyConfig.RunModal() <> Action::LookupOK then
+ exit;
+ ConfigName := MCPCopyConfig.GetConfigName();
+ ConfigDescription := MCPCopyConfig.GetConfigDescription();
+ end;
+
+ TempBlob.CreateInStream(InStream, TextEncoding::UTF8);
+ ImportConfiguration(InStream, ConfigName, ConfigDescription);
+ end;
+
+ internal procedure ExportConfiguration(ConfigId: Guid; var OutStream: OutStream)
+ var
+ MCPConfiguration: Record "MCP Configuration";
+ MCPConfigurationTool: Record "MCP Configuration Tool";
+ ConfigJson: JsonObject;
+ ToolsArray: JsonArray;
+ ToolJson: JsonObject;
+ OutputText: Text;
+ begin
+ if not MCPConfiguration.GetBySystemId(ConfigId) then
+ exit;
+
+ ConfigJson.Add('name', MCPConfiguration.Name);
+ ConfigJson.Add('description', MCPConfiguration.Description);
+ ConfigJson.Add('enableDynamicToolMode', MCPConfiguration.EnableDynamicToolMode);
+ ConfigJson.Add('discoverReadOnlyObjects', MCPConfiguration.DiscoverReadOnlyObjects);
+ ConfigJson.Add('allowProdChanges', MCPConfiguration.AllowProdChanges);
+
+ MCPConfigurationTool.SetRange(ID, ConfigId);
+ if MCPConfigurationTool.FindSet() then
+ repeat
+ Clear(ToolJson);
+ ToolJson.Add('objectType', Format(MCPConfigurationTool."Object Type"));
+ ToolJson.Add('objectId', MCPConfigurationTool."Object ID");
+ ToolJson.Add('allowRead', MCPConfigurationTool."Allow Read");
+ ToolJson.Add('allowCreate', MCPConfigurationTool."Allow Create");
+ ToolJson.Add('allowModify', MCPConfigurationTool."Allow Modify");
+ ToolJson.Add('allowDelete', MCPConfigurationTool."Allow Delete");
+ ToolJson.Add('allowBoundActions', MCPConfigurationTool."Allow Bound Actions");
+ ToolsArray.Add(ToolJson);
+ until MCPConfigurationTool.Next() = 0;
+
+ ConfigJson.Add('tools', ToolsArray);
+ ConfigJson.WriteTo(OutputText);
+ OutStream.WriteText(OutputText);
+ end;
+
+ local procedure GetConfigFromJson(var InStream: InStream; var ConfigName: Text[100]; var ConfigDescription: Text[250]): Boolean
+ var
+ ConfigJson: JsonObject;
+ JsonToken: JsonToken;
+ InputText: Text;
+ begin
+ InStream.ReadText(InputText);
+ if not ConfigJson.ReadFrom(InputText) then
+ exit(false);
+
+ if ConfigJson.Get('name', JsonToken) then
+ ConfigName := CopyStr(JsonToken.AsValue().AsText(), 1, MaxStrLen(ConfigName));
+
+ if ConfigJson.Get('description', JsonToken) then
+ ConfigDescription := CopyStr(JsonToken.AsValue().AsText(), 1, MaxStrLen(ConfigDescription));
+
+ exit(true);
+ end;
+
+ internal procedure ImportConfiguration(var InStream: InStream; NewName: Text[100]; NewDescription: Text[250]): Guid
+ var
+ MCPConfiguration: Record "MCP Configuration";
+ MCPConfigurationTool: Record "MCP Configuration Tool";
+ ConfigJson: JsonObject;
+ ToolsArray: JsonArray;
+ ToolToken: JsonToken;
+ ToolJson: JsonObject;
+ JsonToken: JsonToken;
+ InputText: Text;
+ ObjectTypeText: Text;
+ i: Integer;
+ begin
+ InStream.ReadText(InputText);
+ if not ConfigJson.ReadFrom(InputText) then
+ exit;
+
+ MCPConfiguration.Name := NewName;
+ MCPConfiguration.Description := NewDescription;
+ MCPConfiguration.Active := false;
+
+ if ConfigJson.Get('enableDynamicToolMode', JsonToken) then
+ MCPConfiguration.EnableDynamicToolMode := JsonToken.AsValue().AsBoolean();
+
+ if ConfigJson.Get('discoverReadOnlyObjects', JsonToken) then
+ MCPConfiguration.DiscoverReadOnlyObjects := JsonToken.AsValue().AsBoolean();
+
+ if ConfigJson.Get('allowProdChanges', JsonToken) then
+ MCPConfiguration.AllowProdChanges := JsonToken.AsValue().AsBoolean();
+
+ MCPConfiguration.Insert();
+ LogConfigurationCreated(MCPConfiguration);
+
+ if ConfigJson.Get('tools', JsonToken) then begin
+ ToolsArray := JsonToken.AsArray();
+ for i := 0 to ToolsArray.Count() - 1 do begin
+ ToolsArray.Get(i, ToolToken);
+ ToolJson := ToolToken.AsObject();
+
+ MCPConfigurationTool.Init();
+ MCPConfigurationTool.ID := MCPConfiguration.SystemId;
+
+ if ToolJson.Get('objectType', JsonToken) then begin
+ ObjectTypeText := JsonToken.AsValue().AsText();
+ if ObjectTypeText = 'Page' then
+ MCPConfigurationTool."Object Type" := MCPConfigurationTool."Object Type"::Page;
+ end;
+
+ if ToolJson.Get('objectId', JsonToken) then
+ MCPConfigurationTool."Object ID" := JsonToken.AsValue().AsInteger();
+
+ if ToolJson.Get('allowRead', JsonToken) then
+ MCPConfigurationTool."Allow Read" := JsonToken.AsValue().AsBoolean();
+
+ if ToolJson.Get('allowCreate', JsonToken) then
+ MCPConfigurationTool."Allow Create" := JsonToken.AsValue().AsBoolean();
+
+ if ToolJson.Get('allowModify', JsonToken) then
+ MCPConfigurationTool."Allow Modify" := JsonToken.AsValue().AsBoolean();
+
+ if ToolJson.Get('allowDelete', JsonToken) then
+ MCPConfigurationTool."Allow Delete" := JsonToken.AsValue().AsBoolean();
+
+ if ToolJson.Get('allowBoundActions', JsonToken) then
+ MCPConfigurationTool."Allow Bound Actions" := JsonToken.AsValue().AsBoolean();
+
+ MCPConfigurationTool.Insert();
+ end;
+ end;
+
+ exit(MCPConfiguration.SystemId);
+ end;
+ #endregion
+
#if not CLEAN28
internal procedure IsFeatureEnabled(): Boolean
var
diff --git a/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigCard.Page.al b/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigCard.Page.al
index 1ee87dbac9..50c373a0c9 100644
--- a/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigCard.Page.al
+++ b/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigCard.Page.al
@@ -142,7 +142,7 @@ page 8351 "MCP Config Card"
{
Caption = 'Connection String';
ToolTip = 'Generate a connection string for this MCP configuration to use in your MCP client.';
- Image = Export;
+ Image = Link;
trigger OnAction()
begin
diff --git a/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigList.Page.al b/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigList.Page.al
index 5c172350c9..33f6ec5ece 100644
--- a/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigList.Page.al
+++ b/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigList.Page.al
@@ -51,6 +51,7 @@ page 8350 "MCP Config List"
ToolTip = 'Creates a copy of the current MCP configuration, including its tools and permissions.';
Image = Copy;
AccessByPermission = tabledata "MCP Configuration" = IM;
+ Scope = Repeater;
trigger OnAction()
var
@@ -71,7 +72,7 @@ page 8350 "MCP Config List"
{
Caption = 'Connection String';
ToolTip = 'Generate a connection string for this MCP configuration to use in your MCP client.';
- Image = Export;
+ Image = Link;
Scope = Repeater;
trigger OnAction()
@@ -88,6 +89,35 @@ page 8350 "MCP Config List"
Image = Setup;
RunObject = page "MCP Entra Application List";
}
+ action(ExportConfiguration)
+ {
+ Caption = 'Export';
+ ToolTip = 'Export the selected MCP configuration and its tools to a JSON file.';
+ Image = Export;
+ Scope = Repeater;
+
+ trigger OnAction()
+ var
+ MCPConfigImplementation: Codeunit "MCP Config Implementation";
+ begin
+ MCPConfigImplementation.ExportConfigurationToFile(Rec.SystemId, Rec.Name);
+ end;
+ }
+ action(ImportConfiguration)
+ {
+ Caption = 'Import';
+ ToolTip = 'Import an MCP configuration and its tools from a JSON file.';
+ Image = Import;
+ AccessByPermission = tabledata "MCP Configuration" = IM;
+
+ trigger OnAction()
+ var
+ MCPConfigImplementation: Codeunit "MCP Config Implementation";
+ begin
+ MCPConfigImplementation.ImportConfigurationFromFile();
+ CurrPage.Update(false);
+ end;
+ }
}
}
area(Promoted)
@@ -99,6 +129,8 @@ page 8350 "MCP Config List"
actionref(Promoted_GenerateConnectionString; GenerateConnectionString) { }
actionref(Promoted_MCPEntraApplications; MCPEntraApplications) { }
+ actionref(Promoted_ExportConfiguration; ExportConfiguration) { }
+ actionref(Promoted_ImportConfiguration; ImportConfiguration) { }
}
}
}
@@ -131,4 +163,5 @@ page 8350 "MCP Config List"
FeatureNotEnabledErr: Label 'MCP server feature is not enabled. Please contact your system administrator to enable the feature.';
GoToFeatureManagementLbl: Label 'Go to Feature Management';
#endif
+
}
\ No newline at end of file
diff --git a/src/System Application/App/MCP/src/Configuration/Pages/MCPCopyConfig.Page.al b/src/System Application/App/MCP/src/Configuration/Pages/MCPCopyConfig.Page.al
index 18e2110f29..66d5bfcc62 100644
--- a/src/System Application/App/MCP/src/Configuration/Pages/MCPCopyConfig.Page.al
+++ b/src/System Application/App/MCP/src/Configuration/Pages/MCPCopyConfig.Page.al
@@ -19,6 +19,15 @@ page 8355 "MCP Copy Config"
group(Control1)
{
ShowCaption = false;
+ field(InstructionMessage; InstructionMessage)
+ {
+ ApplicationArea = All;
+ Editable = false;
+ ShowCaption = false;
+ MultiLine = true;
+ Style = Attention;
+ Visible = InstructionMessage <> '';
+ }
field(ConfigName; ConfigName)
{
ApplicationArea = All;
@@ -38,6 +47,7 @@ page 8355 "MCP Copy Config"
var
ConfigName: Text[100];
ConfigDescription: Text[250];
+ InstructionMessage: Text;
internal procedure GetConfigName(): Text[100]
begin
@@ -48,4 +58,19 @@ page 8355 "MCP Copy Config"
begin
exit(ConfigDescription);
end;
+
+ internal procedure SetConfigName(NewConfigName: Text[100])
+ begin
+ ConfigName := NewConfigName;
+ end;
+
+ internal procedure SetConfigDescription(NewConfigDescription: Text[250])
+ begin
+ ConfigDescription := NewConfigDescription;
+ end;
+
+ internal procedure SetInstructionMessage(NewInstructionMessage: Text)
+ begin
+ InstructionMessage := NewInstructionMessage;
+ end;
}
\ No newline at end of file
diff --git a/src/System Application/Test/MCP/src/MCPConfigTest.Codeunit.al b/src/System Application/Test/MCP/src/MCPConfigTest.Codeunit.al
index c7b10b1191..9aa0ed74d5 100644
--- a/src/System Application/Test/MCP/src/MCPConfigTest.Codeunit.al
+++ b/src/System Application/Test/MCP/src/MCPConfigTest.Codeunit.al
@@ -9,6 +9,7 @@ using System.MCP;
using System.Reflection;
using System.TestLibraries.MCP;
using System.TestLibraries.Utilities;
+using System.Utilities;
codeunit 130130 "MCP Config Test"
{
@@ -760,6 +761,81 @@ codeunit 130130 "MCP Config Test"
Assert.RecordIsEmpty(MCPConfigWarning);
end;
+ [Test]
+ procedure TestExportConfiguration()
+ var
+ MCPConfiguration: Record "MCP Configuration";
+ TempBlob: Codeunit "Temp Blob";
+ OutStream: OutStream;
+ InStream: InStream;
+ ConfigId: Guid;
+ JsonText: Text;
+ ConfigJson: JsonObject;
+ JsonToken: JsonToken;
+ begin
+ // [GIVEN] Configuration with tools is created
+ ConfigId := CreateMCPConfig(false, true, true, true);
+ CreateMCPConfigTool(ConfigId);
+ MCPConfiguration.GetBySystemId(ConfigId);
+
+ // [WHEN] Export configuration is called
+ TempBlob.CreateOutStream(OutStream, TextEncoding::UTF8);
+ MCPConfig.ExportConfiguration(ConfigId, OutStream);
+
+ // [THEN] JSON contains configuration data
+ TempBlob.CreateInStream(InStream, TextEncoding::UTF8);
+ InStream.ReadText(JsonText);
+ Assert.IsTrue(ConfigJson.ReadFrom(JsonText), 'Invalid JSON exported');
+
+ ConfigJson.Get('name', JsonToken);
+ Assert.AreEqual(MCPConfiguration.Name, JsonToken.AsValue().AsText(), 'Name mismatch');
+
+ ConfigJson.Get('enableDynamicToolMode', JsonToken);
+ Assert.AreEqual(true, JsonToken.AsValue().AsBoolean(), 'EnableDynamicToolMode mismatch');
+
+ ConfigJson.Get('tools', JsonToken);
+ Assert.AreEqual(1, JsonToken.AsArray().Count(), 'Tools count mismatch');
+ end;
+
+ [Test]
+ procedure TestImportConfiguration()
+ var
+ MCPConfiguration: Record "MCP Configuration";
+ MCPConfigurationTool: Record "MCP Configuration Tool";
+ TempBlob: Codeunit "Temp Blob";
+ OutStream: OutStream;
+ InStream: InStream;
+ SourceConfigId: Guid;
+ ImportedConfigId: Guid;
+ NewName: Text[100];
+ NewDescription: Text[250];
+ begin
+ // [GIVEN] Configuration with tools is created and exported
+ SourceConfigId := CreateMCPConfig(false, true, true, true);
+ CreateMCPConfigTool(SourceConfigId);
+
+ TempBlob.CreateOutStream(OutStream, TextEncoding::UTF8);
+ MCPConfig.ExportConfiguration(SourceConfigId, OutStream);
+
+ // [WHEN] Import configuration is called with new name
+ NewName := CopyStr(Format(CreateGuid()), 1, 100);
+ NewDescription := 'Imported configuration';
+ TempBlob.CreateInStream(InStream, TextEncoding::UTF8);
+ ImportedConfigId := MCPConfig.ImportConfiguration(InStream, NewName, NewDescription);
+
+ // [THEN] New configuration is created with imported settings
+ MCPConfiguration.GetBySystemId(ImportedConfigId);
+ Assert.AreEqual(NewName, MCPConfiguration.Name, 'Name mismatch');
+ Assert.AreEqual(NewDescription, MCPConfiguration.Description, 'Description mismatch');
+ Assert.IsFalse(MCPConfiguration.Active, 'Imported config should be inactive');
+ Assert.IsTrue(MCPConfiguration.EnableDynamicToolMode, 'EnableDynamicToolMode mismatch');
+ Assert.IsTrue(MCPConfiguration.DiscoverReadOnlyObjects, 'DiscoverReadOnlyObjects mismatch');
+
+ // [THEN] Tools are imported
+ MCPConfigurationTool.SetRange(ID, ImportedConfigId);
+ Assert.RecordCount(MCPConfigurationTool, 1);
+ end;
+
local procedure CreateMCPConfig(Active: Boolean; DynamicToolMode: Boolean; AllowCreateUpdateDeleteTools: Boolean; DiscoverReadOnlyObjects: Boolean): Guid
var
MCPConfiguration: Record "MCP Configuration";
From 1e53331e91fdb553a2997d0424c8cc1a31e148eb Mon Sep 17 00:00:00 2001
From: Onat Buyukakkus <55088871+onbuyuka@users.noreply.github.com>
Date: Thu, 29 Jan 2026 21:36:37 +0100
Subject: [PATCH 2/4] Update
---
.../Codeunits/MCPConfigImplementation.Codeunit.al | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al b/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al
index 78b1054bd5..68f46936ce 100644
--- a/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al
+++ b/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al
@@ -829,8 +829,10 @@ codeunit 8351 "MCP Config Implementation"
if not ConfigJson.ReadFrom(InputText) then
exit(false);
- if ConfigJson.Get('name', JsonToken) then
- ConfigName := CopyStr(JsonToken.AsValue().AsText(), 1, MaxStrLen(ConfigName));
+ if not ConfigJson.Get('name', JsonToken) then
+ exit(false);
+
+ ConfigName := CopyStr(JsonToken.AsValue().AsText(), 1, MaxStrLen(ConfigName));
if ConfigJson.Get('description', JsonToken) then
ConfigDescription := CopyStr(JsonToken.AsValue().AsText(), 1, MaxStrLen(ConfigDescription));
From bb7008629759f4dcecb129ebe4259adfc1bd0515 Mon Sep 17 00:00:00 2001
From: Onat Buyukakkus <55088871+onbuyuka@users.noreply.github.com>
Date: Thu, 29 Jan 2026 23:03:48 +0100
Subject: [PATCH 3/4] Update
---
.../MCPConfigImplementation.Codeunit.al | 77 +++++++++----------
.../Test/MCP/src/MCPConfigTest.Codeunit.al | 10 ++-
2 files changed, 44 insertions(+), 43 deletions(-)
diff --git a/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al b/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al
index 68f46936ce..ba878f94f8 100644
--- a/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al
+++ b/src/System Application/App/MCP/src/Configuration/Codeunits/MCPConfigImplementation.Codeunit.al
@@ -843,15 +843,10 @@ codeunit 8351 "MCP Config Implementation"
internal procedure ImportConfiguration(var InStream: InStream; NewName: Text[100]; NewDescription: Text[250]): Guid
var
MCPConfiguration: Record "MCP Configuration";
- MCPConfigurationTool: Record "MCP Configuration Tool";
ConfigJson: JsonObject;
ToolsArray: JsonArray;
ToolToken: JsonToken;
- ToolJson: JsonObject;
- JsonToken: JsonToken;
InputText: Text;
- ObjectTypeText: Text;
- i: Integer;
begin
InStream.ReadText(InputText);
if not ConfigJson.ReadFrom(InputText) then
@@ -861,56 +856,60 @@ codeunit 8351 "MCP Config Implementation"
MCPConfiguration.Description := NewDescription;
MCPConfiguration.Active := false;
- if ConfigJson.Get('enableDynamicToolMode', JsonToken) then
- MCPConfiguration.EnableDynamicToolMode := JsonToken.AsValue().AsBoolean();
+ if ConfigJson.Contains('enableDynamicToolMode') then
+ MCPConfiguration.EnableDynamicToolMode := ConfigJson.GetBoolean('enableDynamicToolMode');
- if ConfigJson.Get('discoverReadOnlyObjects', JsonToken) then
- MCPConfiguration.DiscoverReadOnlyObjects := JsonToken.AsValue().AsBoolean();
+ if ConfigJson.Contains('discoverReadOnlyObjects') then
+ MCPConfiguration.DiscoverReadOnlyObjects := ConfigJson.GetBoolean('discoverReadOnlyObjects');
- if ConfigJson.Get('allowProdChanges', JsonToken) then
- MCPConfiguration.AllowProdChanges := JsonToken.AsValue().AsBoolean();
+ if ConfigJson.Contains('allowProdChanges') then
+ MCPConfiguration.AllowProdChanges := ConfigJson.GetBoolean('allowProdChanges');
MCPConfiguration.Insert();
LogConfigurationCreated(MCPConfiguration);
- if ConfigJson.Get('tools', JsonToken) then begin
- ToolsArray := JsonToken.AsArray();
- for i := 0 to ToolsArray.Count() - 1 do begin
- ToolsArray.Get(i, ToolToken);
- ToolJson := ToolToken.AsObject();
+ if ConfigJson.Contains('tools') then begin
+ ToolsArray := ConfigJson.GetArray('tools');
+ foreach ToolToken in ToolsArray do
+ ImportTool(MCPConfiguration.SystemId, ToolToken.AsObject());
+ end;
- MCPConfigurationTool.Init();
- MCPConfigurationTool.ID := MCPConfiguration.SystemId;
+ exit(MCPConfiguration.SystemId);
+ end;
- if ToolJson.Get('objectType', JsonToken) then begin
- ObjectTypeText := JsonToken.AsValue().AsText();
- if ObjectTypeText = 'Page' then
- MCPConfigurationTool."Object Type" := MCPConfigurationTool."Object Type"::Page;
- end;
+ local procedure ImportTool(ConfigId: Guid; ToolJson: JsonObject)
+ var
+ MCPConfigurationTool: Record "MCP Configuration Tool";
+ ObjectTypeText: Text;
+ begin
+ MCPConfigurationTool.Init();
+ MCPConfigurationTool.ID := ConfigId;
- if ToolJson.Get('objectId', JsonToken) then
- MCPConfigurationTool."Object ID" := JsonToken.AsValue().AsInteger();
+ if ToolJson.Contains('objectType') then begin
+ ObjectTypeText := ToolJson.GetText('objectType');
+ if ObjectTypeText = 'Page' then
+ MCPConfigurationTool."Object Type" := MCPConfigurationTool."Object Type"::Page;
+ end;
- if ToolJson.Get('allowRead', JsonToken) then
- MCPConfigurationTool."Allow Read" := JsonToken.AsValue().AsBoolean();
+ if ToolJson.Contains('objectId') then
+ MCPConfigurationTool."Object ID" := ToolJson.GetInteger('objectId');
- if ToolJson.Get('allowCreate', JsonToken) then
- MCPConfigurationTool."Allow Create" := JsonToken.AsValue().AsBoolean();
+ if ToolJson.Contains('allowRead') then
+ MCPConfigurationTool."Allow Read" := ToolJson.GetBoolean('allowRead');
- if ToolJson.Get('allowModify', JsonToken) then
- MCPConfigurationTool."Allow Modify" := JsonToken.AsValue().AsBoolean();
+ if ToolJson.Contains('allowCreate') then
+ MCPConfigurationTool."Allow Create" := ToolJson.GetBoolean('allowCreate');
- if ToolJson.Get('allowDelete', JsonToken) then
- MCPConfigurationTool."Allow Delete" := JsonToken.AsValue().AsBoolean();
+ if ToolJson.Contains('allowModify') then
+ MCPConfigurationTool."Allow Modify" := ToolJson.GetBoolean('allowModify');
- if ToolJson.Get('allowBoundActions', JsonToken) then
- MCPConfigurationTool."Allow Bound Actions" := JsonToken.AsValue().AsBoolean();
+ if ToolJson.Contains('allowDelete') then
+ MCPConfigurationTool."Allow Delete" := ToolJson.GetBoolean('allowDelete');
- MCPConfigurationTool.Insert();
- end;
- end;
+ if ToolJson.Contains('allowBoundActions') then
+ MCPConfigurationTool."Allow Bound Actions" := ToolJson.GetBoolean('allowBoundActions');
- exit(MCPConfiguration.SystemId);
+ MCPConfigurationTool.Insert();
end;
#endregion
diff --git a/src/System Application/Test/MCP/src/MCPConfigTest.Codeunit.al b/src/System Application/Test/MCP/src/MCPConfigTest.Codeunit.al
index 9aa0ed74d5..68b8f33a0a 100644
--- a/src/System Application/Test/MCP/src/MCPConfigTest.Codeunit.al
+++ b/src/System Application/Test/MCP/src/MCPConfigTest.Codeunit.al
@@ -773,9 +773,10 @@ codeunit 130130 "MCP Config Test"
ConfigJson: JsonObject;
JsonToken: JsonToken;
begin
- // [GIVEN] Configuration with tools is created
+ // [GIVEN] Configuration with two tools is created
ConfigId := CreateMCPConfig(false, true, true, true);
CreateMCPConfigTool(ConfigId);
+ CreateMCPConfigTool(ConfigId);
MCPConfiguration.GetBySystemId(ConfigId);
// [WHEN] Export configuration is called
@@ -794,7 +795,7 @@ codeunit 130130 "MCP Config Test"
Assert.AreEqual(true, JsonToken.AsValue().AsBoolean(), 'EnableDynamicToolMode mismatch');
ConfigJson.Get('tools', JsonToken);
- Assert.AreEqual(1, JsonToken.AsArray().Count(), 'Tools count mismatch');
+ Assert.AreEqual(2, JsonToken.AsArray().Count(), 'Tools count mismatch');
end;
[Test]
@@ -810,9 +811,10 @@ codeunit 130130 "MCP Config Test"
NewName: Text[100];
NewDescription: Text[250];
begin
- // [GIVEN] Configuration with tools is created and exported
+ // [GIVEN] Configuration with two tools is created and exported
SourceConfigId := CreateMCPConfig(false, true, true, true);
CreateMCPConfigTool(SourceConfigId);
+ CreateMCPConfigTool(SourceConfigId);
TempBlob.CreateOutStream(OutStream, TextEncoding::UTF8);
MCPConfig.ExportConfiguration(SourceConfigId, OutStream);
@@ -833,7 +835,7 @@ codeunit 130130 "MCP Config Test"
// [THEN] Tools are imported
MCPConfigurationTool.SetRange(ID, ImportedConfigId);
- Assert.RecordCount(MCPConfigurationTool, 1);
+ Assert.RecordCount(MCPConfigurationTool, 2);
end;
local procedure CreateMCPConfig(Active: Boolean; DynamicToolMode: Boolean; AllowCreateUpdateDeleteTools: Boolean; DiscoverReadOnlyObjects: Boolean): Guid
From 0a0f341d3e3bae3ad79e5b19cff5da88ce805e53 Mon Sep 17 00:00:00 2001
From: Onat Buyukakkus <55088871+onbuyuka@users.noreply.github.com>
Date: Fri, 30 Jan 2026 00:02:54 +0100
Subject: [PATCH 4/4] Update
---
.../MCP/src/Configuration/Pages/MCPConfigToolList.Page.al | 4 ++++
src/System Application/Test/MCP/app.json | 6 ++++++
2 files changed, 10 insertions(+)
diff --git a/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigToolList.Page.al b/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigToolList.Page.al
index eb8958bd5f..03f61c3736 100644
--- a/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigToolList.Page.al
+++ b/src/System Application/App/MCP/src/Configuration/Pages/MCPConfigToolList.Page.al
@@ -41,6 +41,8 @@ page 8352 "MCP Config Tool List"
exit;
repeat
+ if MCPConfigImplementation.CheckAPIToolExists(Rec.ID, PageMetadata.ID) then
+ continue;
MCPConfig.CreateAPITool(Rec.ID, PageMetadata.ID);
until PageMetadata.Next() = 0;
@@ -104,6 +106,8 @@ page 8352 "MCP Config Tool List"
exit;
repeat
+ if MCPConfigImplementation.CheckAPIToolExists(Rec.ID, PageMetadata.ID) then
+ continue;
MCPConfig.CreateAPITool(Rec.ID, PageMetadata.ID);
until PageMetadata.Next() = 0;
diff --git a/src/System Application/Test/MCP/app.json b/src/System Application/Test/MCP/app.json
index 36e090514a..66ff28ebb5 100644
--- a/src/System Application/Test/MCP/app.json
+++ b/src/System Application/Test/MCP/app.json
@@ -40,6 +40,12 @@
"name": "Environment Information Test Library",
"publisher": "Microsoft",
"version": "28.0.0.0"
+ },
+ {
+ "id": "e31ad830-3d46-472e-afeb-1d3d35247943",
+ "name": "BLOB Storage",
+ "publisher": "Microsoft",
+ "version": "28.0.0.0"
}
],
"screenshots": [],