Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -230,4 +230,26 @@ codeunit 8350 "MCP Config"
begin
MCPConfigImplementation.DeleteEntraApplication(Name);
end;

/// <summary>
/// Exports the specified MCP configuration and its tools to a JSON stream.
/// </summary>
/// <param name="ConfigId">The SystemId (GUID) of the configuration to export.</param>
/// <param name="OutStream">The output stream to write the JSON to.</param>
procedure ExportConfiguration(ConfigId: Guid; var OutStream: OutStream)
begin
MCPConfigImplementation.ExportConfiguration(ConfigId, OutStream);
end;

/// <summary>
/// Imports an MCP configuration and its tools from a JSON stream.
/// </summary>
/// <param name="InStream">The input stream containing the JSON configuration.</param>
/// <param name="NewName">The name for the imported configuration.</param>
/// <param name="NewDescription">The description for the imported configuration.</param>
/// <returns>The SystemId (GUID) of the imported configuration.</returns>
procedure ImportConfiguration(var InStream: InStream; NewName: Text[100]; NewDescription: Text[250]): Guid
begin
exit(MCPConfigImplementation.ImportConfiguration(InStream, NewName, NewDescription));
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = '');
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -724,6 +730,189 @@ 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, '<Year4>-<Month,2>-<Day,2>'));
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 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));

exit(true);
end;

internal procedure ImportConfiguration(var InStream: InStream; NewName: Text[100]; NewDescription: Text[250]): Guid
var
MCPConfiguration: Record "MCP Configuration";
ConfigJson: JsonObject;
ToolsArray: JsonArray;
ToolToken: JsonToken;
InputText: Text;
begin
InStream.ReadText(InputText);
if not ConfigJson.ReadFrom(InputText) then
exit;

MCPConfiguration.Name := NewName;
MCPConfiguration.Description := NewDescription;
MCPConfiguration.Active := false;

if ConfigJson.Contains('enableDynamicToolMode') then
MCPConfiguration.EnableDynamicToolMode := ConfigJson.GetBoolean('enableDynamicToolMode');

if ConfigJson.Contains('discoverReadOnlyObjects') then
MCPConfiguration.DiscoverReadOnlyObjects := ConfigJson.GetBoolean('discoverReadOnlyObjects');

if ConfigJson.Contains('allowProdChanges') then
MCPConfiguration.AllowProdChanges := ConfigJson.GetBoolean('allowProdChanges');

MCPConfiguration.Insert();
LogConfigurationCreated(MCPConfiguration);

if ConfigJson.Contains('tools') then begin
ToolsArray := ConfigJson.GetArray('tools');
foreach ToolToken in ToolsArray do
ImportTool(MCPConfiguration.SystemId, ToolToken.AsObject());
end;

exit(MCPConfiguration.SystemId);
end;

local procedure ImportTool(ConfigId: Guid; ToolJson: JsonObject)
var
MCPConfigurationTool: Record "MCP Configuration Tool";
ObjectTypeText: Text;
begin
MCPConfigurationTool.Init();
MCPConfigurationTool.ID := ConfigId;

if ToolJson.Contains('objectType') then begin
ObjectTypeText := ToolJson.GetText('objectType');
if ObjectTypeText = 'Page' then
MCPConfigurationTool."Object Type" := MCPConfigurationTool."Object Type"::Page;
end;

if ToolJson.Contains('objectId') then
MCPConfigurationTool."Object ID" := ToolJson.GetInteger('objectId');

if ToolJson.Contains('allowRead') then
MCPConfigurationTool."Allow Read" := ToolJson.GetBoolean('allowRead');

if ToolJson.Contains('allowCreate') then
MCPConfigurationTool."Allow Create" := ToolJson.GetBoolean('allowCreate');

if ToolJson.Contains('allowModify') then
MCPConfigurationTool."Allow Modify" := ToolJson.GetBoolean('allowModify');

if ToolJson.Contains('allowDelete') then
MCPConfigurationTool."Allow Delete" := ToolJson.GetBoolean('allowDelete');

if ToolJson.Contains('allowBoundActions') then
MCPConfigurationTool."Allow Bound Actions" := ToolJson.GetBoolean('allowBoundActions');

MCPConfigurationTool.Insert();
end;
#endregion

#if not CLEAN28
internal procedure IsFeatureEnabled(): Boolean
var
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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)
Expand All @@ -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) { }
}
}
}
Expand Down Expand Up @@ -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

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down
Loading
Loading