diff --git a/.gitignore b/.gitignore
index 5c211d8d82..a11d810d7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ node_modules/
generated/
/docs/commandline
.DS_Store
+.vscode/mcp.json
diff --git a/AzureMcp.sln b/AzureMcp.sln
index 62c4a14fe1..3a33bfc631 100644
--- a/AzureMcp.sln
+++ b/AzureMcp.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
-VisualStudioVersion = 18.0.11205.157 d18.0
+VisualStudioVersion = 18.0.11205.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{FBF56CC3-7AE6-AD2D-3F14-7F97FD322CD6}"
EndProject
@@ -267,6 +267,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0131AD4F-393
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.Storage", "tools\Azure.Mcp.Tools.Storage\src\Azure.Mcp.Tools.Storage.csproj", "{DE1B4312-1A4F-4774-B7EB-B1EC77F80D5E}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Azure.Mcp.Tools.StorageSync", "Azure.Mcp.Tools.StorageSync", "{85F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{95F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.StorageSync", "tools\Azure.Mcp.Tools.StorageSync\src\Azure.Mcp.Tools.StorageSync.csproj", "{A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Azure.Mcp.Tools.VirtualDesktop", "Azure.Mcp.Tools.VirtualDesktop", "{B28A9B67-1C09-C756-C02A-7AC1895F9584}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E38B6DEF-57A1-6CCA-498B-5697FF0B466C}"
@@ -539,6 +545,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.Storage.Liv
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.Storage.UnitTests", "tools\Azure.Mcp.Tools.Storage\tests\Azure.Mcp.Tools.Storage.UnitTests\Azure.Mcp.Tools.Storage.UnitTests.csproj", "{F3F49C7E-9106-4FF7-A71D-442022D63F7B}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{B5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.StorageSync.UnitTests", "tools\Azure.Mcp.Tools.StorageSync\tests\Azure.Mcp.Tools.StorageSync.UnitTests\Azure.Mcp.Tools.StorageSync.UnitTests.csproj", "{C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D38B6103-E564-8894-9748-4CF0C62984DB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.VirtualDesktop.LiveTests", "tools\Azure.Mcp.Tools.VirtualDesktop\tests\Azure.Mcp.Tools.VirtualDesktop.LiveTests\Azure.Mcp.Tools.VirtualDesktop.LiveTests.csproj", "{0A09784C-BB49-44E8-B07A-DA4EEEC1184E}"
@@ -563,6 +573,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{319B94CD
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Server.UnitTests", "servers\Azure.Mcp.Server\tests\Azure.Mcp.Server.UnitTests\Azure.Mcp.Server.UnitTests.csproj", "{ADF14627-FCB5-4BD3-B65F-DDCC3A3F727C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.StorageSync.LiveTests", "tools\Azure.Mcp.Tools.StorageSync\tests\Azure.Mcp.Tools.StorageSync.LiveTests\Azure.Mcp.Tools.StorageSync.LiveTests.csproj", "{38FE6BAB-DAEF-2CF7-2752-379F9094C190}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1101,6 +1113,18 @@ Global
{DE1B4312-1A4F-4774-B7EB-B1EC77F80D5E}.Release|x64.Build.0 = Release|Any CPU
{DE1B4312-1A4F-4774-B7EB-B1EC77F80D5E}.Release|x86.ActiveCfg = Release|Any CPU
{DE1B4312-1A4F-4774-B7EB-B1EC77F80D5E}.Release|x86.Build.0 = Release|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|x64.Build.0 = Debug|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|x86.Build.0 = Debug|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|x64.ActiveCfg = Release|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|x64.Build.0 = Release|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|x86.ActiveCfg = Release|Any CPU
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|x86.Build.0 = Release|Any CPU
{3156A400-78C7-410A-9B79-9CDFFD5B94E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3156A400-78C7-410A-9B79-9CDFFD5B94E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3156A400-78C7-410A-9B79-9CDFFD5B94E3}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -2037,6 +2061,18 @@ Global
{F3F49C7E-9106-4FF7-A71D-442022D63F7B}.Release|x64.Build.0 = Release|Any CPU
{F3F49C7E-9106-4FF7-A71D-442022D63F7B}.Release|x86.ActiveCfg = Release|Any CPU
{F3F49C7E-9106-4FF7-A71D-442022D63F7B}.Release|x86.Build.0 = Release|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|x64.Build.0 = Debug|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Debug|x86.Build.0 = Debug|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|x64.ActiveCfg = Release|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|x64.Build.0 = Release|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|x86.ActiveCfg = Release|Any CPU
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}.Release|x86.Build.0 = Release|Any CPU
{0A09784C-BB49-44E8-B07A-DA4EEEC1184E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A09784C-BB49-44E8-B07A-DA4EEEC1184E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A09784C-BB49-44E8-B07A-DA4EEEC1184E}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -2121,6 +2157,18 @@ Global
{ADF14627-FCB5-4BD3-B65F-DDCC3A3F727C}.Release|x64.Build.0 = Release|Any CPU
{ADF14627-FCB5-4BD3-B65F-DDCC3A3F727C}.Release|x86.ActiveCfg = Release|Any CPU
{ADF14627-FCB5-4BD3-B65F-DDCC3A3F727C}.Release|x86.Build.0 = Release|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Debug|x64.Build.0 = Debug|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Debug|x86.Build.0 = Debug|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Release|Any CPU.Build.0 = Release|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Release|x64.ActiveCfg = Release|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Release|x64.Build.0 = Release|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Release|x86.ActiveCfg = Release|Any CPU
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2255,6 +2303,9 @@ Global
{ED9D3D4A-502F-41A4-BBCC-970E65472F33} = {07C2787E-EAC7-C090-1BA3-A61EC2A24D84}
{0131AD4F-3934-F56E-5081-42129AD09143} = {ED9D3D4A-502F-41A4-BBCC-970E65472F33}
{DE1B4312-1A4F-4774-B7EB-B1EC77F80D5E} = {0131AD4F-3934-F56E-5081-42129AD09143}
+ {85F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B} = {07C2787E-EAC7-C090-1BA3-A61EC2A24D84}
+ {95F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B} = {85F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}
+ {A5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B} = {95F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}
{B28A9B67-1C09-C756-C02A-7AC1895F9584} = {07C2787E-EAC7-C090-1BA3-A61EC2A24D84}
{E38B6DEF-57A1-6CCA-498B-5697FF0B466C} = {B28A9B67-1C09-C756-C02A-7AC1895F9584}
{3156A400-78C7-410A-9B79-9CDFFD5B94E3} = {E38B6DEF-57A1-6CCA-498B-5697FF0B466C}
@@ -2392,6 +2443,8 @@ Global
{E03D2171-C4AB-45A3-681D-A2A2EBBB122A} = {ED9D3D4A-502F-41A4-BBCC-970E65472F33}
{9A72A0E3-091A-4C64-AE66-ADAA5B46B1E8} = {E03D2171-C4AB-45A3-681D-A2A2EBBB122A}
{F3F49C7E-9106-4FF7-A71D-442022D63F7B} = {E03D2171-C4AB-45A3-681D-A2A2EBBB122A}
+ {B5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B} = {85F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}
+ {C5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B} = {B5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}
{D38B6103-E564-8894-9748-4CF0C62984DB} = {B28A9B67-1C09-C756-C02A-7AC1895F9584}
{0A09784C-BB49-44E8-B07A-DA4EEEC1184E} = {D38B6103-E564-8894-9748-4CF0C62984DB}
{F5980D17-1A14-4DD9-82DF-6496E0C4B70D} = {D38B6103-E564-8894-9748-4CF0C62984DB}
@@ -2403,6 +2456,7 @@ Global
{BF0354AE-3748-A8DC-F79D-B21FDDEDDFAE} = {37B0CE47-14C8-F5BF-BDDD-13EEBE580A88}
{319B94CD-694C-16E8-9E3A-9577B99158DD} = {F7E192D1-DE6C-42A2-B52F-02849D482450}
{ADF14627-FCB5-4BD3-B65F-DDCC3A3F727C} = {319B94CD-694C-16E8-9E3A-9577B99158DD}
+ {38FE6BAB-DAEF-2CF7-2752-379F9094C190} = {B5F9C8A1-2B3D-4E5F-6A7B-8C9D0E1F2A3B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {926577F9-9246-44E4-BCE9-25DB003F1C51}
diff --git a/Directory.Build.props b/Directory.Build.props
index c9bceecf58..6b0e5d9932 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -46,10 +46,6 @@
true
-
- $(RepoRoot)eng/dnx/.mcp/server.json
-
-
true
diff --git a/Directory.Build.targets b/Directory.Build.targets
deleted file mode 100644
index 206917e460..0000000000
--- a/Directory.Build.targets
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- <_ProjectReferences>@(ProjectReference->'"%(FullPath)"', ',%0D ')
- <_ProjectReferencesJson>[%0D $([System.String]::Copy('$(_ProjectReferences)').Replace('\','\\'))%0D]
- $(MSBuildThisFileDirectory).work/project-references.json
-
-
-
-
-
-
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 3b1a3b4f39..751a1b4c0c 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -56,6 +56,7 @@
+
diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs
index 47fc2db6e8..39cd160cf4 100644
--- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs
+++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using Azure.Mcp.Core.Areas.Server.Commands.Discovery;
@@ -265,6 +264,13 @@ public static void InitializeConfigurationAndOptions(this IServiceCollection ser
.BindConfiguration(string.Empty)
.Configure>((options, rootConfiguration, serviceStartOptions) =>
{
+ // This environment variable can be used to disable telemetry collection entirely. This takes precedence
+ // over any other settings.
+ var collectTelemetry = rootConfiguration.GetValue("AZURE_MCP_COLLECT_TELEMETRY", true);
+ var transport = serviceStartOptions.Value.Transport;
+ var isStdioTransport = string.IsNullOrEmpty(transport)
+ || string.Equals(transport, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase);
+
// Assembly.GetEntryAssembly is used to retrieve the version of the server application as that is
// the assembly that will run the tool calls.
var entryAssembly = Assembly.GetEntryAssembly();
@@ -275,22 +281,6 @@ public static void InitializeConfigurationAndOptions(this IServiceCollection ser
options.Version = AssemblyHelper.GetAssemblyVersion(entryAssembly);
- // Disable telemetry when support logging is enabled to prevent sensitive data from being sent
- // to telemetry endpoints. Support logging captures debug-level information that may contain
- // sensitive data, so we disable all telemetry as a safety measure.
- if (!string.IsNullOrWhiteSpace(serviceStartOptions.Value.SupportLoggingFolder))
- {
- options.IsTelemetryEnabled = false;
- return;
- }
-
- // This environment variable can be used to disable telemetry collection entirely. This takes precedence
- // over any other settings.
- var collectTelemetry = rootConfiguration.GetValue("AZURE_MCP_COLLECT_TELEMETRY", true);
- var transport = serviceStartOptions.Value.Transport;
- var isStdioTransport = string.IsNullOrEmpty(transport)
- || string.Equals(transport, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase);
-
// if transport is not set (default to stdio) or is set to stdio, enable telemetry
// telemetry is disabled for HTTP transport
options.IsTelemetryEnabled = collectTelemetry && isStdioTransport;
diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs
index 1b93932300..7ecb2bf4fc 100644
--- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs
+++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs
@@ -7,7 +7,6 @@
using Azure.Mcp.Core.Areas.Server.Models;
using Azure.Mcp.Core.Areas.Server.Options;
using Azure.Mcp.Core.Helpers;
-using Azure.Mcp.Core.Logging;
using Azure.Mcp.Core.Services.Azure;
using Azure.Mcp.Core.Services.Azure.Authentication;
using Azure.Mcp.Core.Services.Caching;
@@ -83,7 +82,6 @@ protected override void RegisterOptions(Command command)
command.Options.Add(ServiceOptionDefinitions.DangerouslyDisableHttpIncomingAuth);
command.Options.Add(ServiceOptionDefinitions.InsecureDisableElicitation);
command.Options.Add(ServiceOptionDefinitions.OutgoingAuthStrategy);
- command.Options.Add(ServiceOptionDefinitions.DangerouslyWriteSupportLogsToDir);
command.Validators.Add(commandResult =>
{
string transport = ResolveTransport(commandResult);
@@ -95,42 +93,9 @@ protected override void RegisterOptions(Command command)
commandResult.GetValueOrDefault(ServiceOptionDefinitions.Tool.Name),
commandResult);
ValidateOutgoingAuthStrategy(commandResult);
- ValidateSupportLoggingFolder(commandResult);
});
}
- ///
- /// Validates that the support logging folder path is valid when specified.
- ///
- /// Command result to update on failure.
- private static void ValidateSupportLoggingFolder(CommandResult commandResult)
- {
- string? folderPath = commandResult.GetValueOrDefault(ServiceOptionDefinitions.DangerouslyWriteSupportLogsToDir.Name);
-
- if (folderPath is null)
- {
- return; // Option not specified, nothing to validate
- }
-
- // Validate the folder path is not empty or whitespace
- if (string.IsNullOrWhiteSpace(folderPath))
- {
- commandResult.AddError("The --dangerously-write-support-logs-to-dir option requires a valid folder path.");
- return;
- }
-
- // Validate the folder path is actually a valid path format
- try
- {
- // GetFullPath will throw for invalid path characters and other path format issues
- _ = Path.GetFullPath(folderPath);
- }
- catch (Exception ex) when (ex is ArgumentException or PathTooLongException or NotSupportedException)
- {
- commandResult.AddError($"The --dangerously-write-support-logs-to-dir option contains an invalid folder path '{folderPath}': {ex.Message}");
- }
- }
-
///
/// Binds the parsed command line arguments to the ServiceStartOptions object.
///
@@ -159,8 +124,7 @@ protected override ServiceStartOptions BindOptions(ParseResult parseResult)
Debug = parseResult.GetValueOrDefault(ServiceOptionDefinitions.Debug.Name),
DangerouslyDisableHttpIncomingAuth = parseResult.GetValueOrDefault(ServiceOptionDefinitions.DangerouslyDisableHttpIncomingAuth.Name),
InsecureDisableElicitation = parseResult.GetValueOrDefault(ServiceOptionDefinitions.InsecureDisableElicitation.Name),
- OutgoingAuthStrategy = outgoingAuthStrategy,
- SupportLoggingFolder = parseResult.GetValueOrDefault(ServiceOptionDefinitions.DangerouslyWriteSupportLogsToDir.Name)
+ OutgoingAuthStrategy = outgoingAuthStrategy
};
return options;
}
@@ -232,26 +196,6 @@ internal static void LogStartTelemetry(ITelemetryService telemetryService, Servi
}
}
- ///
- /// Configures support logging when a support logging folder is specified.
- /// This enables debug-level logging for troubleshooting and support purposes.
- ///
- /// The logging builder to configure.
- /// The server configuration options.
- private static void ConfigureSupportLogging(ILoggingBuilder logging, ServiceStartOptions options)
- {
- if (options.SupportLoggingFolder is null)
- {
- return;
- }
-
- // Set minimum log level to Debug when support logging is enabled
- logging.SetMinimumLevel(LogLevel.Debug);
-
- // Add file logging to the specified folder
- logging.AddSupportFileLogging(options.SupportLoggingFolder);
- }
-
///
/// Validates if the provided mode is a valid mode type.
///
@@ -424,8 +368,6 @@ private IHost CreateStdioHost(ServiceStartOptions serverOptions)
logging.AddFilter("Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider", LogLevel.Debug);
logging.SetMinimumLevel(LogLevel.Debug);
}
-
- ConfigureSupportLogging(logging, serverOptions);
})
.ConfigureServices(services =>
{
@@ -452,7 +394,6 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions)
builder.Logging.ConfigureOpenTelemetryLogger();
builder.Logging.AddEventSourceLogger();
builder.Logging.AddConsole();
- ConfigureSupportLogging(builder.Logging, serverOptions);
IServiceCollection services = builder.Services;
@@ -630,7 +571,6 @@ private IHost CreateIncomingAuthDisabledHttpHost(ServiceStartOptions serverOptio
builder.Logging.ConfigureOpenTelemetryLogger();
builder.Logging.AddEventSourceLogger();
builder.Logging.AddConsole();
- ConfigureSupportLogging(builder.Logging, serverOptions);
IServiceCollection services = builder.Services;
@@ -831,14 +771,6 @@ private static WebApplication UseHttpsRedirectionIfEnabled(WebApplication app)
return null;
}
- // Disable telemetry when support logging is enabled to prevent sensitive data from being sent
- // to telemetry endpoints. Support logging captures debug-level information that may contain
- // sensitive data, so we disable all telemetry as a safety measure.
- if (!string.IsNullOrWhiteSpace(options.SupportLoggingFolder))
- {
- return null;
- }
-
string? collectTelemetry = Environment.GetEnvironmentVariable("AZURE_MCP_COLLECT_TELEMETRY");
bool isTelemetryEnabled = string.IsNullOrWhiteSpace(collectTelemetry) ||
(bool.TryParse(collectTelemetry, out bool shouldCollectTelemetry) && shouldCollectTelemetry);
diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Options/ServiceOptionDefinitions.cs b/core/Azure.Mcp.Core/src/Areas/Server/Options/ServiceOptionDefinitions.cs
index 0cb4a951e1..4b554e79e9 100644
--- a/core/Azure.Mcp.Core/src/Areas/Server/Options/ServiceOptionDefinitions.cs
+++ b/core/Azure.Mcp.Core/src/Areas/Server/Options/ServiceOptionDefinitions.cs
@@ -14,7 +14,6 @@ public static class ServiceOptionDefinitions
public const string DangerouslyDisableHttpIncomingAuthName = "dangerously-disable-http-incoming-auth";
public const string InsecureDisableElicitationName = "insecure-disable-elicitation";
public const string OutgoingAuthStrategyName = "outgoing-auth-strategy";
- public const string DangerouslyWriteSupportLogsToDirName = "dangerously-write-support-logs-to-dir";
public static readonly Option Transport = new($"--{TransportName}")
{
@@ -92,12 +91,4 @@ public static class ServiceOptionDefinitions
Description = "Outgoing authentication strategy for Azure service requests. Valid values: NotSet, UseHostingEnvironmentIdentity, UseOnBehalfOf.",
DefaultValueFactory = _ => Options.OutgoingAuthStrategy.NotSet
};
-
- public static readonly Option DangerouslyWriteSupportLogsToDir = new(
- $"--{DangerouslyWriteSupportLogsToDirName}")
- {
- Required = false,
- Description = "Dangerously enables detailed debug-level logging for support and troubleshooting purposes. Specify a folder path where log files will be automatically created with timestamp-based filenames (e.g., azmcp_20251202_143052.log). This may include sensitive information in logs. Use with extreme caution and only when requested by support.",
- DefaultValueFactory = _ => null
- };
}
diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Options/ServiceStartOptions.cs b/core/Azure.Mcp.Core/src/Areas/Server/Options/ServiceStartOptions.cs
index dd3c6256f3..8023c60605 100644
--- a/core/Azure.Mcp.Core/src/Areas/Server/Options/ServiceStartOptions.cs
+++ b/core/Azure.Mcp.Core/src/Areas/Server/Options/ServiceStartOptions.cs
@@ -72,13 +72,4 @@ public class ServiceStartOptions
///
[JsonPropertyName("outgoingAuthStrategy")]
public OutgoingAuthStrategy OutgoingAuthStrategy { get; set; } = OutgoingAuthStrategy.NotSet;
-
- ///
- /// Gets or sets the folder path for support logging.
- /// When specified, detailed debug-level logging is enabled and logs are written to
- /// automatically generated files in this folder with timestamp-based filenames.
- /// Warning: This may include sensitive information in logs.
- ///
- [JsonPropertyName("supportLoggingFolder")]
- public string? SupportLoggingFolder { get; set; } = null;
}
diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json b/core/Azure.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json
index db35c9f20a..46b10d8b0d 100644
--- a/core/Azure.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json
+++ b/core/Azure.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json
@@ -2196,6 +2196,600 @@
"mappedToolList": [
"speech_tts_synthesize"
]
+ },
+ {
+ "name": "get_azure_storage_sync_services",
+ "description": "Get and list Azure File Sync Storage Sync Services. A Storage Sync Service is the top-level resource for Azure File Sync deployments.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": true,
+ "description": "This tool only performs read operations without modifying any state or data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_service_get"
+ ]
+ },
+ {
+ "name": "get_azure_storage_sync_groups",
+ "description": "Get and list Azure File Sync Sync Groups. A Sync Group defines the synchronization topology for a set of cloud and server endpoints.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": true,
+ "description": "This tool only performs read operations without modifying any state or data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_syncgroup_get"
+ ]
+ },
+ {
+ "name": "get_azure_storage_sync_cloud_endpoints",
+ "description": "Get and list Azure File Sync Cloud Endpoints. A Cloud Endpoint represents an Azure file share being synchronized.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": true,
+ "description": "This tool only performs read operations without modifying any state or data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_cloudendpoint_get"
+ ]
+ },
+ {
+ "name": "get_azure_storage_sync_registered_servers",
+ "description": "Get and list Azure File Sync Registered Servers. A Registered Server represents a server that has been registered with a Storage Sync Service.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": true,
+ "description": "This tool only performs read operations without modifying any state or data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_registeredserver_get"
+ ]
+ },
+ {
+ "name": "get_azure_storage_sync_server_endpoints",
+ "description": "Get and list Azure File Sync Server Endpoints. A Server Endpoint represents a specific folder on a registered server that is being synchronized.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": true,
+ "description": "This tool only performs read operations without modifying any state or data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_serverendpoint_get"
+ ]
+ },
+ {
+ "name": "create_azure_storage_sync_services",
+ "description": "Create new Azure File Sync Storage Sync Services. A Storage Sync Service is the top-level resource for Azure File Sync deployments.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": false,
+ "description": "Running this operation multiple times with the same arguments may have additional effects or produce different results."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_service_create"
+ ]
+ },
+ {
+ "name": "create_azure_storage_sync_groups",
+ "description": "Create new Azure File Sync Sync Groups. A Sync Group defines the synchronization topology for a set of cloud and server endpoints.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": false,
+ "description": "Running this operation multiple times with the same arguments may have additional effects or produce different results."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_syncgroup_create"
+ ]
+ },
+ {
+ "name": "create_azure_storage_sync_cloud_endpoints",
+ "description": "Create new Azure File Sync Cloud Endpoints. A Cloud Endpoint represents an Azure file share being synchronized.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": false,
+ "description": "Running this operation multiple times with the same arguments may have additional effects or produce different results."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_cloudendpoint_create"
+ ]
+ },
+ {
+ "name": "create_azure_storage_sync_server_endpoints",
+ "description": "Create new Azure File Sync Server Endpoints. A Server Endpoint represents a specific folder on a registered server that is being synchronized.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": false,
+ "description": "Running this operation multiple times with the same arguments may have additional effects or produce different results."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_serverendpoint_create"
+ ]
+ },
+ {
+ "name": "update_azure_storage_sync_services",
+ "description": "Update Azure File Sync Storage Sync Services configurations and settings.",
+ "toolMetadata": {
+ "destructive": {
+ "value": true,
+ "description": "This tool may delete or modify existing resources in its environment."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_service_update"
+ ]
+ },
+ {
+ "name": "update_azure_storage_sync_registered_servers",
+ "description": "Update Azure File Sync Registered Server configurations and settings.",
+ "toolMetadata": {
+ "destructive": {
+ "value": true,
+ "description": "This tool may delete or modify existing resources in its environment."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_registeredserver_update"
+ ]
+ },
+ {
+ "name": "update_azure_storage_sync_server_endpoints",
+ "description": "Update Azure File Sync Server Endpoint configurations and settings.",
+ "toolMetadata": {
+ "destructive": {
+ "value": true,
+ "description": "This tool may delete or modify existing resources in its environment."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_serverendpoint_update"
+ ]
+ },
+ {
+ "name": "delete_azure_storage_sync_services",
+ "description": "Delete Azure File Sync Storage Sync Services. Warning: This permanently removes the Storage Sync Service and all its associated sync groups and endpoints.",
+ "toolMetadata": {
+ "destructive": {
+ "value": true,
+ "description": "This tool may delete or modify existing resources in its environment."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_service_delete"
+ ]
+ },
+ {
+ "name": "delete_azure_storage_sync_groups",
+ "description": "Delete Azure File Sync Sync Groups. Warning: This permanently removes the Sync Group and all its associated endpoints.",
+ "toolMetadata": {
+ "destructive": {
+ "value": true,
+ "description": "This tool may delete or modify existing resources in its environment."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_syncgroup_delete"
+ ]
+ },
+ {
+ "name": "delete_azure_storage_sync_cloud_endpoints",
+ "description": "Delete Azure File Sync Cloud Endpoints. Warning: This stops synchronization for the associated Azure file share.",
+ "toolMetadata": {
+ "destructive": {
+ "value": true,
+ "description": "This tool may delete or modify existing resources in its environment."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_cloudendpoint_delete"
+ ]
+ },
+ {
+ "name": "delete_azure_storage_sync_registered_servers",
+ "description": "Unregister Azure File Sync Registered Servers from a Storage Sync Service. Warning: This removes the server registration and stops synchronization for all associated server endpoints.",
+ "toolMetadata": {
+ "destructive": {
+ "value": true,
+ "description": "This tool may delete or modify existing resources in its environment."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_registeredserver_unregister"
+ ]
+ },
+ {
+ "name": "delete_azure_storage_sync_server_endpoints",
+ "description": "Delete Azure File Sync Server Endpoints. Warning: This stops synchronization for the specified server folder.",
+ "toolMetadata": {
+ "destructive": {
+ "value": true,
+ "description": "This tool may delete or modify existing resources in its environment."
+ },
+ "idempotent": {
+ "value": true,
+ "description": "Running this operation multiple times with the same arguments produces the same result without additional effects."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_serverendpoint_delete"
+ ]
+ },
+ {
+ "name": "trigger_azure_storage_sync_cloud_endpoint_change_detection",
+ "description": "Trigger change detection on Azure File Sync Cloud Endpoints to force detection of changes in the Azure file share.",
+ "toolMetadata": {
+ "destructive": {
+ "value": false,
+ "description": "This tool performs only additive updates without deleting or modifying existing resources."
+ },
+ "idempotent": {
+ "value": false,
+ "description": "Running this operation multiple times with the same arguments may have additional effects or produce different results."
+ },
+ "openWorld": {
+ "value": false,
+ "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities."
+ },
+ "readOnly": {
+ "value": false,
+ "description": "This tool may modify its environment by creating, updating, or deleting data."
+ },
+ "secret": {
+ "value": false,
+ "description": "This tool does not handle sensitive or secret information."
+ },
+ "localRequired": {
+ "value": false,
+ "description": "This tool is available in both local and remote server modes."
+ }
+ },
+ "mappedToolList": [
+ "storagesync_cloudendpoint_changedetection"
+ ]
}
]
-}
\ No newline at end of file
+}
diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceCollectionExtensionsSerializedTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceCollectionExtensionsSerializedTests.cs
index c7f7527eb4..32e4e00688 100644
--- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceCollectionExtensionsSerializedTests.cs
+++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceCollectionExtensionsSerializedTests.cs
@@ -107,78 +107,4 @@ public void InitializeConfigurationAndOptions_Stdio()
Assert.False(actual.IsTelemetryEnabled);
}
-
- ///
- /// When SupportLoggingFolder is set, telemetry should be automatically disabled
- /// to prevent sensitive debug information from being sent to telemetry endpoints.
- ///
- [Fact]
- public void InitializeConfigurationAndOptions_WithSupportLoggingFolder_DisablesTelemetry()
- {
- // Arrange
- var serviceStartOptions = new ServiceStartOptions
- {
- SupportLoggingFolder = "/tmp/logs"
- };
- var services = SetupBaseServices().AddSingleton(Options.Create(serviceStartOptions));
-
- // Act
- Environment.SetEnvironmentVariable("AZURE_MCP_COLLECT_TELEMETRY", null);
- ServiceCollectionExtensions.InitializeConfigurationAndOptions(services);
- var provider = services.BuildServiceProvider();
-
- // Assert
- var options = provider.GetRequiredService>();
- Assert.False(options.Value.IsTelemetryEnabled, "Telemetry should be disabled when support logging folder is set");
- }
-
- ///
- /// SupportLoggingFolder takes precedence over AZURE_MCP_COLLECT_TELEMETRY=true.
- /// When support logging is enabled, telemetry must be disabled regardless of env var.
- ///
- [Fact]
- public void InitializeConfigurationAndOptions_WithSupportLoggingFolderAndEnvVarTrue_StillDisablesTelemetry()
- {
- // Arrange
- var serviceStartOptions = new ServiceStartOptions
- {
- SupportLoggingFolder = "/tmp/logs"
- };
- var services = SetupBaseServices().AddSingleton(Options.Create(serviceStartOptions));
-
- // Act
- Environment.SetEnvironmentVariable("AZURE_MCP_COLLECT_TELEMETRY", "true");
- ServiceCollectionExtensions.InitializeConfigurationAndOptions(services);
- var provider = services.BuildServiceProvider();
-
- // Assert
- var options = provider.GetRequiredService>();
- Assert.False(options.Value.IsTelemetryEnabled, "Telemetry should be disabled when support logging folder is set, regardless of environment variable");
- }
-
- ///
- /// Empty or whitespace SupportLoggingFolder should not disable telemetry.
- ///
- [Theory]
- [InlineData(null)]
- [InlineData("")]
- [InlineData(" ")]
- public void InitializeConfigurationAndOptions_WithEmptyOrWhitespaceSupportLoggingFolder_EnablesTelemetry(string? folderPath)
- {
- // Arrange
- var serviceStartOptions = new ServiceStartOptions
- {
- SupportLoggingFolder = folderPath
- };
- var services = SetupBaseServices().AddSingleton(Options.Create(serviceStartOptions));
-
- // Act
- Environment.SetEnvironmentVariable("AZURE_MCP_COLLECT_TELEMETRY", null);
- ServiceCollectionExtensions.InitializeConfigurationAndOptions(services);
- var provider = services.BuildServiceProvider();
-
- // Assert
- var options = provider.GetRequiredService>();
- Assert.True(options.Value.IsTelemetryEnabled, $"Telemetry should be enabled when support logging folder is '{folderPath}'");
- }
}
diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/ServiceStartCommandTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/ServiceStartCommandTests.cs
index 4465af5a86..a90169df9f 100644
--- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/ServiceStartCommandTests.cs
+++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/ServiceStartCommandTests.cs
@@ -279,62 +279,6 @@ public void BindOptions_WithDefaults_ReturnsDefaultValues()
Assert.False(options.Debug);
Assert.False(options.DangerouslyDisableHttpIncomingAuth);
Assert.False(options.InsecureDisableElicitation);
- Assert.Null(options.SupportLoggingFolder);
- }
-
- [Theory]
- [InlineData("/tmp/logs")]
- [InlineData("C:\\logs")]
- [InlineData(null)]
- public void DangerouslyWriteSupportLogsToDirOption_ParsesCorrectly(string? expectedFolder)
- {
- // Arrange
- var parseResult = CreateParseResultWithSupportLogging(expectedFolder);
-
- // Act
- var actualValue = parseResult.GetValue(ServiceOptionDefinitions.DangerouslyWriteSupportLogsToDir);
-
- // Assert
- Assert.Equal(expectedFolder, actualValue);
- }
-
- [Fact]
- public void BindOptions_WithSupportLoggingFolder_ReturnsCorrectlyConfiguredOptions()
- {
- // Arrange
- var logFolder = "/tmp/mcp-support-logs";
- var parseResult = CreateParseResultWithSupportLogging(logFolder);
-
- // Act
- var options = GetBoundOptions(parseResult);
-
- // Assert
- Assert.Equal(logFolder, options.SupportLoggingFolder);
- }
-
- [Fact]
- public void BindOptions_WithoutSupportLoggingFolder_ReturnsCorrectlyConfiguredOptions()
- {
- // Arrange
- var parseResult = CreateParseResultWithSupportLogging(null);
-
- // Act
- var options = GetBoundOptions(parseResult);
-
- // Assert
- Assert.Null(options.SupportLoggingFolder);
- }
-
- [Fact]
- public void AllOptionsRegistered_IncludesSupportLoggingToFolder()
- {
- // Arrange & Act
- var command = _command.GetCommand();
-
- // Assert
- var hasSupportLoggingFolderOption = command.Options.Any(o =>
- o.Name == ServiceOptionDefinitions.DangerouslyWriteSupportLogsToDir.Name);
- Assert.True(hasSupportLoggingFolderOption, "DangerouslyWriteSupportLogsToDir option should be registered");
}
[Fact]
@@ -397,67 +341,6 @@ public void Validate_WithNamespaceAndTool_ReturnsInvalidResult()
Assert.Contains("--namespace and --tool options cannot be used together", string.Join('\n', result.Errors));
}
- [Fact]
- public void Validate_WithSupportLoggingFolderWhitespace_ReturnsInvalidResult()
- {
- // Arrange
- var parseResult = CreateParseResultWithSupportLogging(" ");
- var commandResult = parseResult.CommandResult;
-
- // Act
- var result = _command.Validate(commandResult, null);
-
- // Assert
- Assert.False(result.IsValid);
- Assert.Contains("The --dangerously-write-support-logs-to-dir option requires a valid folder path", string.Join('\n', result.Errors));
- }
-
- [Fact]
- public void Validate_WithValidSupportLoggingFolder_ReturnsValidResult()
- {
- // Arrange
- var parseResult = CreateParseResultWithSupportLogging("/tmp/mcp-support-logs");
- var commandResult = parseResult.CommandResult;
-
- // Act
- var result = _command.Validate(commandResult, null);
-
- // Assert
- Assert.True(result.IsValid);
- Assert.Empty(result.Errors);
- }
-
- [Fact]
- public void Validate_WithoutSupportLoggingFolder_ReturnsValidResult()
- {
- // Arrange
- var parseResult = CreateParseResultWithSupportLogging(null);
- var commandResult = parseResult.CommandResult;
-
- // Act
- var result = _command.Validate(commandResult, null);
-
- // Assert
- Assert.True(result.IsValid);
- Assert.Empty(result.Errors);
- }
-
- [Fact]
- public async Task ExecuteAsync_WithSupportLoggingFolderWhitespace_ReturnsValidationError()
- {
- // Arrange
- var parseResult = CreateParseResultWithSupportLogging(" ");
- var serviceProvider = new ServiceCollection().BuildServiceProvider();
- var context = new CommandContext(serviceProvider);
-
- // Act
- var response = await _command.ExecuteAsync(context, parseResult, TestContext.Current.CancellationToken);
-
- // Assert
- Assert.Equal(HttpStatusCode.BadRequest, response.Status);
- Assert.Contains("The --dangerously-write-support-logs-to-dir option requires a valid folder path", response.Message);
- }
-
[Fact]
public async Task ExecuteAsync_WithNamespaceAndTool_ReturnsValidationError()
{
@@ -837,22 +720,6 @@ private ParseResult CreateParseResultWithMinimalOptions()
return _command.GetCommand().Parse([]);
}
- private ParseResult CreateParseResultWithSupportLogging(string? folderPath)
- {
- var args = new List
- {
- "--transport", "stdio"
- };
-
- if (folderPath is not null)
- {
- args.Add("--dangerously-write-support-logs-to-dir");
- args.Add(folderPath);
- }
-
- return _command.GetCommand().Parse([.. args]);
- }
-
private ParseResult CreateParseResultWithToolsAndMode(string[] tools, string mode)
{
var args = new List
diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs
index c63f559141..5187f854cd 100644
--- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs
+++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs
@@ -36,7 +36,6 @@ public TelemetryServiceTests()
_mockOptions.Value.Returns(_testConfiguration);
_mockServiceOptions = Substitute.For>();
- _mockServiceOptions.Value.Returns(new ServiceStartOptions());
_mockInformationProvider = Substitute.For();
_mockInformationProvider.GetMacAddressHash().Returns(Task.FromResult(TestMacAddressHash));
diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/CommandTestsBase.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/CommandTestsBase.cs
index dcc683ee0e..e24dbcba90 100644
--- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/CommandTestsBase.cs
+++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/CommandTestsBase.cs
@@ -45,8 +45,8 @@ public virtual async ValueTask InitializeAsync()
ResourceBaseName = "Sanitized",
SubscriptionName = "Sanitized",
TenantName = "Sanitized",
- ResourceGroupName = "Sanitized",
- TestMode = TestMode.Playback
+ TestMode = TestMode.Playback,
+ ResourceGroupName = "Sanitized"
};
protected virtual async ValueTask LoadSettingsAsync()
diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/Helpers/IRecordingPathResolver.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/Helpers/IRecordingPathResolver.cs
new file mode 100644
index 0000000000..b513f2d7b3
--- /dev/null
+++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/Helpers/IRecordingPathResolver.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Azure.Mcp.Tests.Client.Helpers;
+
+///
+/// Abstraction for resolving recording asset paths and session directories.
+/// Enables tests to substitute custom paths when exercising record/playback infrastructure.
+///
+public interface IRecordingPathResolver
+{
+ string RepositoryRoot { get; }
+
+ string GetSessionDirectory(Type testType, string? variantSuffix = null);
+
+ string GetAssetsJson(Type testType);
+}
diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/Helpers/LiveTestSettingsFixture.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/Helpers/LiveTestSettingsFixture.cs
index 24a33f0a1c..98c4bc526d 100644
--- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/Helpers/LiveTestSettingsFixture.cs
+++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/Helpers/LiveTestSettingsFixture.cs
@@ -10,23 +10,10 @@ namespace Azure.Mcp.Tests.Client.Helpers
{
public class LiveTestSettingsFixture : IAsyncLifetime
{
- private static readonly JsonSerializerOptions s_jsonSerializerOptions = new()
- {
- PropertyNameCaseInsensitive = true,
- Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() }
- };
-
public LiveTestSettings Settings { get; private set; } = new();
public virtual async ValueTask InitializeAsync()
{
- // If the TestMode is Playback, skip loading other settings. Skipping will match behaviors in CI when resources aren't deployed,
- // as content is recorded.
- if (Settings.TestMode == Tests.Helpers.TestMode.Playback)
- {
- return;
- }
-
var testSettingsFileName = ".testsettings.json";
var directory = Path.GetDirectoryName(typeof(LiveTestSettingsFixture).Assembly.Location);
@@ -37,7 +24,13 @@ public virtual async ValueTask InitializeAsync()
{
var content = await File.ReadAllTextAsync(testSettingsFilePath);
- Settings = JsonSerializer.Deserialize(content, s_jsonSerializerOptions)
+ var options = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true,
+ Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() }
+ };
+
+ Settings = JsonSerializer.Deserialize(content, options)
?? throw new Exception("Unable to deserialize live test settings");
foreach (var (key, value) in Settings.EnvironmentVariables)
diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/RecordedCommandTestsBase.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/RecordedCommandTestsBase.cs
index 0da52b32e5..25ff3a60d6 100644
--- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/RecordedCommandTestsBase.cs
+++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Client/RecordedCommandTestsBase.cs
@@ -242,9 +242,10 @@ public async Task StartProxyAsync(TestProxyFixture fixture)
private void PopulateDefaultSanitizers()
{
- // Registering a few common sanitizers for values that we know will be universally present and cleaned up
if (EnableDefaultSanitizerAdditions)
{
+ // Sanitize out the resource basename by default!
+ // This implies that tests shouldn't use this baseresourcename as part of their validation logic, as sanitization will replace it with "Sanitized" and cause confusion.
GeneralRegexSanitizers.Add(new GeneralRegexSanitizer(new GeneralRegexSanitizerBody()
{
Regex = Settings.ResourceBaseName,
diff --git a/eng/pipelines/templates/jobs/integration.yml b/eng/pipelines/templates/jobs/integration.yml
index 5761c2d9c6..f920240399 100644
--- a/eng/pipelines/templates/jobs/integration.yml
+++ b/eng/pipelines/templates/jobs/integration.yml
@@ -10,11 +10,6 @@ jobs:
- job: PublishToDev
displayName: Publish packages to dev feeds
condition: and(succeeded(), ne(variables['Skip.PublishPackage'], 'true'))
- pool:
- # On linux, we'd need mono to run nuget, so just use windows
- name: $(WINDOWSPOOL)
- image: $(WINDOWSVMIMAGE)
- os: windows
steps:
- checkout: self
@@ -49,7 +44,6 @@ jobs:
- task: PowerShell@2
displayName: 'Attach usage instructions to build summary'
inputs:
- pwsh: true
targetType: 'filePath'
filePath: 'eng/scripts/Get-PackageUsageText.ps1'
arguments: >
diff --git a/eng/pipelines/templates/jobs/release.yml b/eng/pipelines/templates/jobs/release.yml
index 05eb523eca..5fbd133974 100644
--- a/eng/pipelines/templates/jobs/release.yml
+++ b/eng/pipelines/templates/jobs/release.yml
@@ -90,7 +90,6 @@ jobs:
- task: PowerShell@2
displayName: Increment version
inputs:
- pwsh: true
targetType: filePath
filePath: $(Build.SourcesDirectory)/eng/scripts/Update-Version.ps1
arguments: >
diff --git a/eng/pipelines/templates/variables/image.yml b/eng/pipelines/templates/variables/image.yml
index c9dd2252b5..d6ace0e2d2 100644
--- a/eng/pipelines/templates/variables/image.yml
+++ b/eng/pipelines/templates/variables/image.yml
@@ -9,7 +9,7 @@ variables:
value: Azure Pipelines
- name: LINUXVMIMAGE
- value: ubuntu-24.04
+ value: ubuntu-22.04
- name: WINDOWSVMIMAGE
value: windows-2022
- name: MACVMIMAGE
diff --git a/servers/Azure.Mcp.Server/CHANGELOG.md b/servers/Azure.Mcp.Server/CHANGELOG.md
index 903b306b1c..48bef66203 100644
--- a/servers/Azure.Mcp.Server/CHANGELOG.md
+++ b/servers/Azure.Mcp.Server/CHANGELOG.md
@@ -2,10 +2,17 @@
The Azure MCP Server updates automatically by default whenever a new release comes out 🚀. We ship updates twice a week on Tuesdays and Thursdays 😊
-## 2.0.0-beta.8 (Unreleased)
+## 2.0.0-beta.9 (Unreleased)
### Features Added
+- Added Azure Storage Sync (StorageSync) module with 24 commands for managing cloud synchronization of file shares:
+ - **StorageSyncService** commands (5): Create, Delete, Get, List, Update
+ - **RegisteredServer** commands (5): Get, List, Register, Unregister, Update
+ - **SyncGroup** commands (4): Create, Delete, Get, List
+ - **CloudEndpoint** commands (5): Create, Delete, Get, List, TriggerChangeDetection
+ - **ServerEndpoint** commands (5): Create, Delete, Get, List, Update
+
- Added support logging capability with `--dangerously-write-support-logs-to-dir` option for troubleshooting and support scenarios. When enabled, detailed debug-level logs are written to automatically-generated timestamped log files (e.g., `azmcp_20251202_143052.log`) in the specified folder. All telemetry is automatically disabled when support logging is enabled to prevent sensitive debug information from being sent to telemetry endpoints.
- Replace hard-coded strings for Azure.Mcp.Server with ones from IConfiguration. [[#1269](https://github.com/microsoft/mcp/pull/1269)]
@@ -18,7 +25,6 @@ The Azure MCP Server updates automatically by default whenever a new release com
### Other Changes
- Switched to the new `Azure.Monitor.Query.Logs` package to query logs from Azure Monitor. [[#1309](https://github.com/microsoft/mcp/pull/1309)]
-- Move Azure AI Best Practices tool into Best Practice namespace [[#1323](https://github.com/microsoft/mcp/pull/1323)]
#### Dependency updates
@@ -26,6 +32,12 @@ The Azure MCP Server updates automatically by default whenever a new release com
- Updated `Microsoft.Azure.Mcp.AzTypes.Internal.Compact` from `0.2.802` to `0.2.804`. [[#1348](https://github.com/microsoft/mcp/pull/1348)]
+## 2.0.0-beta.8 (2025-12-11)
+
+### Bugs Fixed
+
+- Fixed an issue where the AI Best Practices tool would get called instead of the Best Practices tool. [[#1323](https://github.com/microsoft/mcp/pull/1323)]
+
## 2.0.0-beta.7 (2025-11-25)
### Bugs Fixed
diff --git a/servers/Azure.Mcp.Server/README.md b/servers/Azure.Mcp.Server/README.md
index aed6de218d..e66f8ea358 100644
--- a/servers/Azure.Mcp.Server/README.md
+++ b/servers/Azure.Mcp.Server/README.md
@@ -106,7 +106,7 @@ All Azure MCP tools in a single server. The Azure MCP Server implements the [MCP
Install Azure MCP Server using either an IDE extension or package manager. Choose one method below.
-> [!IMPORTANT]
+> [!IMPORTANT]
> Authenticate to Azure before running the Azure MCP server. See the [Authentication guide](https://github.com/microsoft/mcp/blob/main/docs/Authentication.md) for authentication methods and instructions.
## IDE
@@ -127,7 +127,7 @@ Compatible with both the [Stable](https://code.visualstudio.com/download) and [I
- If Visual Studio 2026 is already installed, open the **Visual Studio Installer** and select the **Modify** button, which displays the available workloads.
1. On the Workloads tab, select **Azure and AI development** and select **GitHub Copilot**.
1. Click **install while downloading** to complete the installation.
-
+
For more information, visit [Install GitHub Copilot for Azure in Visual Studio 2026](https://aka.ms/ghcp4a/vs2026)
### Visual Studio 2022
@@ -239,7 +239,7 @@ Install the .NET Tool: [Azure.Mcp](https://www.nuget.org/packages/Azure.Mcp).
```bash
dotnet tool install Azure.Mcp
```
-or
+or
```bash
dotnet tool install Azure.Mcp --version
```
@@ -382,7 +382,7 @@ Microsoft Foundry and Microsoft Copilot Studio require remote MCP server endpoin
* Create Microsoft Foundry agent threads
* List Microsoft Foundry agent threads
* Get messages of a Microsoft Foundry thread
-
+
### 🔎 Azure AI Search
* "What indexes do I have in my Azure AI Search service 'mysvc'?"
@@ -534,7 +534,7 @@ Microsoft Foundry and Microsoft Copilot Studio require remote MCP server endpoin
## Complete List of Supported Azure Services
-The Azure MCP Server provides tools for interacting with **40+ Azure service areas**:
+The Azure MCP Server provides tools for interacting with **41+ Azure service areas**:
- 🧮 **Microsoft Foundry** - AI model management, AI model deployment, and knowledge index management
- 🔎 **Azure AI Search** - Search engine/vector database operations
@@ -572,6 +572,7 @@ The Azure MCP Server provides tools for interacting with **40+ Azure service are
- 🗄️ **Azure SQL Elastic Pool** - Database resource sharing
- 🗄️ **Azure SQL Server** - Server administration
- 💾 **Azure Storage** - Blob storage
+- 🔄 **Azure Storage Sync** - Azure File Sync management operations
- 📋 **Azure Subscription** - Subscription management
- 🏗️ **Azure Terraform Best Practices** - Infrastructure as code guidance
- 🖥️ **Azure Virtual Desktop** - Virtual desktop infrastructure
diff --git a/servers/Azure.Mcp.Server/docs/azmcp-commands.md b/servers/Azure.Mcp.Server/docs/azmcp-commands.md
index 69b07ffc70..9278314e47 100644
--- a/servers/Azure.Mcp.Server/docs/azmcp-commands.md
+++ b/servers/Azure.Mcp.Server/docs/azmcp-commands.md
@@ -1885,6 +1885,173 @@ azmcp storage blob upload --subscription \
--local-file-path
```
+### Azure Storage Sync Operations
+
+#### Storage Sync Service
+
+```bash
+# Create a new Storage Sync Service for cloud file share synchronization
+azmcp storagesync service create --subscription \
+ --resource-group \
+ --name \
+ --location
+
+# Delete a Storage Sync Service (idempotent – succeeds even if the service does not exist)
+azmcp storagesync service delete --subscription \
+ --resource-group \
+ --name
+
+# Get a specific Storage Sync Service or list all services. If --name is provided, returns a specific service; otherwise, lists all services.
+# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync service get --subscription \
+ [--resource-group ] \
+ [--name ]
+
+# Update an existing Storage Sync Service configuration
+azmcp storagesync service update --subscription \
+ --resource-group \
+ --name \
+ [--tags ]
+```
+
+#### Sync Group
+
+```bash
+# Create a new Sync Group within a Storage Sync Service
+# ✅ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync syncgroup create --subscription \
+ --resource-group \
+ --service \
+ --name
+
+# Delete a Sync Group (idempotent – succeeds even if the group does not exist)
+# ✅ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync syncgroup delete --subscription \
+ --resource-group \
+ --service \
+ --name
+
+# Get a specific Sync Group or list all sync groups. If --name is provided, returns a specific sync group; otherwise, lists all sync groups in the Storage Sync Service.
+# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync syncgroup get --subscription \
+ --resource-group \
+ --service \
+ [--name ]
+```
+
+#### Cloud Endpoint
+
+```bash
+# Create a new Cloud Endpoint within a Sync Group
+# ✅ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync cloudendpoint create --subscription \
+ --resource-group \
+ --service \
+ --syncgroup \
+ --name \
+ --storage-account \
+ --share
+
+# Delete a Cloud Endpoint (idempotent – succeeds even if the endpoint does not exist)
+# ✅ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync cloudendpoint delete --subscription \
+ --resource-group \
+ --service \
+ --syncgroup \
+ --name
+
+# Get a specific Cloud Endpoint or list all cloud endpoints. If --name is provided, returns a specific cloud endpoint; otherwise, lists all cloud endpoints in the Sync Group.
+# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync cloudendpoint get --subscription \
+ --resource-group \
+ --service \
+ --syncgroup \
+ [--name ]
+
+# Trigger change detection on a Cloud Endpoint
+azmcp storagesync cloudendpoint changedetection --subscription \
+ --resource-group \
+ --service \
+ --syncgroup \
+ --name \
+ [--directory-path ]
+```
+
+#### Registered Server
+
+```bash
+# Get a specific Registered Server or list all registered servers. If --server is provided, returns a specific registered server; otherwise, lists all registered servers in the Storage Sync Service.
+# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync registeredserver get --subscription \
+ --resource-group \
+ --service \
+ [--server ]
+
+# Register a new server with a Storage Sync Service
+# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync registeredserver register --subscription \
+ --resource-group \
+ --service \
+ --server \
+ --server-id
+
+# Unregister a server from a Storage Sync Service
+# ✅ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync registeredserver unregister --subscription \
+ --resource-group \
+ --service \
+ --server
+
+# Update a Registered Server configuration
+# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync registeredserver update --subscription \
+ --resource-group \
+ --service \
+ --server \
+ [--certificate ]
+```
+
+#### Server Endpoint
+
+```bash
+# Create a new Server Endpoint within a Sync Group
+# ✅ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync serverendpoint create --subscription \
+ --resource-group \
+ --service \
+ --syncgroup \
+ --server \
+ --name \
+ --server-local-path
+
+# Delete a Server Endpoint (idempotent – succeeds even if the endpoint does not exist)
+# ✅ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync serverendpoint delete --subscription \
+ --resource-group \
+ --service \
+ --syncgroup \
+ --name
+
+# Get a specific Server Endpoint or list all server endpoints. If --name is provided, returns a specific server endpoint; otherwise, lists all server endpoints in the Sync Group.
+# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync serverendpoint get --subscription \
+ --resource-group \
+ --service \
+ --syncgroup \
+ [--name ]
+
+# Update a Server Endpoint configuration
+# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp storagesync serverendpoint update --subscription \
+ --resource-group \
+ --service \
+ --syncgroup \
+ --name \
+ [--cloud-tiering ] \
+ [--tiering-policy-days ] \
+ [--tiering-policy-volume-free-percent ]
+```
+
### Azure Subscription Management
```bash
diff --git a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
index c059e67977..b48e803194 100644
--- a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
+++ b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
@@ -625,6 +625,40 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
| storage_blob_get | Show me the blobs in the blob container in the storage account |
| storage_blob_upload | Upload file to storage blob in container in storage account |
+## Azure Storage Sync
+
+| Tool Name | Test Prompt |
+|:----------|:----------|
+| storagesync_service_create | Create a new Storage Sync Service named in resource group at location |
+| storagesync_service_delete | Delete the Storage Sync Service from resource group |
+| storagesync_service_get | Get the details of Storage Sync Service in resource group |
+| storagesync_service_get | List all Storage Sync Services in resource group |
+| storagesync_service_list | List all Storage Sync Services in my subscription |
+| storagesync_service_list | Show me all Storage Sync Services in resource group |
+| storagesync_service_update | Update Storage Sync Service with new tags |
+| storagesync_registeredserver_get | Get the details of registered server in service |
+| storagesync_registeredserver_get | List all registered servers in service |
+| storagesync_registeredserver_list | List all registered servers in service in resource group |
+| storagesync_registeredserver_register | Register a new server with service using server ID |
+| storagesync_registeredserver_unregister | Unregister server from service |
+| storagesync_registeredserver_update | Update registered server configuration in service |
+| storagesync_syncgroup_create | Create a new sync group named in service |
+| storagesync_syncgroup_delete | Delete the sync group from service |
+| storagesync_syncgroup_get | Get the details of sync group in service |
+| storagesync_syncgroup_list | List all sync groups in service in resource group |
+| storagesync_cloudendpoint_changedetection | Trigger change detection on cloud endpoint in sync group in service |
+| storagesync_cloudendpoint_create | Create a new cloud endpoint named for Azure file share in storage account |
+| storagesync_cloudendpoint_delete | Delete the cloud endpoint from sync group |
+| storagesync_cloudendpoint_get | Get the details of cloud endpoint in sync group |
+| storagesync_cloudendpoint_get | List all cloud endpoints in sync group |
+| storagesync_cloudendpoint_list | List all cloud endpoints in sync group in service |
+| storagesync_serverendpoint_create | Create a new server endpoint on server pointing to local path in sync group |
+| storagesync_serverendpoint_delete | Delete the server endpoint from sync group |
+| storagesync_serverendpoint_get | Get the details of server endpoint in sync group |
+| storagesync_serverendpoint_get | List all server endpoints in sync group |
+| storagesync_serverendpoint_list | List all server endpoints in sync group in service |
+| storagesync_serverendpoint_update | Update server endpoint with cloud tiering enabled and tiering policy in sync group |
+
## Azure Subscription Management
| Tool Name | Test Prompt |
diff --git a/servers/Azure.Mcp.Server/azureicon.png b/servers/Azure.Mcp.Server/images/azureicon.png
similarity index 100%
rename from servers/Azure.Mcp.Server/azureicon.png
rename to servers/Azure.Mcp.Server/images/azureicon.png
diff --git a/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj b/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj
index 7ccc36c4ba..14c47f2939 100644
--- a/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj
+++ b/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj
@@ -1,12 +1,12 @@
- 2.0.0-beta.8
+ 2.0.0-beta.9
azmcp
Azure MCP Server
Azure MCP Server - Model Context Protocol implementation for Azure
https://github.com/Microsoft/mcp/blob/main/servers/Azure.Mcp.Server#readme
$(RepoRoot)/servers/Azure.Mcp.Server/README.md
- $(MSBuildThisFileDirectory)../azureicon.png
+ $(MSBuildThisFileDirectory)../images/azureicon.png
com.microsoft/azure
diff --git a/servers/Azure.Mcp.Server/src/Program.cs b/servers/Azure.Mcp.Server/src/Program.cs
index d3c21f81ff..7b77d56bbf 100644
--- a/servers/Azure.Mcp.Server/src/Program.cs
+++ b/servers/Azure.Mcp.Server/src/Program.cs
@@ -116,6 +116,7 @@ private static IAreaSetup[] RegisterAreas()
new Azure.Mcp.Tools.SignalR.SignalRSetup(),
new Azure.Mcp.Tools.Sql.SqlSetup(),
new Azure.Mcp.Tools.Storage.StorageSetup(),
+ new Azure.Mcp.Tools.StorageSync.StorageSyncSetup(),
new Azure.Mcp.Tools.VirtualDesktop.VirtualDesktopSetup(),
new Azure.Mcp.Tools.Workbooks.WorkbooksSetup(),
#if !BUILD_NATIVE
diff --git a/servers/Azure.Mcp.Server/src/Properties/launchSettings.json b/servers/Azure.Mcp.Server/src/Properties/launchSettings.json
index bffcb4ab1d..11295e1a47 100644
--- a/servers/Azure.Mcp.Server/src/Properties/launchSettings.json
+++ b/servers/Azure.Mcp.Server/src/Properties/launchSettings.json
@@ -22,4 +22,4 @@
}
},
"$schema": "https://json.schemastore.org/launchsettings.json"
-}
\ No newline at end of file
+}
diff --git a/servers/Azure.Mcp.Server/vscode/CHANGELOG.md b/servers/Azure.Mcp.Server/vscode/CHANGELOG.md
index c0ae4ae236..8952d7af5a 100644
--- a/servers/Azure.Mcp.Server/vscode/CHANGELOG.md
+++ b/servers/Azure.Mcp.Server/vscode/CHANGELOG.md
@@ -1,5 +1,11 @@
# Release History
+## 2.0.8 (2025-12-11) (pre-release)
+
+### Fixed
+
+- Fixed an issue where the AI Best Practices tool would get called instead of the Best Practices tool. [[#1323](https://github.com/microsoft/mcp/pull/1323)]
+
## 2.0.7 (2025-11-25) (pre-release)
### Changed
diff --git a/servers/Fabric.Mcp.Server/CHANGELOG.md b/servers/Fabric.Mcp.Server/CHANGELOG.md
index ef5248f05c..260e2e35df 100644
--- a/servers/Fabric.Mcp.Server/CHANGELOG.md
+++ b/servers/Fabric.Mcp.Server/CHANGELOG.md
@@ -5,7 +5,17 @@ All notable changes to the Microsoft Fabric MCP Server will be documented in thi
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## [0.0.0-beta.4] (2025-12-16)
+## 0.0.0-beta.5 (Unreleased)
+
+### Features Added
+
+### Breaking Changes
+
+### Bugs Fixed
+
+### Other Changes
+
+## 0.0.0-beta.4 (2025-12-16)
### Features Added
@@ -20,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated API specifications for multiple items.
-## [0.0.0-beta.3] (2025-12-04)
+## 0.0.0-beta.3 (2025-12-04)
### Features Added
@@ -64,4 +74,4 @@ Initial release of the Microsoft Fabric MCP Server in **Public Preview**.
---
-For support, contributions, and feedback, see [SUPPORT](https://github.com/microsoft/mcp/blob/main/servers/Fabric.Mcp.Server/SUPPORT.md).
\ No newline at end of file
+For support, contributions, and feedback, see [SUPPORT](https://github.com/microsoft/mcp/blob/main/servers/Fabric.Mcp.Server/SUPPORT.md).
diff --git a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj
index 134eedb39c..dfddf5495f 100644
--- a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj
+++ b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj
@@ -1,6 +1,6 @@
-
+
- 0.0.0-beta.4
+ 0.0.0-beta.5
fabmcp
Fabric MCP Server
Microsoft Fabric MCP Server - Model Context Protocol implementation for Fabric
diff --git a/servers/Fabric.Mcp.Server/vscode/package.json b/servers/Fabric.Mcp.Server/vscode/package.json
index 7a6232f2b2..9f2aa19e33 100644
--- a/servers/Fabric.Mcp.Server/vscode/package.json
+++ b/servers/Fabric.Mcp.Server/vscode/package.json
@@ -52,10 +52,12 @@
"items": {
"type": "string",
"enum": [
- "publicapis"
+ "publicapis",
+ "onelake"
],
"markdownEnumDescriptions": [
- "Fabric public APIs — Container registry management."
+ "Fabric public APIs — Fabric public APIs specifications and examples.",
+ "OneLake — Manage and interact with OneLake data lake storage."
]
},
"uniqueItems": true,
@@ -66,7 +68,7 @@
"type": "string",
"enum": ["single", "namespace", "all"],
"default": "all",
- "markdownDescription": "Server Mode determines how tools are exposed: `single` collapses every tool (100+) into one (1) single tool that routes internally, `namespace` collapses all tools down into logical Fabric service namespace grouping, while `all` (default) exposes every MCP tool directly to the MCP client. We recommend all as the right balance between MCP tool count and tool selection accuracy. **Note:** Changes require restarting the MCP server if MCP Autostart is not configured (Command Palette → MCP: List Servers → Fabric MCP → Start/Restart)."
+ "markdownDescription": "Server Mode determines how tools are exposed: `single` collapses every tool into one single tool that routes internally, `namespace` collapses all tools down into logical Fabric service namespace grouping, while `all` (default) exposes every MCP tool directly to the MCP client. We recommend all as the right balance between MCP tool count and tool selection accuracy. **Note:** Changes require restarting the MCP server if MCP Autostart is not configured (Command Palette → MCP: List Servers → Fabric MCP → Start/Restart)."
},
"fabricMcp.readOnly": {
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/AksCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/AksCommandTests.cs
index eeae72bf11..38732bc6e0 100644
--- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/AksCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/AksCommandTests.cs
@@ -4,12 +4,12 @@
using System.Text.Json;
using Azure.Mcp.Tests;
using Azure.Mcp.Tests.Client;
+using Azure.Mcp.Tests.Client.Helpers;
using Xunit;
namespace Azure.Mcp.Tools.Aks.LiveTests;
-public sealed class AksCommandTests(ITestOutputHelper output)
- : CommandTestsBase(output)
+public sealed class AksCommandTests(ITestOutputHelper output, TestProxyFixture fixture) : RecordedCommandTestsBase(output, fixture)
{
[Fact]
@@ -149,8 +149,8 @@ public async Task Should_get_specific_aks_cluster()
// Get the first cluster's details
var firstCluster = clusters.EnumerateArray().First();
- var clusterName = firstCluster.GetProperty("name").GetString()!;
- var resourceGroupName = firstCluster.GetProperty("resourceGroupName").GetString()!;
+ var clusterName = RegisterOrRetrieveVariable("firstClusterName", firstCluster.GetProperty("name").GetString()!);
+ var resourceGroupName = RegisterOrRetrieveVariable("firstResourceGroupName", firstCluster.GetProperty("resourceGroupName").GetString()!);
// Now test the get command
var getResult = await CallToolAsync(
@@ -172,33 +172,33 @@ public async Task Should_get_specific_aks_cluster()
// Verify the cluster details
var nameProperty = cluster.AssertProperty("name");
- Assert.Equal(clusterName, nameProperty.GetString());
+ Assert.Equal(TestMode == Tests.Helpers.TestMode.Playback ? "Sanitized" : clusterName, nameProperty.GetString());
var rgProperty = cluster.AssertProperty("resourceGroupName");
Assert.Equal(resourceGroupName, rgProperty.GetString());
// Verify other common properties exist
- Assert.True(cluster.TryGetProperty("subscriptionId", out _));
- Assert.True(cluster.TryGetProperty("location", out _));
+ cluster.AssertProperty("subscriptionId");
+ cluster.AssertProperty("location");
// Enriched cluster checks
- Assert.True(cluster.TryGetProperty("id", out _));
- Assert.True(cluster.TryGetProperty("enableRbac", out _));
- Assert.True(cluster.TryGetProperty("skuName", out _));
- Assert.True(cluster.TryGetProperty("skuTier", out _));
- Assert.True(cluster.TryGetProperty("nodeResourceGroup", out _));
- Assert.True(cluster.TryGetProperty("maxAgentPools", out _));
- Assert.True(cluster.TryGetProperty("supportPlan", out _));
+ cluster.AssertProperty("id");
+ cluster.AssertProperty("enableRbac");
+ cluster.AssertProperty("skuName");
+ cluster.AssertProperty("skuTier");
+ cluster.AssertProperty("nodeResourceGroup");
+ cluster.AssertProperty("maxAgentPools");
+ cluster.AssertProperty("supportPlan");
// Profiles present or null
- Assert.True(cluster.TryGetProperty("networkProfile", out _));
- Assert.True(cluster.TryGetProperty("windowsProfile", out _));
- Assert.True(cluster.TryGetProperty("servicePrincipalProfile", out _));
- Assert.True(cluster.TryGetProperty("addonProfiles", out _));
- Assert.True(cluster.TryGetProperty("identityProfile", out _));
+ cluster.AssertProperty("networkProfile");
+ cluster.AssertProperty("windowsProfile");
+ cluster.AssertProperty("servicePrincipalProfile");
+ cluster.AssertProperty("addonProfiles");
+ cluster.AssertProperty("identityProfile");
// Get-specific should return agentPoolProfiles (we populate on Get)
- Assert.True(cluster.TryGetProperty("agentPoolProfiles", out var pools));
+ var pools = cluster.AssertProperty("agentPoolProfiles");
Assert.Equal(JsonValueKind.Array, pools.ValueKind);
}
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolCommandTests.cs
index 031261b5c5..41c5033277 100644
--- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolCommandTests.cs
@@ -4,12 +4,12 @@
using System.Text.Json;
using Azure.Mcp.Tests;
using Azure.Mcp.Tests.Client;
+using Azure.Mcp.Tests.Client.Helpers;
using Xunit;
namespace Azure.Mcp.Tools.Aks.LiveTests;
-public sealed class NodepoolCommandTests(ITestOutputHelper output)
- : CommandTestsBase(output)
+public sealed class NodepoolCommandTests(ITestOutputHelper output, TestProxyFixture fixture) : RecordedCommandTestsBase(output, fixture)
{
[Fact]
public async Task Should_list_nodepools_for_cluster()
@@ -26,8 +26,8 @@ public async Task Should_list_nodepools_for_cluster()
Assert.True(clusters.GetArrayLength() > 0, "Expected at least one AKS cluster for testing nodepool get command");
var firstCluster = clusters.EnumerateArray().First();
- var clusterName = firstCluster.GetProperty("name").GetString()!;
- var resourceGroupName = firstCluster.GetProperty("resourceGroupName").GetString()!;
+ var clusterName = RegisterOrRetrieveVariable("firstClusterName", firstCluster.GetProperty("name").GetString()!);
+ var resourceGroupName = RegisterOrRetrieveVariable("firstResourceGroupName", firstCluster.GetProperty("resourceGroupName").GetString()!);
// List node pools for that cluster
var nodepoolResult = await CallToolAsync(
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolGetCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolGetCommandTests.cs
index 9927e69d90..7149e43206 100644
--- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolGetCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolGetCommandTests.cs
@@ -4,12 +4,12 @@
using System.Text.Json;
using Azure.Mcp.Tests;
using Azure.Mcp.Tests.Client;
+using Azure.Mcp.Tests.Client.Helpers;
using Xunit;
namespace Azure.Mcp.Tools.Aks.LiveTests;
-public sealed class NodepoolGetCommandTests(ITestOutputHelper output)
- : CommandTestsBase(output)
+public sealed class NodepoolGetCommandTests(ITestOutputHelper output, TestProxyFixture fixture) : RecordedCommandTestsBase(output, fixture)
{
[Fact]
public async Task Should_get_nodepool_for_cluster()
@@ -26,8 +26,8 @@ public async Task Should_get_nodepool_for_cluster()
Assert.True(clusters.GetArrayLength() > 0, "Expected at least one AKS cluster for testing nodepool get command");
var firstCluster = clusters.EnumerateArray().First();
- var clusterName = firstCluster.GetProperty("name").GetString()!;
- var resourceGroupName = firstCluster.GetProperty("resourceGroupName").GetString()!;
+ var clusterName = RegisterOrRetrieveVariable("firstClusterName", firstCluster.GetProperty("name").GetString()!);
+ var resourceGroupName = RegisterOrRetrieveVariable("firstResourceGroupName", firstCluster.GetProperty("resourceGroupName").GetString()!);
// Find a node pool to query
var nodepoolList = await CallToolAsync(
@@ -43,7 +43,7 @@ public async Task Should_get_nodepool_for_cluster()
Assert.True(nodePools.GetArrayLength() > 0, "Expected at least one node pool in the cluster");
var firstPool = nodePools.EnumerateArray().First();
- var nodepoolName = firstPool.GetProperty("name").GetString()!;
+ var nodepoolName = RegisterOrRetrieveVariable("firstNodepoolName", firstPool.GetProperty("name").GetString()!);
// Get details for that node pool
var nodepoolGet = await CallToolAsync(
@@ -62,7 +62,7 @@ public async Task Should_get_nodepool_for_cluster()
var nodePool = nodePools.EnumerateArray().First();
Assert.Equal(JsonValueKind.Object, nodePool.ValueKind);
- Assert.Equal(nodepoolName, nodePool.GetProperty("name").GetString());
+ Assert.Equal(TestMode == Tests.Helpers.TestMode.Playback ? "Sanitized" : nodepoolName, nodePool.GetProperty("name").GetString());
if (nodePool.TryGetProperty("mode", out var modeProperty))
{
@@ -74,12 +74,12 @@ public async Task Should_get_nodepool_for_cluster()
Assert.False(string.IsNullOrEmpty(stateProperty.GetString()));
}
- Assert.True(nodePool.TryGetProperty("orchestratorVersion", out _));
- Assert.True(nodePool.TryGetProperty("currentOrchestratorVersion", out _));
- Assert.True(nodePool.TryGetProperty("enableAutoScaling", out _));
- Assert.True(nodePool.TryGetProperty("maxPods", out _));
- Assert.True(nodePool.TryGetProperty("osSKU", out _));
- Assert.True(nodePool.TryGetProperty("nodeImageVersion", out _));
+ nodePool.AssertProperty("orchestratorVersion");
+ nodePool.AssertProperty("currentOrchestratorVersion");
+ nodePool.AssertProperty("enableAutoScaling");
+ nodePool.AssertProperty("maxPods");
+ nodePool.AssertProperty("osSKU");
+ nodePool.AssertProperty("nodeImageVersion");
// Enriched node pool fields (presence/type checks)
if (nodePool.TryGetProperty("tags", out var tags))
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/assets.json b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/assets.json
new file mode 100644
index 0000000000..26d8c0e9a4
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/assets.json
@@ -0,0 +1,6 @@
+{
+ "AssetsRepo": "Azure/azure-sdk-assets",
+ "AssetsRepoPrefixPath": "",
+ "TagPrefix": "Azure.Mcp.Tools.Aks.LiveTests",
+ "Tag": "Azure.Mcp.Tools.Aks.LiveTests_2f1814827b"
+}
diff --git a/tools/Azure.Mcp.Tools.Authorization/tests/Azure.Mcp.Tools.Authorization.LiveTests/AuthorizationCommandTests.cs b/tools/Azure.Mcp.Tools.Authorization/tests/Azure.Mcp.Tools.Authorization.LiveTests/AuthorizationCommandTests.cs
index b508f8ec40..38f8a92f77 100644
--- a/tools/Azure.Mcp.Tools.Authorization/tests/Azure.Mcp.Tools.Authorization.LiveTests/AuthorizationCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Authorization/tests/Azure.Mcp.Tools.Authorization.LiveTests/AuthorizationCommandTests.cs
@@ -4,18 +4,19 @@
using System.Text.Json;
using Azure.Mcp.Tests;
using Azure.Mcp.Tests.Client;
+using Azure.Mcp.Tests.Client.Helpers;
using Xunit;
namespace Azure.Mcp.Tools.Authorization.LiveTests;
-public class AuthorizationCommandTests(ITestOutputHelper output)
- : CommandTestsBase(output)
+public class AuthorizationCommandTests(ITestOutputHelper output, TestProxyFixture fixture) : RecordedCommandTestsBase(output, fixture)
{
[Fact]
public async Task Should_list_role_assignments()
{
- var scope = $"/subscriptions/{Settings.SubscriptionId}/resourceGroups/{Settings.ResourceGroupName}";
+ var resourceGroupName = RegisterOrRetrieveVariable("resourceGroupName", Settings.ResourceGroupName);
+ var scope = $"/subscriptions/{Settings.SubscriptionId}/resourceGroups/{resourceGroupName}";
var result = await CallToolAsync(
"role_assignment_list",
new()
diff --git a/tools/Azure.Mcp.Tools.Authorization/tests/Azure.Mcp.Tools.Authorization.LiveTests/assets.json b/tools/Azure.Mcp.Tools.Authorization/tests/Azure.Mcp.Tools.Authorization.LiveTests/assets.json
new file mode 100644
index 0000000000..3fdba95297
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Authorization/tests/Azure.Mcp.Tools.Authorization.LiveTests/assets.json
@@ -0,0 +1,6 @@
+{
+ "AssetsRepo": "Azure/azure-sdk-assets",
+ "AssetsRepoPrefixPath": "",
+ "TagPrefix": "Azure.Mcp.Tools.Authorization.LiveTests",
+ "Tag": "Azure.Mcp.Tools.Authorization.LiveTests_23629421af"
+}
diff --git a/tools/Azure.Mcp.Tools.FunctionApp/tests/Azure.Mcp.Tools.FunctionApp.LiveTests/FunctionAppCommandTests.cs b/tools/Azure.Mcp.Tools.FunctionApp/tests/Azure.Mcp.Tools.FunctionApp.LiveTests/FunctionAppCommandTests.cs
index db12f5b800..eee5053a8a 100644
--- a/tools/Azure.Mcp.Tools.FunctionApp/tests/Azure.Mcp.Tools.FunctionApp.LiveTests/FunctionAppCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.FunctionApp/tests/Azure.Mcp.Tools.FunctionApp.LiveTests/FunctionAppCommandTests.cs
@@ -4,12 +4,29 @@
using System.Text.Json;
using Azure.Mcp.Tests;
using Azure.Mcp.Tests.Client;
+using Azure.Mcp.Tests.Client.Helpers;
+using Azure.Mcp.Tests.Generated.Models;
using Xunit;
namespace Azure.Mcp.Tools.FunctionApp.LiveTests;
-public sealed class FunctionAppCommandTests(ITestOutputHelper output) : CommandTestsBase(output)
+public sealed class FunctionAppCommandTests(ITestOutputHelper output, TestProxyFixture fixture) : RecordedCommandTestsBase(output, fixture)
{
+ public override List BodyKeySanitizers =>
+ [
+ ..base.BodyKeySanitizers,
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.customDomainVerificationId")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.inboundIpAddress")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.possibleInboundIpAddresses")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.inboundIpv6Address")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.possibleInboundIpv6Addresses")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.ftpsHostName")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.outboundIpAddresses")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.possibleOutboundIpAddresses")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.outboundIpv6Addresses")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.possibleOutboundIpv6Addresses")),
+ new BodyKeySanitizer(new BodyKeySanitizerBody("$..properties.homeStamp")),
+ ];
[Fact]
public async Task Should_list_function_apps_by_subscription()
@@ -30,13 +47,13 @@ public async Task Should_list_function_apps_by_subscription()
{
Assert.Equal(JsonValueKind.Object, functionApp.ValueKind);
- var nameProperty = functionApp.GetProperty("name");
+ var nameProperty = functionApp.AssertProperty("name");
Assert.False(string.IsNullOrEmpty(nameProperty.GetString()));
- var rgProperty = functionApp.GetProperty("resourceGroupName");
+ var rgProperty = functionApp.AssertProperty("resourceGroupName");
Assert.False(string.IsNullOrEmpty(rgProperty.GetString()));
- var aspProperty = functionApp.GetProperty("appServicePlanName");
+ var aspProperty = functionApp.AssertProperty("appServicePlanName");
Assert.False(string.IsNullOrEmpty(aspProperty.GetString()));
if (functionApp.TryGetProperty("location", out var locationProperty))
@@ -92,20 +109,26 @@ public async Task Should_validate_required_subscription_parameter()
[Fact]
public async Task Should_get_specific_function_app()
{
+ var resourceGroupName = RegisterOrRetrieveVariable("resourceGroupName", Settings.ResourceGroupName);
// List to obtain a real function app and its resource group
var listResult = await CallToolAsync(
"functionapp_get",
new()
{
- { "subscription", Settings.SubscriptionId }
+ { "subscription", Settings.SubscriptionId },
+ { "resource-group", resourceGroupName }
});
var functionApps = listResult.AssertProperty("functionApps");
Assert.True(functionApps.GetArrayLength() > 0, "Expected at least one Function App for get command test");
var first = functionApps.EnumerateArray().First();
- var name = first.GetProperty("name").GetString()!;
- var resourceGroup = first.GetProperty("resourceGroupName").GetString()!;
+ var name = RegisterOrRetrieveVariable("functionAppName", first.AssertProperty("name").GetString()!);
+ if (TestMode == Tests.Helpers.TestMode.Playback)
+ {
+ name = string.Concat("Sanitized", name.AsSpan(name.IndexOf('-')));
+ }
+ var resourceGroup = first.AssertProperty("resourceGroupName").GetString();
var getResult = await CallToolAsync(
"functionapp_get",
@@ -123,8 +146,8 @@ public async Task Should_get_specific_function_app()
var functionApp = functionApps.EnumerateArray().First();
Assert.Equal(JsonValueKind.Object, functionApp.ValueKind);
- Assert.Equal(name, functionApp.GetProperty("name").GetString());
- Assert.Equal(resourceGroup, functionApp.GetProperty("resourceGroupName").GetString());
+ Assert.Equal(TestMode == Tests.Helpers.TestMode.Playback ? "Sanitized" : name, functionApp.AssertProperty("name").GetString());
+ Assert.Equal(resourceGroup, functionApp.AssertProperty("resourceGroupName").GetString());
// Common useful properties
if (functionApp.TryGetProperty("location", out var loc))
{
diff --git a/tools/Azure.Mcp.Tools.FunctionApp/tests/Azure.Mcp.Tools.FunctionApp.LiveTests/assets.json b/tools/Azure.Mcp.Tools.FunctionApp/tests/Azure.Mcp.Tools.FunctionApp.LiveTests/assets.json
new file mode 100644
index 0000000000..4f0cfada03
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.FunctionApp/tests/Azure.Mcp.Tools.FunctionApp.LiveTests/assets.json
@@ -0,0 +1,6 @@
+{
+ "AssetsRepo": "Azure/azure-sdk-assets",
+ "AssetsRepoPrefixPath": "",
+ "TagPrefix": "Azure.Mcp.Tools.FunctionApp.LiveTests",
+ "Tag": "Azure.Mcp.Tools.FunctionApp.LiveTests_bcd7a5b214"
+}
diff --git a/tools/Azure.Mcp.Tools.Marketplace/src/Services/MarketplaceService.cs b/tools/Azure.Mcp.Tools.Marketplace/src/Services/MarketplaceService.cs
index 5470a89d76..7f7d2fb87a 100644
--- a/tools/Azure.Mcp.Tools.Marketplace/src/Services/MarketplaceService.cs
+++ b/tools/Azure.Mcp.Tools.Marketplace/src/Services/MarketplaceService.cs
@@ -223,30 +223,23 @@ CancellationToken cancellationToken
)
{
// Use Azure Core pipeline approach consistently
- var clientOptions = AddDefaultPolicies(new MarketplaceClientOptions());
+ using var httpClient = TenantService.GetClient();
+ var clientOptions = ConfigureRetryPolicy(
+ AddDefaultPolicies(new MarketplaceClientOptions()),
+ retryPolicy);
+ clientOptions.Transport = new HttpClientTransport(httpClient);
- // Configure retry policy if provided
- if (retryPolicy != null)
- {
- clientOptions.Retry.MaxRetries = retryPolicy.MaxRetries;
- clientOptions.Retry.Mode = retryPolicy.Mode;
- clientOptions.Retry.Delay = TimeSpan.FromSeconds(retryPolicy.DelaySeconds);
- clientOptions.Retry.MaxDelay = TimeSpan.FromSeconds(retryPolicy.MaxDelaySeconds);
- clientOptions.Retry.NetworkTimeout = TimeSpan.FromSeconds(retryPolicy.NetworkTimeoutSeconds);
- }
-
- // Create pipeline
var pipeline = HttpPipelineBuilder.Build(clientOptions);
string accessToken = (await GetArmAccessTokenAsync(tenantId: tenant, cancellationToken)).Token;
ValidateRequiredParameters((nameof(accessToken), accessToken));
- var request = pipeline.CreateRequest();
+ using var request = pipeline.CreateRequest();
request.Method = RequestMethod.Get;
request.Uri.Reset(new Uri(url));
request.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await pipeline.SendRequestAsync(request, cancellationToken);
+ using var response = await pipeline.SendRequestAsync(request, cancellationToken);
if (!response.IsError)
{
diff --git a/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/ProductGetCommandTests.cs b/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/ProductGetCommandTests.cs
index c179e5ec78..0473bbbc55 100644
--- a/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/ProductGetCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/ProductGetCommandTests.cs
@@ -2,49 +2,19 @@
// Licensed under the MIT License.
using System.Text.Json;
-using Azure.Mcp.Core.Services.Azure.Authentication;
-using Azure.Mcp.Core.Services.Azure.Tenant;
-using Azure.Mcp.Core.Services.Caching;
using Azure.Mcp.Tests;
using Azure.Mcp.Tests.Client;
-using Azure.Mcp.Tests.Helpers;
-using Azure.Mcp.Tools.Marketplace.Services;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging.Abstractions;
+using Azure.Mcp.Tests.Client.Helpers;
using Xunit;
namespace Azure.Mcp.Tools.Marketplace.LiveTests;
-public class ProductGetCommandTests : CommandTestsBase
+public sealed class ProductGetCommandTests(ITestOutputHelper output, TestProxyFixture fixture) : RecordedCommandTestsBase(output, fixture)
{
private const string ProductKey = "product";
private const string ProductId = "test_test_pmc2pc1.vmsr_uat_beta";
private const string Language = "en";
private const string Market = "US";
- private readonly MarketplaceService _marketplaceService;
- private readonly ServiceProvider _httpClientProvider;
-
- public ProductGetCommandTests(ITestOutputHelper output) : base(output)
- {
- var memoryCache = new MemoryCache(Microsoft.Extensions.Options.Options.Create(new MemoryCacheOptions()));
- var cacheService = new SingleUserCliCacheService(memoryCache);
- var tokenProvider = new SingleIdentityTokenCredentialProvider(NullLoggerFactory.Instance);
- _httpClientProvider = TestHttpClientFactoryProvider.Create();
- var httpClientFactory = _httpClientProvider.GetRequiredService();
- var tenantService = new TenantService(tokenProvider, cacheService, httpClientFactory);
- _marketplaceService = new MarketplaceService(tenantService);
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- _httpClientProvider.Dispose();
- }
-
- base.Dispose(disposing);
- }
[Fact]
public async Task Should_get_marketplace_product()
diff --git a/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/ProductListCommandTests.cs b/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/ProductListCommandTests.cs
index 42a2930bfc..21efe1f1fe 100644
--- a/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/ProductListCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/ProductListCommandTests.cs
@@ -2,47 +2,17 @@
// Licensed under the MIT License.
using System.Text.Json;
-using Azure.Mcp.Core.Services.Azure.Authentication;
-using Azure.Mcp.Core.Services.Azure.Tenant;
-using Azure.Mcp.Core.Services.Caching;
using Azure.Mcp.Tests;
using Azure.Mcp.Tests.Client;
-using Azure.Mcp.Tests.Helpers;
-using Azure.Mcp.Tools.Marketplace.Services;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging.Abstractions;
+using Azure.Mcp.Tests.Client.Helpers;
using Xunit;
namespace Azure.Mcp.Tools.Marketplace.LiveTests;
-public class ProductListCommandTests : CommandTestsBase
+public sealed class ProductListCommandTests(ITestOutputHelper output, TestProxyFixture fixture) : RecordedCommandTestsBase(output, fixture)
{
private const string ProductsKey = "products";
private const string Language = "en";
- private readonly MarketplaceService _marketplaceService;
- private readonly ServiceProvider _httpClientProvider;
-
- public ProductListCommandTests(ITestOutputHelper output) : base(output)
- {
- var memoryCache = new MemoryCache(Microsoft.Extensions.Options.Options.Create(new MemoryCacheOptions()));
- var cacheService = new SingleUserCliCacheService(memoryCache);
- var tokenProvider = new SingleIdentityTokenCredentialProvider(NullLoggerFactory.Instance);
- _httpClientProvider = TestHttpClientFactoryProvider.Create();
- var httpClientFactory = _httpClientProvider.GetRequiredService();
- var tenantService = new TenantService(tokenProvider, cacheService, httpClientFactory);
- _marketplaceService = new MarketplaceService(tenantService);
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- _httpClientProvider.Dispose();
- }
-
- base.Dispose(disposing);
- }
[Fact]
public async Task Should_list_marketplace_products()
diff --git a/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/assets.json b/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/assets.json
new file mode 100644
index 0000000000..f2731ac0cb
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Marketplace/tests/Azure.Mcp.Tools.Marketplace.LiveTests/assets.json
@@ -0,0 +1,6 @@
+{
+ "AssetsRepo": "Azure/azure-sdk-assets",
+ "AssetsRepoPrefixPath": "",
+ "TagPrefix": "Azure.Mcp.Tools.Marketplace.LiveTests",
+ "Tag": "Azure.Mcp.Tools.Marketplace.LiveTests_0f59abc9d6"
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/AssemblyInfo.cs b/tools/Azure.Mcp.Tools.StorageSync/src/AssemblyInfo.cs
new file mode 100644
index 0000000000..5229bcb0aa
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Azure.Mcp.Tools.StorageSync.UnitTests")]
+[assembly: InternalsVisibleTo("Azure.Mcp.Tools.StorageSync.LiveTests")]
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Azure.Mcp.Tools.StorageSync.csproj b/tools/Azure.Mcp.Tools.StorageSync/src/Azure.Mcp.Tools.StorageSync.csproj
new file mode 100644
index 0000000000..3a6b7a2e65
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Azure.Mcp.Tools.StorageSync.csproj
@@ -0,0 +1,19 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/BaseStorageSyncCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/BaseStorageSyncCommand.cs
new file mode 100644
index 0000000000..b04dd8b5b5
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/BaseStorageSyncCommand.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics.CodeAnalysis;
+using Azure.Mcp.Core.Commands;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Tools.StorageSync.Options;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands;
+
+///
+/// Base command class for all Storage Sync commands.
+/// Provides common command infrastructure and option registration.
+///
+public abstract class BaseStorageSyncCommand<
+ [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions>
+ : SubscriptionCommand where TOptions : BaseStorageSyncOptions, new()
+{
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ // Additional option registration can be added here for common Storage Sync options
+ }
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointCreateCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointCreateCommand.cs
new file mode 100644
index 0000000000..7caee8389d
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointCreateCommand.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Core.Extensions;
+using Azure.Mcp.Core.Models.Option;
+using Azure.Mcp.Tools.StorageSync.Models;
+using Azure.Mcp.Tools.StorageSync.Options;
+using Azure.Mcp.Tools.StorageSync.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+using Microsoft.Mcp.Core.Models.Option;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands.CloudEndpoint;
+
+public sealed class CloudEndpointCreateCommand(ILogger logger, IStorageSyncService service) : BaseStorageSyncCommand
+{
+ private const string CommandTitle = "Create Cloud Endpoint";
+ private readonly IStorageSyncService _service = service;
+ private readonly ILogger _logger = logger;
+
+ public override string Id => "df0d4ae3-519a-44f1-ad30-d25a0985e9c2";
+
+ public override string Name => "create";
+
+ public override string Description => "Create a new cloud endpoint in a sync group.";
+
+ public override string Title => CommandTitle;
+
+ public override ToolMetadata Metadata => new()
+ {
+ Destructive = true,
+ Idempotent = false,
+ OpenWorld = false,
+ ReadOnly = false,
+ LocalRequired = false,
+ Secret = false
+ };
+
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.StorageSyncService.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.SyncGroup.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.CloudEndpoint.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.CloudEndpoint.StorageAccountResourceId.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.CloudEndpoint.AzureFileShareName.AsRequired());
+ }
+
+ protected override CloudEndpointCreateOptions BindOptions(ParseResult parseResult)
+ {
+ var options = base.BindOptions(parseResult);
+ options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name);
+ options.StorageSyncServiceName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.StorageSyncService.Name.Name);
+ options.SyncGroupName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.SyncGroup.Name.Name);
+ options.CloudEndpointName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.CloudEndpoint.Name.Name);
+ options.StorageAccountResourceId = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.CloudEndpoint.StorageAccountResourceId.Name);
+ options.AzureFileShareName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.CloudEndpoint.AzureFileShareName.Name);
+ return options;
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
+ {
+ if (!Validate(parseResult.CommandResult, context.Response).IsValid)
+ {
+ return context.Response;
+ }
+
+ var options = BindOptions(parseResult);
+
+ try
+ {
+ _logger.LogInformation("Creating cloud endpoint. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}, GroupName: {GroupName}, EndpointName: {EndpointName}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName, options.SyncGroupName, options.CloudEndpointName);
+
+ var endpoint = await _service.CreateCloudEndpointAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.SyncGroupName!,
+ options.CloudEndpointName!,
+ options.StorageAccountResourceId!,
+ options.AzureFileShareName!,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ var results = new CloudEndpointCreateCommandResult(endpoint);
+ context.Response.Results = ResponseResult.Create(results, StorageSyncJsonContext.Default.CloudEndpointCreateCommandResult);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error creating cloud endpoint");
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+
+ [JsonSerializable(typeof(CloudEndpointCreateCommandResult))]
+ internal record CloudEndpointCreateCommandResult(CloudEndpointDataSchema Result);
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointDeleteCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointDeleteCommand.cs
new file mode 100644
index 0000000000..14ae41065b
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointDeleteCommand.cs
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Core.Extensions;
+using Azure.Mcp.Core.Models.Option;
+using Azure.Mcp.Tools.StorageSync.Options;
+using Azure.Mcp.Tools.StorageSync.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+using Microsoft.Mcp.Core.Models.Option;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands.CloudEndpoint;
+
+public sealed class CloudEndpointDeleteCommand(ILogger logger, IStorageSyncService service) : BaseStorageSyncCommand
+{
+ private const string CommandTitle = "Delete Cloud Endpoint";
+ private readonly IStorageSyncService _service = service;
+ private readonly ILogger _logger = logger;
+
+ public override string Id => "f5e76906-cc2a-41a4-b4f9-498221aaaf2e";
+
+ public override string Name => "delete";
+
+ public override string Description => "Delete a cloud endpoint from a sync group.";
+
+ public override string Title => CommandTitle;
+
+ public override ToolMetadata Metadata => new()
+ {
+ Destructive = true,
+ Idempotent = false,
+ OpenWorld = false,
+ ReadOnly = false,
+ LocalRequired = false,
+ Secret = false
+ };
+
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.StorageSyncService.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.SyncGroup.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.CloudEndpoint.Name.AsRequired());
+ }
+
+ protected override CloudEndpointDeleteOptions BindOptions(ParseResult parseResult)
+ {
+ var options = base.BindOptions(parseResult);
+ options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name);
+ options.StorageSyncServiceName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.StorageSyncService.Name.Name);
+ options.SyncGroupName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.SyncGroup.Name.Name);
+ options.CloudEndpointName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.CloudEndpoint.Name.Name);
+ return options;
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
+ {
+ if (!Validate(parseResult.CommandResult, context.Response).IsValid)
+ {
+ return context.Response;
+ }
+
+ var options = BindOptions(parseResult);
+
+ try
+ {
+ _logger.LogInformation("Deleting cloud endpoint. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}, GroupName: {GroupName}, EndpointName: {EndpointName}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName, options.SyncGroupName, options.CloudEndpointName);
+
+ await _service.DeleteCloudEndpointAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.SyncGroupName!,
+ options.CloudEndpointName!,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ context.Response.Message = "Cloud endpoint deleted successfully";
+ var results = new CloudEndpointDeleteCommandResult("Cloud endpoint deleted successfully");
+ context.Response.Results = ResponseResult.Create(results, StorageSyncJsonContext.Default.CloudEndpointDeleteCommandResult);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error deleting cloud endpoint");
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+
+ [JsonSerializable(typeof(CloudEndpointDeleteCommandResult))]
+ internal record CloudEndpointDeleteCommandResult(string Message);
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointGetCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointGetCommand.cs
new file mode 100644
index 0000000000..c1f848455e
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointGetCommand.cs
@@ -0,0 +1,129 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Net;
+using System.Text.Json.Serialization;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Core.Extensions;
+using Azure.Mcp.Core.Models.Option;
+using Azure.Mcp.Tools.StorageSync.Models;
+using Azure.Mcp.Tools.StorageSync.Options;
+using Azure.Mcp.Tools.StorageSync.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+using Microsoft.Mcp.Core.Models.Option;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands.CloudEndpoint;
+
+public sealed class CloudEndpointGetCommand(ILogger logger, IStorageSyncService service) : BaseStorageSyncCommand
+{
+ private const string CommandTitle = "Get Cloud Endpoint";
+ private readonly IStorageSyncService _service = service;
+ private readonly ILogger _logger = logger;
+
+ public override string Id => "25dd8bb3-5ba3-4c0d-993d-54917f63d52e";
+
+ public override string Name => "get";
+
+ public override string Description => "Get details about a specific cloud endpoint or list all cloud endpoints. If --cloud-endpoint-name is provided, returns a specific cloud endpoint; otherwise, lists all cloud endpoints in the sync group.";
+
+ public override string Title => CommandTitle;
+
+ public override ToolMetadata Metadata => new()
+ {
+ Destructive = false,
+ Idempotent = true,
+ OpenWorld = false,
+ ReadOnly = true,
+ LocalRequired = false,
+ Secret = false
+ };
+
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.StorageSyncService.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.SyncGroup.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.CloudEndpoint.Name.AsOptional());
+ }
+
+ protected override CloudEndpointGetOptions BindOptions(ParseResult parseResult)
+ {
+ var options = base.BindOptions(parseResult);
+ options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name);
+ options.StorageSyncServiceName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.StorageSyncService.Name.Name);
+ options.SyncGroupName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.SyncGroup.Name.Name);
+ options.CloudEndpointName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.CloudEndpoint.Name.Name);
+ return options;
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
+ {
+ if (!Validate(parseResult.CommandResult, context.Response).IsValid)
+ {
+ return context.Response;
+ }
+
+ var options = BindOptions(parseResult);
+
+ try
+ {
+ // If cloud endpoint name is provided, get specific endpoint
+ if (!string.IsNullOrEmpty(options.CloudEndpointName))
+ {
+ _logger.LogInformation("Getting cloud endpoint. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}, GroupName: {GroupName}, EndpointName: {EndpointName}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName, options.SyncGroupName, options.CloudEndpointName);
+
+ var endpoint = await _service.GetCloudEndpointAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.SyncGroupName!,
+ options.CloudEndpointName!,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ if (endpoint == null)
+ {
+ context.Response.Status = HttpStatusCode.NotFound;
+ context.Response.Message = "Cloud endpoint not found";
+ return context.Response;
+ }
+
+ var singleResult = new CloudEndpointGetCommandResult([endpoint]);
+ context.Response.Results = ResponseResult.Create(singleResult, StorageSyncJsonContext.Default.CloudEndpointGetCommandResult);
+ }
+ else
+ {
+ // List all cloud endpoints
+ _logger.LogInformation("Listing cloud endpoints. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}, GroupName: {GroupName}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName, options.SyncGroupName);
+
+ var endpoints = await _service.ListCloudEndpointsAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.SyncGroupName!,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ var results = new CloudEndpointGetCommandResult(endpoints ?? []);
+ context.Response.Results = ResponseResult.Create(results, StorageSyncJsonContext.Default.CloudEndpointGetCommandResult);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error getting cloud endpoint(s)");
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+
+ [JsonSerializable(typeof(CloudEndpointGetCommandResult))]
+ internal record CloudEndpointGetCommandResult(List Results);
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointTriggerChangeDetectionCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointTriggerChangeDetectionCommand.cs
new file mode 100644
index 0000000000..4a3214c906
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/CloudEndpoint/CloudEndpointTriggerChangeDetectionCommand.cs
@@ -0,0 +1,102 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Core.Extensions;
+using Azure.Mcp.Core.Models.Option;
+using Azure.Mcp.Tools.StorageSync.Options;
+using Azure.Mcp.Tools.StorageSync.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+using Microsoft.Mcp.Core.Models.Option;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands.CloudEndpoint;
+
+public sealed class CloudEndpointTriggerChangeDetectionCommand(ILogger logger, IStorageSyncService service) : BaseStorageSyncCommand
+{
+ private const string CommandTitle = "Trigger Change Detection";
+ private readonly IStorageSyncService _service = service;
+ private readonly ILogger _logger = logger;
+
+ public override string Id => "96f096a2-d36f-4361-aa74-4e393e7f48a5";
+
+ public override string Name => "triggerchangedetection";
+
+ public override string Description => "Trigger change detection on a cloud endpoint to sync file changes.";
+
+ public override string Title => CommandTitle;
+
+ public override ToolMetadata Metadata => new()
+ {
+ Destructive = false,
+ Idempotent = false,
+ OpenWorld = false,
+ ReadOnly = false,
+ LocalRequired = false,
+ Secret = false
+ };
+
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.StorageSyncService.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.SyncGroup.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.CloudEndpoint.Name.AsRequired());
+ }
+
+ protected override CloudEndpointTriggerChangeDetectionOptions BindOptions(ParseResult parseResult)
+ {
+ var options = base.BindOptions(parseResult);
+ options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name);
+ options.StorageSyncServiceName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.StorageSyncService.Name.Name);
+ options.SyncGroupName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.SyncGroup.Name.Name);
+ options.CloudEndpointName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.CloudEndpoint.Name.Name);
+ return options;
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
+ {
+ if (!Validate(parseResult.CommandResult, context.Response).IsValid)
+ {
+ return context.Response;
+ }
+
+ var options = BindOptions(parseResult);
+
+ try
+ {
+ _logger.LogInformation("Triggering change detection. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}, GroupName: {GroupName}, EndpointName: {EndpointName}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName, options.SyncGroupName, options.CloudEndpointName);
+
+ await _service.TriggerChangeDetectionAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.SyncGroupName!,
+ options.CloudEndpointName!,
+ null,
+ null,
+ false,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ context.Response.Message = "Change detection triggered successfully";
+ var results = new CloudEndpointTriggerChangeDetectionCommandResult("Change detection triggered successfully");
+ context.Response.Results = ResponseResult.Create(results, StorageSyncJsonContext.Default.CloudEndpointTriggerChangeDetectionCommandResult);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error triggering change detection");
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+
+ [JsonSerializable(typeof(CloudEndpointTriggerChangeDetectionCommandResult))]
+ internal record CloudEndpointTriggerChangeDetectionCommandResult(string Message);
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/RegisteredServer/RegisteredServerGetCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/RegisteredServer/RegisteredServerGetCommand.cs
new file mode 100644
index 0000000000..f098aea1ca
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/RegisteredServer/RegisteredServerGetCommand.cs
@@ -0,0 +1,125 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Net;
+using System.Text.Json.Serialization;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Core.Extensions;
+using Azure.Mcp.Core.Models.Option;
+using Azure.Mcp.Tools.StorageSync.Models;
+using Azure.Mcp.Tools.StorageSync.Options;
+using Azure.Mcp.Tools.StorageSync.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+using Microsoft.Mcp.Core.Models.Option;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands.RegisteredServer;
+
+public sealed class RegisteredServerGetCommand(ILogger logger, IStorageSyncService service) : BaseStorageSyncCommand
+{
+ private const string CommandTitle = "Get Registered Server";
+ private readonly IStorageSyncService _service = service;
+ private readonly ILogger _logger = logger;
+
+ public override string Id => "fe3b07c3-9a11-465e-bfb6-6461b85b2e52";
+
+ public override string Name => "get";
+
+ public override string Description => "Get details about a specific registered server or list all registered servers. If --server-id is provided, returns a specific registered server; otherwise, lists all registered servers in the Storage Sync Service.";
+
+ public override string Title => CommandTitle;
+
+ public override ToolMetadata Metadata => new()
+ {
+ Destructive = false,
+ Idempotent = true,
+ OpenWorld = false,
+ ReadOnly = true,
+ LocalRequired = false,
+ Secret = false
+ };
+
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.StorageSyncService.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.RegisteredServer.ServerId.AsOptional());
+ }
+
+ protected override RegisteredServerGetOptions BindOptions(ParseResult parseResult)
+ {
+ var options = base.BindOptions(parseResult);
+ options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name);
+ options.StorageSyncServiceName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.StorageSyncService.Name.Name);
+ options.RegisteredServerId = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.RegisteredServer.ServerId.Name);
+ return options;
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
+ {
+ if (!Validate(parseResult.CommandResult, context.Response).IsValid)
+ {
+ return context.Response;
+ }
+
+ var options = BindOptions(parseResult);
+
+ try
+ {
+ // If server ID is provided, get specific server
+ if (!string.IsNullOrEmpty(options.RegisteredServerId))
+ {
+ _logger.LogInformation("Getting registered server. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}, ServerId: {ServerId}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName, options.RegisteredServerId);
+
+ var server = await _service.GetRegisteredServerAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.RegisteredServerId!,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ if (server == null)
+ {
+ context.Response.Status = HttpStatusCode.NotFound;
+ context.Response.Message = "Registered server not found";
+ return context.Response;
+ }
+
+ var singleResult = new RegisteredServerGetCommandResult([server]);
+ context.Response.Results = ResponseResult.Create(singleResult, StorageSyncJsonContext.Default.RegisteredServerGetCommandResult);
+ }
+ else
+ {
+ // List all registered servers
+ _logger.LogInformation("Listing registered servers. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName);
+
+ var servers = await _service.ListRegisteredServersAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ var results = new RegisteredServerGetCommandResult(servers ?? []);
+ context.Response.Results = ResponseResult.Create(results, StorageSyncJsonContext.Default.RegisteredServerGetCommandResult);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error getting registered server(s)");
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+
+ [JsonSerializable(typeof(RegisteredServerGetCommandResult))]
+ internal record RegisteredServerGetCommandResult(List Results);
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/RegisteredServer/RegisteredServerUnregisterCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/RegisteredServer/RegisteredServerUnregisterCommand.cs
new file mode 100644
index 0000000000..8e0b086154
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/RegisteredServer/RegisteredServerUnregisterCommand.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Core.Extensions;
+using Azure.Mcp.Core.Models.Option;
+using Azure.Mcp.Tools.StorageSync.Options;
+using Azure.Mcp.Tools.StorageSync.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+using Microsoft.Mcp.Core.Models.Option;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands.RegisteredServer;
+
+public sealed class RegisteredServerUnregisterCommand(ILogger logger, IStorageSyncService service) : BaseStorageSyncCommand
+{
+ private const string CommandTitle = "Unregister Server";
+ private readonly IStorageSyncService _service = service;
+ private readonly ILogger _logger = logger;
+
+ public override string Id => "346661e1-64be-463a-96c6-3626966f55fa";
+
+ public override string Name => "unregister";
+
+ public override string Description => "Unregister a server from a Storage Sync service.";
+
+ public override string Title => CommandTitle;
+
+ public override ToolMetadata Metadata => new()
+ {
+ Destructive = true,
+ Idempotent = false,
+ OpenWorld = false,
+ ReadOnly = false,
+ LocalRequired = false,
+ Secret = false
+ };
+
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.StorageSyncService.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.RegisteredServer.ServerId.AsRequired());
+ }
+
+ protected override RegisteredServerUnregisterOptions BindOptions(ParseResult parseResult)
+ {
+ var options = base.BindOptions(parseResult);
+ options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name);
+ options.StorageSyncServiceName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.StorageSyncService.Name.Name);
+ options.RegisteredServerId = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.RegisteredServer.ServerId.Name);
+ return options;
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
+ {
+ if (!Validate(parseResult.CommandResult, context.Response).IsValid)
+ {
+ return context.Response;
+ }
+
+ var options = BindOptions(parseResult);
+
+ try
+ {
+ _logger.LogInformation("Unregistering server. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}, ServerId: {ServerId}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName, options.RegisteredServerId);
+
+ await _service.UnregisterServerAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.RegisteredServerId!,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ context.Response.Message = "Server unregistered successfully";
+ var results = new RegisteredServerUnregisterCommandResult("Server unregistered successfully");
+ context.Response.Results = ResponseResult.Create(results, StorageSyncJsonContext.Default.RegisteredServerUnregisterCommandResult);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error unregistering server");
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+
+ [JsonSerializable(typeof(RegisteredServerUnregisterCommandResult))]
+ internal record RegisteredServerUnregisterCommandResult(string Message);
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/RegisteredServer/RegisteredServerUpdateCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/RegisteredServer/RegisteredServerUpdateCommand.cs
new file mode 100644
index 0000000000..8a6a4d254a
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/RegisteredServer/RegisteredServerUpdateCommand.cs
@@ -0,0 +1,97 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Core.Extensions;
+using Azure.Mcp.Core.Models.Option;
+using Azure.Mcp.Tools.StorageSync.Models;
+using Azure.Mcp.Tools.StorageSync.Options;
+using Azure.Mcp.Tools.StorageSync.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+using Microsoft.Mcp.Core.Models.Option;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands.RegisteredServer;
+
+public sealed class RegisteredServerUpdateCommand(ILogger logger, IStorageSyncService service) : BaseStorageSyncCommand
+{
+ private const string CommandTitle = "Update Registered Server";
+ private readonly IStorageSyncService _service = service;
+ private readonly ILogger _logger = logger;
+
+ public override string Id => "c443ed00-f17f-46a8-a5d3-df128aa1606b";
+
+ public override string Name => "update";
+
+ public override string Description => "Update properties of a registered server.";
+
+ public override string Title => CommandTitle;
+
+ public override ToolMetadata Metadata => new()
+ {
+ Destructive = false,
+ Idempotent = false,
+ OpenWorld = false,
+ ReadOnly = false,
+ LocalRequired = false,
+ Secret = false
+ };
+
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.StorageSyncService.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.RegisteredServer.ServerId.AsRequired());
+ }
+
+ protected override RegisteredServerUpdateOptions BindOptions(ParseResult parseResult)
+ {
+ var options = base.BindOptions(parseResult);
+ options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name);
+ options.StorageSyncServiceName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.StorageSyncService.Name.Name);
+ options.RegisteredServerId = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.RegisteredServer.ServerId.Name);
+ return options;
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
+ {
+ if (!Validate(parseResult.CommandResult, context.Response).IsValid)
+ {
+ return context.Response;
+ }
+
+ var options = BindOptions(parseResult);
+
+ try
+ {
+ _logger.LogInformation("Updating registered server. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}, ServerId: {ServerId}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName, options.RegisteredServerId);
+
+ var server = await _service.UpdateServerAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.RegisteredServerId!,
+ null,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ var results = new RegisteredServerUpdateCommandResult(server);
+ context.Response.Results = ResponseResult.Create(results, StorageSyncJsonContext.Default.RegisteredServerUpdateCommandResult);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error updating registered server");
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+
+ [JsonSerializable(typeof(RegisteredServerUpdateCommandResult))]
+ internal record RegisteredServerUpdateCommandResult(RegisteredServerDataSchema Result);
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/ServerEndpoint/ServerEndpointCreateCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/ServerEndpoint/ServerEndpointCreateCommand.cs
new file mode 100644
index 0000000000..fc0189a053
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/ServerEndpoint/ServerEndpointCreateCommand.cs
@@ -0,0 +1,108 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Core.Extensions;
+using Azure.Mcp.Core.Models.Option;
+using Azure.Mcp.Tools.StorageSync.Models;
+using Azure.Mcp.Tools.StorageSync.Options;
+using Azure.Mcp.Tools.StorageSync.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+using Microsoft.Mcp.Core.Models.Option;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands.ServerEndpoint;
+
+public sealed class ServerEndpointCreateCommand(ILogger logger, IStorageSyncService service) : BaseStorageSyncCommand
+{
+ private const string CommandTitle = "Create Server Endpoint";
+ private readonly IStorageSyncService _service = service;
+ private readonly ILogger _logger = logger;
+
+ public override string Id => "fcbdf461-6fde-4cfb-a944-4a56a2be90e4";
+
+ public override string Name => "create";
+
+ public override string Description => "Create a new server endpoint in a sync group.";
+
+ public override string Title => CommandTitle;
+
+ public override ToolMetadata Metadata => new()
+ {
+ Destructive = true,
+ Idempotent = false,
+ OpenWorld = false,
+ ReadOnly = false,
+ LocalRequired = false,
+ Secret = false
+ };
+
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.StorageSyncService.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.SyncGroup.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.ServerEndpoint.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.ServerEndpoint.ServerResourceId.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.ServerEndpoint.ServerLocalPath.AsRequired());
+ }
+
+ protected override ServerEndpointCreateOptions BindOptions(ParseResult parseResult)
+ {
+ var options = base.BindOptions(parseResult);
+ options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name);
+ options.StorageSyncServiceName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.StorageSyncService.Name.Name);
+ options.SyncGroupName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.SyncGroup.Name.Name);
+ options.ServerEndpointName = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.ServerEndpoint.Name.Name);
+ options.ServerResourceId = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.ServerEndpoint.ServerResourceId.Name);
+ options.ServerLocalPath = parseResult.GetValueOrDefault(StorageSyncOptionDefinitions.ServerEndpoint.ServerLocalPath.Name);
+ return options;
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
+ {
+ if (!Validate(parseResult.CommandResult, context.Response).IsValid)
+ {
+ return context.Response;
+ }
+
+ var options = BindOptions(parseResult);
+
+ try
+ {
+ _logger.LogInformation("Creating server endpoint. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ServiceName: {ServiceName}, GroupName: {GroupName}, EndpointName: {EndpointName}",
+ options.Subscription, options.ResourceGroup, options.StorageSyncServiceName, options.SyncGroupName, options.ServerEndpointName);
+
+ var endpoint = await _service.CreateServerEndpointAsync(
+ options.Subscription!,
+ options.ResourceGroup!,
+ options.StorageSyncServiceName!,
+ options.SyncGroupName!,
+ options.ServerEndpointName!,
+ options.ServerResourceId!,
+ options.ServerLocalPath!,
+ false,
+ null,
+ null,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ var results = new ServerEndpointCreateCommandResult(endpoint);
+ context.Response.Results = ResponseResult.Create(results, StorageSyncJsonContext.Default.ServerEndpointCreateCommandResult);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error creating server endpoint");
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+
+ [JsonSerializable(typeof(ServerEndpointCreateCommandResult))]
+ internal record ServerEndpointCreateCommandResult(ServerEndpointDataSchema Result);
+}
diff --git a/tools/Azure.Mcp.Tools.StorageSync/src/Commands/ServerEndpoint/ServerEndpointDeleteCommand.cs b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/ServerEndpoint/ServerEndpointDeleteCommand.cs
new file mode 100644
index 0000000000..175870480c
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.StorageSync/src/Commands/ServerEndpoint/ServerEndpointDeleteCommand.cs
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+using Azure.Mcp.Core.Commands.Subscription;
+using Azure.Mcp.Core.Extensions;
+using Azure.Mcp.Core.Models.Option;
+using Azure.Mcp.Tools.StorageSync.Options;
+using Azure.Mcp.Tools.StorageSync.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+using Microsoft.Mcp.Core.Models.Option;
+
+namespace Azure.Mcp.Tools.StorageSync.Commands.ServerEndpoint;
+
+public sealed class ServerEndpointDeleteCommand(ILogger logger, IStorageSyncService service) : BaseStorageSyncCommand
+{
+ private const string CommandTitle = "Delete Server Endpoint";
+ private readonly IStorageSyncService _service = service;
+ private readonly ILogger _logger = logger;
+
+ public override string Id => "ef6c2aa9-bb64-4f94-b18b-018e04b504c9";
+
+ public override string Name => "delete";
+
+ public override string Description => "Delete a server endpoint from a sync group.";
+
+ public override string Title => CommandTitle;
+
+ public override ToolMetadata Metadata => new()
+ {
+ Destructive = true,
+ Idempotent = false,
+ OpenWorld = false,
+ ReadOnly = false,
+ LocalRequired = false,
+ Secret = false
+ };
+
+ protected override void RegisterOptions(Command command)
+ {
+ base.RegisterOptions(command);
+ command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.StorageSyncService.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.SyncGroup.Name.AsRequired());
+ command.Options.Add(StorageSyncOptionDefinitions.ServerEndpoint.Name.AsRequired());
+ }
+
+ protected override ServerEndpointDeleteOptions BindOptions(ParseResult parseResult)
+ {
+ var options = base.BindOptions(parseResult);
+ options.ResourceGroup ??= parseResult.GetValueOrDefault