From 20655d2b46165d2dedf40f3dc0dca1324b25962e Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Tue, 5 May 2026 12:48:34 -0700 Subject: [PATCH 01/21] Add DataFactory.MCP.Core as submodule reference in Fabric.Mcp.Server - Add DataFactory.MCP as git submodule at external/DataFactory.MCP - Add project reference using $(RepoRoot) for portability - Add Apache.Arrow 22.1.0 to Directory.Packages.props (only new dependency needed) - Microsoft.Identity.Client and Microsoft.Extensions.Http already satisfied transitively - Build verified: 0 warnings, 0 errors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitmodules | 4 ++++ CONTRIBUTING.md | 15 ++++++++++----- Directory.Packages.props | 1 + external/DataFactory.MCP | 1 + .../src/Fabric.Mcp.Server.csproj | 2 ++ 5 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 .gitmodules create mode 160000 external/DataFactory.MCP diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..2f79051cf6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "external/DataFactory.MCP"] + path = external/DataFactory.MCP + url = https://github.com/microsoft/DataFactory.MCP.git + branch = fabric-mcp-integration diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 182511d8ed..ed8d185667 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,6 +92,9 @@ If you are contributing significant changes, or if the issue is already assigned - `{server}.Mcp.Tools.{tool-name}.UnitTests/` - Unit tests require no authentication or test resources - `{server}.Mcp.Tools.{tool-name}.LiveTests/` - Live tests depend on Azure resources and authentication - `test-resources.bicep` - Infrastructure templates for testing +- `external/` - Git submodules for external project integrations + - `DataFactory.MCP` - [DataFactory.MCP](https://github.com/microsoft/DataFactory.MCP) submodule (used by `Fabric.Mcp.Server`) + - Run `git submodule update --init --recursive` after cloning to fetch submodule contents - `eng/` - Shared tools, templates, CLI helpers - `docs/` - Central documentation and onboarding materials @@ -100,11 +103,13 @@ If you are contributing significant changes, or if the issue is already assigned ### Development Process 1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Write or update tests -5. Test locally -6. Submit a pull request +2. Clone with submodules: `git clone --recurse-submodules ` + - If already cloned, run: `git submodule update --init --recursive` +3. Create a feature branch +4. Make your changes +5. Write or update tests +6. Test locally +7. Submit a pull request ### Adding a New Command diff --git a/Directory.Packages.props b/Directory.Packages.props index 530f3816a8..6150468cfa 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,6 +3,7 @@ true + diff --git a/external/DataFactory.MCP b/external/DataFactory.MCP new file mode 160000 index 0000000000..950dc5cbe6 --- /dev/null +++ b/external/DataFactory.MCP @@ -0,0 +1 @@ +Subproject commit 950dc5cbe6c68f3ed4de3773a365fe109d8d6dfa diff --git a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj index 742efddecc..578f9e1840 100644 --- a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj +++ b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj @@ -69,6 +69,8 @@ + + From 64ed49ed352338f54a1ebed998cf3acbbf51f00a Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 12:08:17 -0700 Subject: [PATCH 02/21] =?UTF-8?q?Update=20DataFactory=20submodule=20ref=20?= =?UTF-8?q?after=20Core=E2=86=92Fabric.Mcp.Tools.DataFactory=20rename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update submodule pointer and project reference to match the renamed project in DataFactory.MCP repo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/DataFactory.MCP | 2 +- servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/external/DataFactory.MCP b/external/DataFactory.MCP index 950dc5cbe6..a2654d628f 160000 --- a/external/DataFactory.MCP +++ b/external/DataFactory.MCP @@ -1 +1 @@ -Subproject commit 950dc5cbe6c68f3ed4de3773a365fe109d8d6dfa +Subproject commit a2654d628f05e5fe079b04f8440351b73db14e99 diff --git a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj index 578f9e1840..69b276b098 100644 --- a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj +++ b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj @@ -69,8 +69,8 @@ - - + + From d84da2cfa09df5099940b8ffa41fddfbc3f059d9 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 12:41:28 -0700 Subject: [PATCH 03/21] Rename submodule folder from DataFactory.MCP to Fabric.Mcp.Tools.DataFactory Rename the external submodule path for consistency with Fabric naming conventions. Updates .gitmodules, project reference, and docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitmodules | 6 +- CONTRIBUTING.md | 2 +- external/DataFactory.MCP | 1 - external/Fabric.Mcp.Tools.DataFactory | 1 + .../src/Fabric.Mcp.Server.csproj | 2 - servers/Fabric.Mcp.Server/src/Program.cs | 1 + .../src/Commands/PipelineCreateCommand.cs | 110 +++++++++++++++++ .../src/Commands/PipelineGetCommand.cs | 98 ++++++++++++++++ .../src/Commands/PipelineListCommand.cs | 111 ++++++++++++++++++ .../src/Commands/PipelineRunCommand.cs | 96 +++++++++++++++ .../src/Commands/WorkspaceListCommand.cs | 110 +++++++++++++++++ .../src/Fabric.Mcp.Tools.DataFactory.csproj | 14 +++ .../src/FabricDataFactorySetup.cs | 55 +++++++++ .../src/GlobalUsings.cs | 8 ++ .../src/Models/DataFactoryJsonContext.cs | 21 ++++ .../Options/DataFactoryOptionDefinitions.cs | 58 +++++++++ .../Services/NullUserNotificationService.cs | 18 +++ 17 files changed, 705 insertions(+), 7 deletions(-) delete mode 160000 external/DataFactory.MCP create mode 160000 external/Fabric.Mcp.Tools.DataFactory create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineCreateCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineGetCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineListCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineRunCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/WorkspaceListCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/FabricDataFactorySetup.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Services/NullUserNotificationService.cs diff --git a/.gitmodules b/.gitmodules index 2f79051cf6..d0be985560 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ -[submodule "external/DataFactory.MCP"] - path = external/DataFactory.MCP +[submodule "external/Fabric.Mcp.Tools.DataFactory"] + path = external/Fabric.Mcp.Tools.DataFactory url = https://github.com/microsoft/DataFactory.MCP.git - branch = fabric-mcp-integration + branch = fabric-mcp-tools-integration diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed8d185667..e840416b8c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,7 +93,7 @@ If you are contributing significant changes, or if the issue is already assigned - `{server}.Mcp.Tools.{tool-name}.LiveTests/` - Live tests depend on Azure resources and authentication - `test-resources.bicep` - Infrastructure templates for testing - `external/` - Git submodules for external project integrations - - `DataFactory.MCP` - [DataFactory.MCP](https://github.com/microsoft/DataFactory.MCP) submodule (used by `Fabric.Mcp.Server`) + - `Fabric.Mcp.Tools.DataFactory` - [DataFactory.MCP](https://github.com/microsoft/DataFactory.MCP) submodule (used by `Fabric.Mcp.Server`) - Run `git submodule update --init --recursive` after cloning to fetch submodule contents - `eng/` - Shared tools, templates, CLI helpers - `docs/` - Central documentation and onboarding materials diff --git a/external/DataFactory.MCP b/external/DataFactory.MCP deleted file mode 160000 index a2654d628f..0000000000 --- a/external/DataFactory.MCP +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a2654d628f05e5fe079b04f8440351b73db14e99 diff --git a/external/Fabric.Mcp.Tools.DataFactory b/external/Fabric.Mcp.Tools.DataFactory new file mode 160000 index 0000000000..51f7747ad4 --- /dev/null +++ b/external/Fabric.Mcp.Tools.DataFactory @@ -0,0 +1 @@ +Subproject commit 51f7747ad4e00fb6b4c844f450e1b6e3f5bf57c7 diff --git a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj index 69b276b098..742efddecc 100644 --- a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj +++ b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj @@ -69,8 +69,6 @@ - - diff --git a/servers/Fabric.Mcp.Server/src/Program.cs b/servers/Fabric.Mcp.Server/src/Program.cs index e4c34d5a97..8024e8cf4f 100644 --- a/servers/Fabric.Mcp.Server/src/Program.cs +++ b/servers/Fabric.Mcp.Server/src/Program.cs @@ -77,6 +77,7 @@ private static IAreaSetup[] RegisterAreas() new Fabric.Mcp.Tools.Docs.FabricDocsSetup(), new Fabric.Mcp.Tools.OneLake.FabricOneLakeSetup(), new Fabric.Mcp.Tools.Core.FabricCoreSetup(), + new Fabric.Mcp.Tools.DataFactory.FabricDataFactorySetup(), ]; } diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineCreateCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineCreateCommand.cs new file mode 100644 index 0000000000..c52d13978f --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineCreateCommand.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Models.Pipeline; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands; + +[CommandMetadata( + Id = "12da19c8-48e2-46de-8a0e-055080744315", + Name = "create_pipeline", + Title = "Create Pipeline", + Description = "Creates a new data pipeline in a specified Microsoft Fabric workspace. Requires a workspace ID, display name, and optional description. Use this to set up new data orchestration workflows.", + Destructive = false, + Idempotent = false, + OpenWorld = false, + ReadOnly = false, + Secret = false, + LocalRequired = false)] +public sealed class PipelineCreateCommand( + ILogger logger, + IFabricPipelineService pipelineService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.DisplayName.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.Description.AsOptional()); + command.Options.Add(DataFactoryOptionDefinitions.FolderId.AsOptional()); + } + + protected override PipelineCreateOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceId.Name) ?? string.Empty; + options.DisplayName = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.DisplayName.Name) ?? string.Empty; + options.Description = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.Description.Name); + options.FolderId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.FolderId.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 + { + var request = new CreatePipelineRequest + { + DisplayName = options.DisplayName, + Description = options.Description, + FolderId = options.FolderId + }; + + var response = await _pipelineService.CreatePipelineAsync(options.WorkspaceId, request); + + _logger.LogInformation("Successfully created pipeline '{DisplayName}' in workspace {WorkspaceId}", + options.DisplayName, options.WorkspaceId); + + var result = new PipelineCreateCommandResult + { + PipelineId = response.Id, + DisplayName = response.DisplayName, + Description = response.Description, + WorkspaceId = response.WorkspaceId + }; + + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.PipelineCreateCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating pipeline '{DisplayName}' in workspace {WorkspaceId}.", + options.DisplayName, options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } + + public sealed class PipelineCreateOptions : GlobalOptions + { + public string WorkspaceId { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + public string? Description { get; set; } + public string? FolderId { get; set; } + } + + public sealed class PipelineCreateCommandResult + { + public string PipelineId { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + public string? Description { get; set; } + public string WorkspaceId { get; set; } = string.Empty; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineGetCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineGetCommand.cs new file mode 100644 index 0000000000..8a28fe2316 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineGetCommand.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands; + +[CommandMetadata( + Id = "c761e25e-0bb7-4a70-a920-dcb31daa0edf", + Name = "get_pipeline", + Title = "Get Pipeline", + Description = "Gets the metadata of a specific data pipeline by its ID from a Microsoft Fabric workspace. Returns pipeline details including name, description, and type.", + Destructive = false, + Idempotent = true, + OpenWorld = false, + ReadOnly = true, + Secret = false, + LocalRequired = false)] +public sealed class PipelineGetCommand( + ILogger logger, + IFabricPipelineService pipelineService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.PipelineId.AsRequired()); + } + + protected override PipelineGetOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceId.Name) ?? string.Empty; + options.PipelineId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.PipelineId.Name) ?? string.Empty; + 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 + { + var pipeline = await _pipelineService.GetPipelineAsync(options.WorkspaceId, options.PipelineId); + + _logger.LogInformation("Retrieved pipeline '{PipelineId}' from workspace {WorkspaceId}", + options.PipelineId, options.WorkspaceId); + + var result = new PipelineGetCommandResult + { + Id = pipeline.Id, + DisplayName = pipeline.DisplayName, + Description = pipeline.Description, + Type = pipeline.Type, + WorkspaceId = pipeline.WorkspaceId + }; + + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.PipelineGetCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting pipeline '{PipelineId}' from workspace {WorkspaceId}.", + options.PipelineId, options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } + + public sealed class PipelineGetOptions : GlobalOptions + { + public string WorkspaceId { get; set; } = string.Empty; + public string PipelineId { get; set; } = string.Empty; + } + + public sealed class PipelineGetCommandResult + { + public string Id { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + public string? Description { get; set; } + public string Type { get; set; } = string.Empty; + public string WorkspaceId { get; set; } = string.Empty; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineListCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineListCommand.cs new file mode 100644 index 0000000000..927723c93f --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineListCommand.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands; + +[CommandMetadata( + Id = "ce83c7e5-da1a-4a97-bfdd-8366981ebf75", + Name = "list_pipelines", + Title = "List Pipelines", + Description = "Lists all data pipelines in a specified Microsoft Fabric workspace. Returns pipeline names, IDs, and metadata. Use this to discover available pipelines before running or inspecting them.", + Destructive = false, + Idempotent = true, + OpenWorld = false, + ReadOnly = true, + Secret = false, + LocalRequired = false)] +public sealed class PipelineListCommand( + ILogger logger, + IFabricPipelineService pipelineService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.ContinuationToken.AsOptional()); + } + + protected override PipelineListOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceId.Name) ?? string.Empty; + options.ContinuationToken = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.ContinuationToken.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 + { + var response = await _pipelineService.ListPipelinesAsync( + options.WorkspaceId, + options.ContinuationToken); + + _logger.LogInformation("Retrieved {Count} pipelines from workspace {WorkspaceId}", + response.Value.Count, options.WorkspaceId); + + var result = new PipelineListCommandResult + { + Pipelines = response.Value.Select(p => new PipelineInfo + { + Id = p.Id, + DisplayName = p.DisplayName, + Description = p.Description, + Type = p.Type, + WorkspaceId = p.WorkspaceId + }).ToList(), + ContinuationToken = response.ContinuationToken, + TotalCount = response.Value.Count + }; + + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.PipelineListCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error listing pipelines in workspace {WorkspaceId}.", options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } + + public sealed class PipelineListOptions : GlobalOptions + { + public string WorkspaceId { get; set; } = string.Empty; + public string? ContinuationToken { get; set; } + } + + public sealed class PipelineListCommandResult + { + public List Pipelines { get; set; } = []; + public string? ContinuationToken { get; set; } + public int TotalCount { get; set; } + } + + public sealed class PipelineInfo + { + public string Id { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + public string? Description { get; set; } + public string Type { get; set; } = string.Empty; + public string WorkspaceId { get; set; } = string.Empty; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineRunCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineRunCommand.cs new file mode 100644 index 0000000000..483b8c9869 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineRunCommand.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands; + +[CommandMetadata( + Id = "f68fdd4e-49e5-4a75-a794-a169fcf60d95", + Name = "run_pipeline", + Title = "Run Pipeline", + Description = "Runs a data pipeline on demand in a Microsoft Fabric workspace. Returns a tracking URL for monitoring the pipeline execution status. Use this to trigger pipeline runs programmatically.", + Destructive = false, + Idempotent = false, + OpenWorld = false, + ReadOnly = false, + Secret = false, + LocalRequired = false)] +public sealed class PipelineRunCommand( + ILogger logger, + IFabricPipelineService pipelineService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.PipelineId.AsRequired()); + } + + protected override PipelineRunOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceId.Name) ?? string.Empty; + options.PipelineId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.PipelineId.Name) ?? string.Empty; + 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 + { + var locationUrl = await _pipelineService.RunPipelineAsync(options.WorkspaceId, options.PipelineId); + + _logger.LogInformation("Pipeline '{PipelineId}' run initiated in workspace {WorkspaceId}", + options.PipelineId, options.WorkspaceId); + + var result = new PipelineRunCommandResult + { + PipelineId = options.PipelineId, + WorkspaceId = options.WorkspaceId, + LocationUrl = locationUrl, + Message = "Pipeline run initiated successfully." + }; + + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.PipelineRunCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error running pipeline '{PipelineId}' in workspace {WorkspaceId}.", + options.PipelineId, options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } + + public sealed class PipelineRunOptions : GlobalOptions + { + public string WorkspaceId { get; set; } = string.Empty; + public string PipelineId { get; set; } = string.Empty; + } + + public sealed class PipelineRunCommandResult + { + public string PipelineId { get; set; } = string.Empty; + public string WorkspaceId { get; set; } = string.Empty; + public string? LocationUrl { get; set; } + public string Message { get; set; } = string.Empty; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/WorkspaceListCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/WorkspaceListCommand.cs new file mode 100644 index 0000000000..8434542b26 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/WorkspaceListCommand.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands; + +[CommandMetadata( + Id = "6cafae77-8092-49bf-980b-da2393775d96", + Name = "list_workspaces", + Title = "List Workspaces", + Description = "Lists all Microsoft Fabric workspaces the user has permission for. Returns workspaces filtered by the specified roles if provided. Use this when you need to find workspace IDs for other Data Factory operations.", + Destructive = false, + Idempotent = true, + OpenWorld = false, + ReadOnly = true, + Secret = false, + LocalRequired = false)] +public sealed class WorkspaceListCommand( + ILogger logger, + IFabricWorkspaceService workspaceService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricWorkspaceService _workspaceService = workspaceService ?? throw new ArgumentNullException(nameof(workspaceService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.Roles.AsOptional()); + command.Options.Add(DataFactoryOptionDefinitions.ContinuationToken.AsOptional()); + } + + protected override WorkspaceListOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.Roles = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.Roles.Name); + options.ContinuationToken = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.ContinuationToken.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 + { + var response = await _workspaceService.ListWorkspacesAsync( + options.Roles, + options.ContinuationToken); + + _logger.LogInformation("Retrieved {Count} workspaces", response.Value.Count); + + var result = new WorkspaceListCommandResult + { + Workspaces = response.Value.Select(w => new WorkspaceInfo + { + Id = w.Id, + DisplayName = w.DisplayName, + Description = w.Description, + Type = w.Type.ToString(), + CapacityId = w.CapacityId + }).ToList(), + ContinuationToken = response.ContinuationToken, + TotalCount = response.Value.Count + }; + + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.WorkspaceListCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error listing workspaces."); + HandleException(context, ex); + } + + return context.Response; + } + + public sealed class WorkspaceListOptions : GlobalOptions + { + public string? Roles { get; set; } + public string? ContinuationToken { get; set; } + } + + public sealed class WorkspaceListCommandResult + { + public List Workspaces { get; set; } = []; + public string? ContinuationToken { get; set; } + public int TotalCount { get; set; } + } + + public sealed class WorkspaceInfo + { + public string Id { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public string? CapacityId { get; set; } + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj b/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj new file mode 100644 index 0000000000..ddba968f95 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj @@ -0,0 +1,14 @@ + + + true + + + + + + + + + + + diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/FabricDataFactorySetup.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/FabricDataFactorySetup.cs new file mode 100644 index 0000000000..32c8f9f69d --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/FabricDataFactorySetup.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Extensions; +using Fabric.Mcp.Tools.DataFactory.Commands; +using Fabric.Mcp.Tools.DataFactory.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Mcp.Core.Areas; +using Microsoft.Mcp.Core.Commands; + +namespace Fabric.Mcp.Tools.DataFactory; + +public class FabricDataFactorySetup : IAreaSetup +{ + public string Name => "datafactory"; + public string Title => "Microsoft Fabric Data Factory"; + + public void ConfigureServices(IServiceCollection services) + { + // Register no-op notification service required by DataFactory.MCP.Core + services.AddSingleton(); + + // Register all DataFactory core services (auth, HTTP clients, pipeline/workspace/etc. services) + services.AddDataFactoryMcpServices(); + + // Register command wrappers + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + } + + public CommandGroup RegisterCommands(IServiceProvider serviceProvider) + { + var dataFactory = new CommandGroup(Name, + """ + Microsoft Fabric Data Factory Operations - Manage Data Factory pipelines and workspaces. + Use this tool when you need to: + - List and manage Fabric workspaces + - Create, view, and run data pipelines + - Monitor pipeline execution status + This tool provides operations for working with Data Factory resources within your Fabric tenant. + """); + + dataFactory.AddCommand(serviceProvider); + dataFactory.AddCommand(serviceProvider); + dataFactory.AddCommand(serviceProvider); + dataFactory.AddCommand(serviceProvider); + dataFactory.AddCommand(serviceProvider); + + return dataFactory; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs new file mode 100644 index 0000000000..c1f7b0c9bc --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +global using System.CommandLine; +global using System.CommandLine.Parsing; +global using System.Text.Json; +global using System.Text.Json.Serialization; +global using Microsoft.Mcp.Core.Models.Command; diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs new file mode 100644 index 0000000000..1443713eaa --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using Fabric.Mcp.Tools.DataFactory.Commands; + +namespace Fabric.Mcp.Tools.DataFactory.Models; + +[JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(WorkspaceListCommand.WorkspaceListCommandResult))] +[JsonSerializable(typeof(WorkspaceListCommand.WorkspaceInfo))] +[JsonSerializable(typeof(PipelineListCommand.PipelineListCommandResult))] +[JsonSerializable(typeof(PipelineListCommand.PipelineInfo))] +[JsonSerializable(typeof(PipelineCreateCommand.PipelineCreateCommandResult))] +[JsonSerializable(typeof(PipelineGetCommand.PipelineGetCommandResult))] +[JsonSerializable(typeof(PipelineRunCommand.PipelineRunCommandResult))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(List))] +internal partial class DataFactoryJsonContext : JsonSerializerContext; diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs new file mode 100644 index 0000000000..d9e4e24951 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.CommandLine; + +namespace Fabric.Mcp.Tools.DataFactory.Options; + +public static class DataFactoryOptionDefinitions +{ + public const string WorkspaceIdName = "workspace-id"; + public static readonly Option WorkspaceId = new($"--{WorkspaceIdName}") + { + Description = "The ID of the Microsoft Fabric workspace.", + Required = true + }; + + public const string PipelineIdName = "pipeline-id"; + public static readonly Option PipelineId = new($"--{PipelineIdName}") + { + Description = "The ID of the pipeline.", + Required = true + }; + + public const string DisplayNameName = "display-name"; + public static readonly Option DisplayName = new($"--{DisplayNameName}") + { + Description = "The display name for the item.", + Required = true + }; + + public const string DescriptionOptionName = "description"; + public static readonly Option Description = new($"--{DescriptionOptionName}") + { + Description = "The description for the item.", + Required = false + }; + + public const string RolesName = "roles"; + public static readonly Option Roles = new($"--{RolesName}") + { + Description = "A list of roles to filter by (e.g., 'Admin,Member,Contributor,Viewer').", + Required = false + }; + + public const string ContinuationTokenName = "continuation-token"; + public static readonly Option ContinuationToken = new($"--{ContinuationTokenName}") + { + Description = "A token for retrieving the next page of results.", + Required = false + }; + + public const string FolderIdName = "folder-id"; + public static readonly Option FolderId = new($"--{FolderIdName}") + { + Description = "The folder ID where the item will be created (defaults to workspace root).", + Required = false + }; +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Services/NullUserNotificationService.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Services/NullUserNotificationService.cs new file mode 100644 index 0000000000..5bf0b2fb6a --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Services/NullUserNotificationService.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; + +namespace Fabric.Mcp.Tools.DataFactory.Services; + +/// +/// No-op implementation of IUserNotificationService for the Fabric MCP server integration. +/// The Fabric MCP server handles notifications through its own infrastructure. +/// +internal sealed class NullUserNotificationService : IUserNotificationService +{ + public Task NotifyAsync(string title, string message, NotificationLevel level = NotificationLevel.Info) + { + return Task.CompletedTask; + } +} From c3e349f532ebd4f789567d89601ae92a533afb3e Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 14:04:27 -0700 Subject: [PATCH 04/21] Update DataFactory submodule to include Fabric adapter project The submodule now includes DataFactory.MCP.Fabric which provides IAreaSetup + GlobalCommand wrappers for direct Fabric MCP Server integration without needing a separate wrapper project in this repo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/Fabric.Mcp.Tools.DataFactory | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/Fabric.Mcp.Tools.DataFactory b/external/Fabric.Mcp.Tools.DataFactory index 51f7747ad4..0843119e8e 160000 --- a/external/Fabric.Mcp.Tools.DataFactory +++ b/external/Fabric.Mcp.Tools.DataFactory @@ -1 +1 @@ -Subproject commit 51f7747ad4e00fb6b4c844f450e1b6e3f5bf57c7 +Subproject commit 0843119e8eb48bb16f89d7efb329b5540301dd17 From a9cc7e0d3badeb189160327921d5fc04b58a9d20 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 14:07:31 -0700 Subject: [PATCH 05/21] Remove redundant wrapper; use DataFactory.MCP.Fabric adapter from submodule The DataFactory.MCP.Fabric adapter project now lives in the DataFactory repo itself (submodule), eliminating the need for a separate wrapper project in this repo. Changes: - Delete tools/Fabric.Mcp.Tools.DataFactory/ (redundant wrapper) - Add explicit ProjectReference to submodule's DataFactory.MCP.Fabric - Update Program.cs to use DataFactory.MCP.Fabric.DataFactoryAreaSetup - Fix submodule csproj with conditional paths for Microsoft.Mcp.Core Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/Fabric.Mcp.Tools.DataFactory | 2 +- .../src/Fabric.Mcp.Server.csproj | 1 + servers/Fabric.Mcp.Server/src/Program.cs | 2 +- .../src/Commands/PipelineCreateCommand.cs | 110 ----------------- .../src/Commands/PipelineGetCommand.cs | 98 ---------------- .../src/Commands/PipelineListCommand.cs | 111 ------------------ .../src/Commands/PipelineRunCommand.cs | 96 --------------- .../src/Commands/WorkspaceListCommand.cs | 110 ----------------- .../src/Fabric.Mcp.Tools.DataFactory.csproj | 14 --- .../src/FabricDataFactorySetup.cs | 55 --------- .../src/GlobalUsings.cs | 8 -- .../src/Models/DataFactoryJsonContext.cs | 21 ---- .../Options/DataFactoryOptionDefinitions.cs | 58 --------- .../Services/NullUserNotificationService.cs | 18 --- 14 files changed, 3 insertions(+), 701 deletions(-) delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineCreateCommand.cs delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineGetCommand.cs delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineListCommand.cs delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineRunCommand.cs delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/WorkspaceListCommand.cs delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/FabricDataFactorySetup.cs delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs delete mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Services/NullUserNotificationService.cs diff --git a/external/Fabric.Mcp.Tools.DataFactory b/external/Fabric.Mcp.Tools.DataFactory index 0843119e8e..87c04aa85c 160000 --- a/external/Fabric.Mcp.Tools.DataFactory +++ b/external/Fabric.Mcp.Tools.DataFactory @@ -1 +1 @@ -Subproject commit 0843119e8eb48bb16f89d7efb329b5540301dd17 +Subproject commit 87c04aa85c78aaa9da19198995f78d4f83a4a797 diff --git a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj index 742efddecc..64e6065915 100644 --- a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj +++ b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj @@ -69,6 +69,7 @@ + diff --git a/servers/Fabric.Mcp.Server/src/Program.cs b/servers/Fabric.Mcp.Server/src/Program.cs index 8024e8cf4f..3839336480 100644 --- a/servers/Fabric.Mcp.Server/src/Program.cs +++ b/servers/Fabric.Mcp.Server/src/Program.cs @@ -77,7 +77,7 @@ private static IAreaSetup[] RegisterAreas() new Fabric.Mcp.Tools.Docs.FabricDocsSetup(), new Fabric.Mcp.Tools.OneLake.FabricOneLakeSetup(), new Fabric.Mcp.Tools.Core.FabricCoreSetup(), - new Fabric.Mcp.Tools.DataFactory.FabricDataFactorySetup(), + new DataFactory.MCP.Fabric.DataFactoryAreaSetup(), ]; } diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineCreateCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineCreateCommand.cs deleted file mode 100644 index c52d13978f..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineCreateCommand.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using DataFactory.MCP.Models.Pipeline; -using Fabric.Mcp.Tools.DataFactory.Models; -using Fabric.Mcp.Tools.DataFactory.Options; -using Microsoft.Extensions.Logging; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; -using Microsoft.Mcp.Core.Models.Option; -using Microsoft.Mcp.Core.Options; - -namespace Fabric.Mcp.Tools.DataFactory.Commands; - -[CommandMetadata( - Id = "12da19c8-48e2-46de-8a0e-055080744315", - Name = "create_pipeline", - Title = "Create Pipeline", - Description = "Creates a new data pipeline in a specified Microsoft Fabric workspace. Requires a workspace ID, display name, and optional description. Use this to set up new data orchestration workflows.", - Destructive = false, - Idempotent = false, - OpenWorld = false, - ReadOnly = false, - Secret = false, - LocalRequired = false)] -public sealed class PipelineCreateCommand( - ILogger logger, - IFabricPipelineService pipelineService) : GlobalCommand() -{ - private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); - command.Options.Add(DataFactoryOptionDefinitions.DisplayName.AsRequired()); - command.Options.Add(DataFactoryOptionDefinitions.Description.AsOptional()); - command.Options.Add(DataFactoryOptionDefinitions.FolderId.AsOptional()); - } - - protected override PipelineCreateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceId.Name) ?? string.Empty; - options.DisplayName = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.DisplayName.Name) ?? string.Empty; - options.Description = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.Description.Name); - options.FolderId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.FolderId.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 - { - var request = new CreatePipelineRequest - { - DisplayName = options.DisplayName, - Description = options.Description, - FolderId = options.FolderId - }; - - var response = await _pipelineService.CreatePipelineAsync(options.WorkspaceId, request); - - _logger.LogInformation("Successfully created pipeline '{DisplayName}' in workspace {WorkspaceId}", - options.DisplayName, options.WorkspaceId); - - var result = new PipelineCreateCommandResult - { - PipelineId = response.Id, - DisplayName = response.DisplayName, - Description = response.Description, - WorkspaceId = response.WorkspaceId - }; - - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.PipelineCreateCommandResult); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error creating pipeline '{DisplayName}' in workspace {WorkspaceId}.", - options.DisplayName, options.WorkspaceId); - HandleException(context, ex); - } - - return context.Response; - } - - public sealed class PipelineCreateOptions : GlobalOptions - { - public string WorkspaceId { get; set; } = string.Empty; - public string DisplayName { get; set; } = string.Empty; - public string? Description { get; set; } - public string? FolderId { get; set; } - } - - public sealed class PipelineCreateCommandResult - { - public string PipelineId { get; set; } = string.Empty; - public string DisplayName { get; set; } = string.Empty; - public string? Description { get; set; } - public string WorkspaceId { get; set; } = string.Empty; - } -} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineGetCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineGetCommand.cs deleted file mode 100644 index 8a28fe2316..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineGetCommand.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using Fabric.Mcp.Tools.DataFactory.Models; -using Fabric.Mcp.Tools.DataFactory.Options; -using Microsoft.Extensions.Logging; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; -using Microsoft.Mcp.Core.Models.Option; -using Microsoft.Mcp.Core.Options; - -namespace Fabric.Mcp.Tools.DataFactory.Commands; - -[CommandMetadata( - Id = "c761e25e-0bb7-4a70-a920-dcb31daa0edf", - Name = "get_pipeline", - Title = "Get Pipeline", - Description = "Gets the metadata of a specific data pipeline by its ID from a Microsoft Fabric workspace. Returns pipeline details including name, description, and type.", - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - Secret = false, - LocalRequired = false)] -public sealed class PipelineGetCommand( - ILogger logger, - IFabricPipelineService pipelineService) : GlobalCommand() -{ - private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); - command.Options.Add(DataFactoryOptionDefinitions.PipelineId.AsRequired()); - } - - protected override PipelineGetOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceId.Name) ?? string.Empty; - options.PipelineId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.PipelineId.Name) ?? string.Empty; - 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 - { - var pipeline = await _pipelineService.GetPipelineAsync(options.WorkspaceId, options.PipelineId); - - _logger.LogInformation("Retrieved pipeline '{PipelineId}' from workspace {WorkspaceId}", - options.PipelineId, options.WorkspaceId); - - var result = new PipelineGetCommandResult - { - Id = pipeline.Id, - DisplayName = pipeline.DisplayName, - Description = pipeline.Description, - Type = pipeline.Type, - WorkspaceId = pipeline.WorkspaceId - }; - - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.PipelineGetCommandResult); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting pipeline '{PipelineId}' from workspace {WorkspaceId}.", - options.PipelineId, options.WorkspaceId); - HandleException(context, ex); - } - - return context.Response; - } - - public sealed class PipelineGetOptions : GlobalOptions - { - public string WorkspaceId { get; set; } = string.Empty; - public string PipelineId { get; set; } = string.Empty; - } - - public sealed class PipelineGetCommandResult - { - public string Id { get; set; } = string.Empty; - public string DisplayName { get; set; } = string.Empty; - public string? Description { get; set; } - public string Type { get; set; } = string.Empty; - public string WorkspaceId { get; set; } = string.Empty; - } -} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineListCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineListCommand.cs deleted file mode 100644 index 927723c93f..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineListCommand.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using Fabric.Mcp.Tools.DataFactory.Models; -using Fabric.Mcp.Tools.DataFactory.Options; -using Microsoft.Extensions.Logging; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; -using Microsoft.Mcp.Core.Models.Option; -using Microsoft.Mcp.Core.Options; - -namespace Fabric.Mcp.Tools.DataFactory.Commands; - -[CommandMetadata( - Id = "ce83c7e5-da1a-4a97-bfdd-8366981ebf75", - Name = "list_pipelines", - Title = "List Pipelines", - Description = "Lists all data pipelines in a specified Microsoft Fabric workspace. Returns pipeline names, IDs, and metadata. Use this to discover available pipelines before running or inspecting them.", - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - Secret = false, - LocalRequired = false)] -public sealed class PipelineListCommand( - ILogger logger, - IFabricPipelineService pipelineService) : GlobalCommand() -{ - private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); - command.Options.Add(DataFactoryOptionDefinitions.ContinuationToken.AsOptional()); - } - - protected override PipelineListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceId.Name) ?? string.Empty; - options.ContinuationToken = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.ContinuationToken.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 - { - var response = await _pipelineService.ListPipelinesAsync( - options.WorkspaceId, - options.ContinuationToken); - - _logger.LogInformation("Retrieved {Count} pipelines from workspace {WorkspaceId}", - response.Value.Count, options.WorkspaceId); - - var result = new PipelineListCommandResult - { - Pipelines = response.Value.Select(p => new PipelineInfo - { - Id = p.Id, - DisplayName = p.DisplayName, - Description = p.Description, - Type = p.Type, - WorkspaceId = p.WorkspaceId - }).ToList(), - ContinuationToken = response.ContinuationToken, - TotalCount = response.Value.Count - }; - - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.PipelineListCommandResult); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error listing pipelines in workspace {WorkspaceId}.", options.WorkspaceId); - HandleException(context, ex); - } - - return context.Response; - } - - public sealed class PipelineListOptions : GlobalOptions - { - public string WorkspaceId { get; set; } = string.Empty; - public string? ContinuationToken { get; set; } - } - - public sealed class PipelineListCommandResult - { - public List Pipelines { get; set; } = []; - public string? ContinuationToken { get; set; } - public int TotalCount { get; set; } - } - - public sealed class PipelineInfo - { - public string Id { get; set; } = string.Empty; - public string DisplayName { get; set; } = string.Empty; - public string? Description { get; set; } - public string Type { get; set; } = string.Empty; - public string WorkspaceId { get; set; } = string.Empty; - } -} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineRunCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineRunCommand.cs deleted file mode 100644 index 483b8c9869..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/PipelineRunCommand.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using Fabric.Mcp.Tools.DataFactory.Models; -using Fabric.Mcp.Tools.DataFactory.Options; -using Microsoft.Extensions.Logging; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; -using Microsoft.Mcp.Core.Models.Option; -using Microsoft.Mcp.Core.Options; - -namespace Fabric.Mcp.Tools.DataFactory.Commands; - -[CommandMetadata( - Id = "f68fdd4e-49e5-4a75-a794-a169fcf60d95", - Name = "run_pipeline", - Title = "Run Pipeline", - Description = "Runs a data pipeline on demand in a Microsoft Fabric workspace. Returns a tracking URL for monitoring the pipeline execution status. Use this to trigger pipeline runs programmatically.", - Destructive = false, - Idempotent = false, - OpenWorld = false, - ReadOnly = false, - Secret = false, - LocalRequired = false)] -public sealed class PipelineRunCommand( - ILogger logger, - IFabricPipelineService pipelineService) : GlobalCommand() -{ - private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); - command.Options.Add(DataFactoryOptionDefinitions.PipelineId.AsRequired()); - } - - protected override PipelineRunOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceId.Name) ?? string.Empty; - options.PipelineId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.PipelineId.Name) ?? string.Empty; - 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 - { - var locationUrl = await _pipelineService.RunPipelineAsync(options.WorkspaceId, options.PipelineId); - - _logger.LogInformation("Pipeline '{PipelineId}' run initiated in workspace {WorkspaceId}", - options.PipelineId, options.WorkspaceId); - - var result = new PipelineRunCommandResult - { - PipelineId = options.PipelineId, - WorkspaceId = options.WorkspaceId, - LocationUrl = locationUrl, - Message = "Pipeline run initiated successfully." - }; - - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.PipelineRunCommandResult); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error running pipeline '{PipelineId}' in workspace {WorkspaceId}.", - options.PipelineId, options.WorkspaceId); - HandleException(context, ex); - } - - return context.Response; - } - - public sealed class PipelineRunOptions : GlobalOptions - { - public string WorkspaceId { get; set; } = string.Empty; - public string PipelineId { get; set; } = string.Empty; - } - - public sealed class PipelineRunCommandResult - { - public string PipelineId { get; set; } = string.Empty; - public string WorkspaceId { get; set; } = string.Empty; - public string? LocationUrl { get; set; } - public string Message { get; set; } = string.Empty; - } -} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/WorkspaceListCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/WorkspaceListCommand.cs deleted file mode 100644 index 8434542b26..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/WorkspaceListCommand.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using Fabric.Mcp.Tools.DataFactory.Models; -using Fabric.Mcp.Tools.DataFactory.Options; -using Microsoft.Extensions.Logging; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; -using Microsoft.Mcp.Core.Models.Option; -using Microsoft.Mcp.Core.Options; - -namespace Fabric.Mcp.Tools.DataFactory.Commands; - -[CommandMetadata( - Id = "6cafae77-8092-49bf-980b-da2393775d96", - Name = "list_workspaces", - Title = "List Workspaces", - Description = "Lists all Microsoft Fabric workspaces the user has permission for. Returns workspaces filtered by the specified roles if provided. Use this when you need to find workspace IDs for other Data Factory operations.", - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - Secret = false, - LocalRequired = false)] -public sealed class WorkspaceListCommand( - ILogger logger, - IFabricWorkspaceService workspaceService) : GlobalCommand() -{ - private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricWorkspaceService _workspaceService = workspaceService ?? throw new ArgumentNullException(nameof(workspaceService)); - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(DataFactoryOptionDefinitions.Roles.AsOptional()); - command.Options.Add(DataFactoryOptionDefinitions.ContinuationToken.AsOptional()); - } - - protected override WorkspaceListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Roles = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.Roles.Name); - options.ContinuationToken = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.ContinuationToken.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 - { - var response = await _workspaceService.ListWorkspacesAsync( - options.Roles, - options.ContinuationToken); - - _logger.LogInformation("Retrieved {Count} workspaces", response.Value.Count); - - var result = new WorkspaceListCommandResult - { - Workspaces = response.Value.Select(w => new WorkspaceInfo - { - Id = w.Id, - DisplayName = w.DisplayName, - Description = w.Description, - Type = w.Type.ToString(), - CapacityId = w.CapacityId - }).ToList(), - ContinuationToken = response.ContinuationToken, - TotalCount = response.Value.Count - }; - - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.WorkspaceListCommandResult); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error listing workspaces."); - HandleException(context, ex); - } - - return context.Response; - } - - public sealed class WorkspaceListOptions : GlobalOptions - { - public string? Roles { get; set; } - public string? ContinuationToken { get; set; } - } - - public sealed class WorkspaceListCommandResult - { - public List Workspaces { get; set; } = []; - public string? ContinuationToken { get; set; } - public int TotalCount { get; set; } - } - - public sealed class WorkspaceInfo - { - public string Id { get; set; } = string.Empty; - public string DisplayName { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public string Type { get; set; } = string.Empty; - public string? CapacityId { get; set; } - } -} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj b/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj deleted file mode 100644 index ddba968f95..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - true - - - - - - - - - - - diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/FabricDataFactorySetup.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/FabricDataFactorySetup.cs deleted file mode 100644 index 32c8f9f69d..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/FabricDataFactorySetup.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using DataFactory.MCP.Extensions; -using Fabric.Mcp.Tools.DataFactory.Commands; -using Fabric.Mcp.Tools.DataFactory.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Areas; -using Microsoft.Mcp.Core.Commands; - -namespace Fabric.Mcp.Tools.DataFactory; - -public class FabricDataFactorySetup : IAreaSetup -{ - public string Name => "datafactory"; - public string Title => "Microsoft Fabric Data Factory"; - - public void ConfigureServices(IServiceCollection services) - { - // Register no-op notification service required by DataFactory.MCP.Core - services.AddSingleton(); - - // Register all DataFactory core services (auth, HTTP clients, pipeline/workspace/etc. services) - services.AddDataFactoryMcpServices(); - - // Register command wrappers - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - } - - public CommandGroup RegisterCommands(IServiceProvider serviceProvider) - { - var dataFactory = new CommandGroup(Name, - """ - Microsoft Fabric Data Factory Operations - Manage Data Factory pipelines and workspaces. - Use this tool when you need to: - - List and manage Fabric workspaces - - Create, view, and run data pipelines - - Monitor pipeline execution status - This tool provides operations for working with Data Factory resources within your Fabric tenant. - """); - - dataFactory.AddCommand(serviceProvider); - dataFactory.AddCommand(serviceProvider); - dataFactory.AddCommand(serviceProvider); - dataFactory.AddCommand(serviceProvider); - dataFactory.AddCommand(serviceProvider); - - return dataFactory; - } -} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs deleted file mode 100644 index c1f7b0c9bc..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -global using System.CommandLine; -global using System.CommandLine.Parsing; -global using System.Text.Json; -global using System.Text.Json.Serialization; -global using Microsoft.Mcp.Core.Models.Command; diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs deleted file mode 100644 index 1443713eaa..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Fabric.Mcp.Tools.DataFactory.Commands; - -namespace Fabric.Mcp.Tools.DataFactory.Models; - -[JsonSourceGenerationOptions( - PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] -[JsonSerializable(typeof(WorkspaceListCommand.WorkspaceListCommandResult))] -[JsonSerializable(typeof(WorkspaceListCommand.WorkspaceInfo))] -[JsonSerializable(typeof(PipelineListCommand.PipelineListCommandResult))] -[JsonSerializable(typeof(PipelineListCommand.PipelineInfo))] -[JsonSerializable(typeof(PipelineCreateCommand.PipelineCreateCommandResult))] -[JsonSerializable(typeof(PipelineGetCommand.PipelineGetCommandResult))] -[JsonSerializable(typeof(PipelineRunCommand.PipelineRunCommandResult))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(List))] -internal partial class DataFactoryJsonContext : JsonSerializerContext; diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs deleted file mode 100644 index d9e4e24951..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.CommandLine; - -namespace Fabric.Mcp.Tools.DataFactory.Options; - -public static class DataFactoryOptionDefinitions -{ - public const string WorkspaceIdName = "workspace-id"; - public static readonly Option WorkspaceId = new($"--{WorkspaceIdName}") - { - Description = "The ID of the Microsoft Fabric workspace.", - Required = true - }; - - public const string PipelineIdName = "pipeline-id"; - public static readonly Option PipelineId = new($"--{PipelineIdName}") - { - Description = "The ID of the pipeline.", - Required = true - }; - - public const string DisplayNameName = "display-name"; - public static readonly Option DisplayName = new($"--{DisplayNameName}") - { - Description = "The display name for the item.", - Required = true - }; - - public const string DescriptionOptionName = "description"; - public static readonly Option Description = new($"--{DescriptionOptionName}") - { - Description = "The description for the item.", - Required = false - }; - - public const string RolesName = "roles"; - public static readonly Option Roles = new($"--{RolesName}") - { - Description = "A list of roles to filter by (e.g., 'Admin,Member,Contributor,Viewer').", - Required = false - }; - - public const string ContinuationTokenName = "continuation-token"; - public static readonly Option ContinuationToken = new($"--{ContinuationTokenName}") - { - Description = "A token for retrieving the next page of results.", - Required = false - }; - - public const string FolderIdName = "folder-id"; - public static readonly Option FolderId = new($"--{FolderIdName}") - { - Description = "The folder ID where the item will be created (defaults to workspace root).", - Required = false - }; -} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Services/NullUserNotificationService.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Services/NullUserNotificationService.cs deleted file mode 100644 index 5bf0b2fb6a..0000000000 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Services/NullUserNotificationService.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; - -namespace Fabric.Mcp.Tools.DataFactory.Services; - -/// -/// No-op implementation of IUserNotificationService for the Fabric MCP server integration. -/// The Fabric MCP server handles notifications through its own infrastructure. -/// -internal sealed class NullUserNotificationService : IUserNotificationService -{ - public Task NotifyAsync(string title, string message, NotificationLevel level = NotificationLevel.Info) - { - return Task.CompletedTask; - } -} From 53b45a392e3f9ce10085277ae7e866b7c7303859 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 15:32:59 -0700 Subject: [PATCH 06/21] Move DataFactory.MCP.Fabric to tools/Fabric.Mcp.Tools.DataFactory - Copy source files from external submodule to tools/ folder - Update namespaces from DataFactory.MCP.Fabric to Fabric.Mcp.Tools.DataFactory - Add global:: prefix for DataFactory.MCP.Core namespace references - Remove explicit ProjectReference to external csproj (wildcard covers it) - Update Program.cs to use new namespace - Add project to solution file - Update submodule to latest commit --- external/Fabric.Mcp.Tools.DataFactory | 2 +- .../Fabric.Mcp.Server/Fabric.Mcp.Server.slnx | 4 + .../src/Fabric.Mcp.Server.csproj | 1 - servers/Fabric.Mcp.Server/src/Program.cs | 2 +- .../Dataflow/CreateDataflowCommand.cs | 95 +++++++++++++++++++ .../Commands/Dataflow/ListDataflowsCommand.cs | 73 ++++++++++++++ .../Pipeline/CreatePipelineCommand.cs | 95 +++++++++++++++++++ .../Commands/Pipeline/GetPipelineCommand.cs | 75 +++++++++++++++ .../Commands/Pipeline/ListPipelinesCommand.cs | 72 ++++++++++++++ .../Commands/Pipeline/RunPipelineCommand.cs | 75 +++++++++++++++ .../src/DataFactoryAreaSetup.cs | 52 ++++++++++ .../src/Fabric.Mcp.Tools.DataFactory.csproj | 14 +++ .../src/GlobalUsings.cs | 3 + .../src/Models/DataFactoryJsonContext.cs | 27 ++++++ .../Options/DataFactoryOptionDefinitions.cs | 44 +++++++++ .../Options/Dataflow/CreateDataflowOptions.cs | 13 +++ .../Options/Dataflow/ListDataflowsOptions.cs | 11 +++ .../Options/Pipeline/CreatePipelineOptions.cs | 13 +++ .../Options/Pipeline/GetPipelineOptions.cs | 12 +++ .../Options/Pipeline/ListPipelinesOptions.cs | 11 +++ .../Options/Pipeline/RunPipelineOptions.cs | 12 +++ 21 files changed, 703 insertions(+), 3 deletions(-) create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/CreateDataflowOptions.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/ListDataflowsOptions.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/CreatePipelineOptions.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/GetPipelineOptions.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/ListPipelinesOptions.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/RunPipelineOptions.cs diff --git a/external/Fabric.Mcp.Tools.DataFactory b/external/Fabric.Mcp.Tools.DataFactory index 87c04aa85c..0b25987b01 160000 --- a/external/Fabric.Mcp.Tools.DataFactory +++ b/external/Fabric.Mcp.Tools.DataFactory @@ -1 +1 @@ -Subproject commit 87c04aa85c78aaa9da19198995f78d4f83a4a797 +Subproject commit 0b25987b018f6928d53d9998277f42114a742087 diff --git a/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx b/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx index 94e105e8a0..7af191a04b 100644 --- a/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx +++ b/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx @@ -42,4 +42,8 @@ + + + + diff --git a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj index 64e6065915..742efddecc 100644 --- a/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj +++ b/servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj @@ -69,7 +69,6 @@ - diff --git a/servers/Fabric.Mcp.Server/src/Program.cs b/servers/Fabric.Mcp.Server/src/Program.cs index 3839336480..86bc125420 100644 --- a/servers/Fabric.Mcp.Server/src/Program.cs +++ b/servers/Fabric.Mcp.Server/src/Program.cs @@ -77,7 +77,7 @@ private static IAreaSetup[] RegisterAreas() new Fabric.Mcp.Tools.Docs.FabricDocsSetup(), new Fabric.Mcp.Tools.OneLake.FabricOneLakeSetup(), new Fabric.Mcp.Tools.Core.FabricCoreSetup(), - new DataFactory.MCP.Fabric.DataFactoryAreaSetup(), + new Fabric.Mcp.Tools.DataFactory.DataFactoryAreaSetup(), ]; } diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs new file mode 100644 index 0000000000..3ced96f3c8 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using global::DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Fabric.Mcp.Tools.DataFactory.Options.Dataflow; +using global::DataFactory.MCP.Models.Dataflow; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Command; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; + +[CommandMetadata( + Id = "a1b2c3d4-2001-4000-8000-000000000002", + Name = "create-dataflow", + Title = "Create Dataflow", + Description = "Creates a new dataflow in a specified Microsoft Fabric workspace.", + Destructive = false, + Idempotent = false, + ReadOnly = false, + OpenWorld = false)] +public sealed class CreateDataflowCommand( + ILogger logger, + IFabricDataflowService dataflowService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricDataflowService _dataflowService = dataflowService ?? throw new ArgumentNullException(nameof(dataflowService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.DisplayName.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.Description.AsOptional()); + } + + protected override CreateDataflowOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceIdName) ?? string.Empty; + options.DisplayName = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.DisplayNameName) ?? string.Empty; + options.Description = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.DescriptionName); + 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 + { + var request = new CreateDataflowRequest + { + DisplayName = options.DisplayName, + Description = options.Description + }; + + var response = await _dataflowService.CreateDataflowAsync(options.WorkspaceId, request); + + _logger.LogInformation("Successfully created dataflow '{DisplayName}' in workspace {WorkspaceId}", + options.DisplayName, options.WorkspaceId); + + // Map CreateDataflowResponse to Dataflow for the result + var dataflow = new global::DataFactory.MCP.Models.Dataflow.Dataflow + { + Id = response.Id, + DisplayName = response.DisplayName, + Description = response.Description, + Type = response.Type, + WorkspaceId = response.WorkspaceId, + FolderId = response.FolderId + }; + + var result = new CreateDataflowCommandResult(dataflow); + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.CreateDataflowCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating dataflow '{DisplayName}' in workspace {WorkspaceId}", + options.DisplayName, options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs new file mode 100644 index 0000000000..d207642079 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using global::DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Fabric.Mcp.Tools.DataFactory.Options.Dataflow; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Command; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; + +[CommandMetadata( + Id = "a1b2c3d4-2001-4000-8000-000000000001", + Name = "list-dataflows", + Title = "List Dataflows", + Description = "Lists all dataflows in a specified Microsoft Fabric workspace.", + Destructive = false, + Idempotent = true, + ReadOnly = true, + OpenWorld = false)] +public sealed class ListDataflowsCommand( + ILogger logger, + IFabricDataflowService dataflowService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricDataflowService _dataflowService = dataflowService ?? throw new ArgumentNullException(nameof(dataflowService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + } + + protected override ListDataflowsOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceIdName) ?? string.Empty; + 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 + { + var response = await _dataflowService.ListDataflowsAsync(options.WorkspaceId); + + _logger.LogInformation("Successfully listed {Count} dataflows in workspace {WorkspaceId}", + response.Value.Count, options.WorkspaceId); + + var commandResult = new ListDataflowsCommandResult(response.Value, response.Value.Count); + context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.ListDataflowsCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error listing dataflows in workspace {WorkspaceId}", options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs new file mode 100644 index 0000000000..e5176ec527 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using global::DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Fabric.Mcp.Tools.DataFactory.Options.Pipeline; +using global::DataFactory.MCP.Models.Pipeline; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Command; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; + +[CommandMetadata( + Id = "a1b2c3d4-1001-4000-8000-000000000003", + Name = "create-pipeline", + Title = "Create Pipeline", + Description = "Creates a new pipeline in a Microsoft Fabric workspace. Requires workspace ID and display name. Optionally provide a description.", + Destructive = false, + Idempotent = false, + ReadOnly = false, + OpenWorld = false)] +public sealed class CreatePipelineCommand( + ILogger logger, + IFabricPipelineService pipelineService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.DisplayName.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.Description.AsOptional()); + } + + protected override CreatePipelineOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceIdName) ?? string.Empty; + options.DisplayName = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.DisplayNameName) ?? string.Empty; + options.Description = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.DescriptionName); + 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 + { + var request = new CreatePipelineRequest + { + DisplayName = options.DisplayName, + Description = options.Description + }; + + var response = await _pipelineService.CreatePipelineAsync(options.WorkspaceId, request); + + _logger.LogInformation("Successfully created pipeline '{DisplayName}' in workspace {WorkspaceId}", + options.DisplayName, options.WorkspaceId); + + // Map CreatePipelineResponse to Pipeline for the result + var pipeline = new global::DataFactory.MCP.Models.Pipeline.Pipeline + { + Id = response.Id, + DisplayName = response.DisplayName, + Description = response.Description, + Type = response.Type, + WorkspaceId = response.WorkspaceId, + FolderId = response.FolderId + }; + + var result = new CreatePipelineCommandResult(pipeline); + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.CreatePipelineCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating pipeline '{DisplayName}' in workspace {WorkspaceId}", + options.DisplayName, options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs new file mode 100644 index 0000000000..bc9c2128b2 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using global::DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Fabric.Mcp.Tools.DataFactory.Options.Pipeline; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Command; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; + +[CommandMetadata( + Id = "a1b2c3d4-1001-4000-8000-000000000004", + Name = "get-pipeline", + Title = "Get Pipeline", + Description = "Gets details of a specific pipeline in a Microsoft Fabric workspace. Requires workspace ID and pipeline ID.", + Destructive = false, + Idempotent = true, + ReadOnly = true, + OpenWorld = false)] +public sealed class GetPipelineCommand( + ILogger logger, + IFabricPipelineService pipelineService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.PipelineId.AsRequired()); + } + + protected override GetPipelineOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceIdName) ?? string.Empty; + options.PipelineId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.PipelineIdName) ?? string.Empty; + 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 + { + var pipeline = await _pipelineService.GetPipelineAsync(options.WorkspaceId, options.PipelineId); + + _logger.LogInformation("Successfully retrieved pipeline {PipelineId} from workspace {WorkspaceId}", + options.PipelineId, options.WorkspaceId); + + var result = new GetPipelineCommandResult(pipeline); + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.GetPipelineCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting pipeline {PipelineId} from workspace {WorkspaceId}", + options.PipelineId, options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs new file mode 100644 index 0000000000..7939d1673c --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Fabric.Mcp.Tools.DataFactory.Options.Pipeline; +using global::DataFactory.MCP.Handlers.Pipeline; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Command; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; + +[CommandMetadata( + Id = "a1b2c3d4-1001-4000-8000-000000000002", + Name = "list-pipelines", + Title = "List Pipelines", + Description = "Lists all pipelines in a specified Microsoft Fabric workspace. Requires the workspace ID.", + Destructive = false, + Idempotent = true, + ReadOnly = true, + OpenWorld = false)] +public sealed class ListPipelinesCommand( + ILogger logger, + ListPipelinesHandler handler) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly ListPipelinesHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + } + + protected override ListPipelinesOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceIdName) ?? string.Empty; + 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); + + var result = await _handler.ExecuteAsync(options.WorkspaceId); + if (result.IsSuccess) + { + _logger.LogInformation("Successfully listed {Count} pipelines in workspace {WorkspaceId}", + result.Value!.PipelineCount, options.WorkspaceId); + + var commandResult = new ListPipelinesCommandResult(result.Value.RawPipelines, result.Value.PipelineCount); + context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.ListPipelinesCommandResult); + } + else + { + _logger.LogError("Error listing pipelines in workspace {WorkspaceId}: {Error}", options.WorkspaceId, result.Error); + HandleException(context, new Exception(result.Error)); + } + + return context.Response; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs new file mode 100644 index 0000000000..78118ef0f9 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using global::DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Fabric.Mcp.Tools.DataFactory.Options.Pipeline; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Command; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; + +[CommandMetadata( + Id = "a1b2c3d4-1001-4000-8000-000000000005", + Name = "run-pipeline", + Title = "Run Pipeline", + Description = "Triggers a run of a specified pipeline in a Microsoft Fabric workspace. Requires workspace ID and pipeline ID. Returns the run instance ID.", + Destructive = false, + Idempotent = false, + ReadOnly = false, + OpenWorld = false)] +public sealed class RunPipelineCommand( + ILogger logger, + IFabricPipelineService pipelineService) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.PipelineId.AsRequired()); + } + + protected override RunPipelineOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceIdName) ?? string.Empty; + options.PipelineId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.PipelineIdName) ?? string.Empty; + 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 + { + var runId = await _pipelineService.RunPipelineAsync(options.WorkspaceId, options.PipelineId); + + _logger.LogInformation("Successfully triggered pipeline {PipelineId} in workspace {WorkspaceId}, RunId: {RunId}", + options.PipelineId, options.WorkspaceId, runId); + + var result = new RunPipelineCommandResult(runId); + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.RunPipelineCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error running pipeline {PipelineId} in workspace {WorkspaceId}", + options.PipelineId, options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs new file mode 100644 index 0000000000..c4325c3574 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using global::DataFactory.MCP.Extensions; +using Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; +using Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Mcp.Core.Areas; +using Microsoft.Mcp.Core.Commands; + +namespace Fabric.Mcp.Tools.DataFactory; + +public class DataFactoryAreaSetup : IAreaSetup +{ + public string Name => "datafactory"; + public string Title => "Microsoft Fabric Data Factory"; + + public void ConfigureServices(IServiceCollection services) + { + // Register global::DataFactory.MCP.Core services (auth, HttpClients, all service implementations) + services.AddDataFactoryMcpServices(); + + // Register command instances + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + } + + public CommandGroup RegisterCommands(IServiceProvider serviceProvider) + { + var group = new CommandGroup(Name, + """ + Microsoft Fabric Data Factory Operations - Manage pipelines, dataflows, and workspaces. + Use this tool when you need to: + - List and manage workspaces + - Create, get, list, and run pipelines + - Work with dataflows and data transformations + """); + + group.AddCommand(serviceProvider); + group.AddCommand(serviceProvider); + group.AddCommand(serviceProvider); + group.AddCommand(serviceProvider); + group.AddCommand(serviceProvider); + group.AddCommand(serviceProvider); + + return group; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj b/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj new file mode 100644 index 0000000000..ddba968f95 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj @@ -0,0 +1,14 @@ + + + true + + + + + + + + + + + diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs new file mode 100644 index 0000000000..ce50a919a9 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using System.CommandLine; +global using System.CommandLine.Parsing; +global using System.Text.Json; diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs new file mode 100644 index 0000000000..3f641338c2 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using global::DataFactory.MCP.Models.Pipeline; + +namespace Fabric.Mcp.Tools.DataFactory.Models; + +[JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(ListPipelinesCommandResult))] +[JsonSerializable(typeof(CreatePipelineCommandResult))] +[JsonSerializable(typeof(GetPipelineCommandResult))] +[JsonSerializable(typeof(RunPipelineCommandResult))] +[JsonSerializable(typeof(ListDataflowsCommandResult))] +[JsonSerializable(typeof(CreateDataflowCommandResult))] +public partial class DataFactoryJsonContext : JsonSerializerContext +{ +} + +public sealed record ListPipelinesCommandResult(List Pipelines, int TotalCount); +public sealed record CreatePipelineCommandResult(Pipeline Pipeline); +public sealed record GetPipelineCommandResult(Pipeline Pipeline); +public sealed record RunPipelineCommandResult(string? RunId); +public sealed record ListDataflowsCommandResult(List Dataflows, int TotalCount); +public sealed record CreateDataflowCommandResult(global::DataFactory.MCP.Models.Dataflow.Dataflow Dataflow); diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs new file mode 100644 index 0000000000..bb9b41872c --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.CommandLine; + +namespace Fabric.Mcp.Tools.DataFactory.Options; + +public static class DataFactoryOptionDefinitions +{ + public const string WorkspaceIdName = "workspace-id"; + public static readonly Option WorkspaceId = new($"--{WorkspaceIdName}") + { + Description = "The ID of the Microsoft Fabric workspace.", + Required = true + }; + + public const string PipelineIdName = "pipeline-id"; + public static readonly Option PipelineId = new($"--{PipelineIdName}") + { + Description = "The ID of the pipeline.", + Required = true + }; + + public const string DisplayNameName = "display-name"; + public static readonly Option DisplayName = new($"--{DisplayNameName}") + { + Description = "The display name for the item.", + Required = true + }; + + public const string DescriptionName = "description"; + public static readonly Option Description = new($"--{DescriptionName}") + { + Description = "Optional description for the item.", + Required = false + }; + + public const string RolesName = "roles"; + public static readonly Option Roles = new($"--{RolesName}") + { + Description = "Filter workspaces by roles (Admin, Member, Contributor, Viewer).", + Required = false + }; +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/CreateDataflowOptions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/CreateDataflowOptions.cs new file mode 100644 index 0000000000..98199f9315 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/CreateDataflowOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Options.Dataflow; + +public sealed class CreateDataflowOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + public string? Description { get; set; } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/ListDataflowsOptions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/ListDataflowsOptions.cs new file mode 100644 index 0000000000..819c5e3cf7 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/ListDataflowsOptions.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Options.Dataflow; + +public sealed class ListDataflowsOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/CreatePipelineOptions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/CreatePipelineOptions.cs new file mode 100644 index 0000000000..e9f30141f1 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/CreatePipelineOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Options.Pipeline; + +public sealed class CreatePipelineOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + public string? Description { get; set; } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/GetPipelineOptions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/GetPipelineOptions.cs new file mode 100644 index 0000000000..9458201acd --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/GetPipelineOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Options.Pipeline; + +public sealed class GetPipelineOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; + public string PipelineId { get; set; } = string.Empty; +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/ListPipelinesOptions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/ListPipelinesOptions.cs new file mode 100644 index 0000000000..c4f065e21f --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/ListPipelinesOptions.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Options.Pipeline; + +public sealed class ListPipelinesOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/RunPipelineOptions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/RunPipelineOptions.cs new file mode 100644 index 0000000000..5db716f78f --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Pipeline/RunPipelineOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Options.Pipeline; + +public sealed class RunPipelineOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; + public string PipelineId { get; set; } = string.Empty; +} From 36b2455a9a98390e58e0ccbae04f91c763e53ac7 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 16:17:29 -0700 Subject: [PATCH 07/21] Update DataFactory commands to use domain handlers - All commands now delegate to PipelineHandler/DataflowHandler - Commands are thin shims (~15 lines each) - Handlers registered via AddDataFactoryMcpServices() in submodule - Update submodule to latest Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/Fabric.Mcp.Tools.DataFactory | 2 +- .../Dataflow/CreateDataflowCommand.cs | 31 +++++++------------ .../Commands/Dataflow/ListDataflowsCommand.cs | 21 ++++++------- .../Pipeline/CreatePipelineCommand.cs | 31 +++++++------------ .../Commands/Pipeline/GetPipelineCommand.cs | 23 +++++++------- .../Commands/Pipeline/ListPipelinesCommand.cs | 8 ++--- .../Commands/Pipeline/RunPipelineCommand.cs | 25 +++++++-------- 7 files changed, 62 insertions(+), 79 deletions(-) diff --git a/external/Fabric.Mcp.Tools.DataFactory b/external/Fabric.Mcp.Tools.DataFactory index 0b25987b01..d3e4348c1d 160000 --- a/external/Fabric.Mcp.Tools.DataFactory +++ b/external/Fabric.Mcp.Tools.DataFactory @@ -1 +1 @@ -Subproject commit 0b25987b018f6928d53d9998277f42114a742087 +Subproject commit d3e4348c1d4f37f6a72f9f034bbbbb4ae5a88f4d diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs index 3ced96f3c8..4b3b7e0154 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using global::DataFactory.MCP.Abstractions.Interfaces; using Fabric.Mcp.Tools.DataFactory.Models; using Fabric.Mcp.Tools.DataFactory.Options; using Fabric.Mcp.Tools.DataFactory.Options.Dataflow; -using global::DataFactory.MCP.Models.Dataflow; +using global::DataFactory.MCP.Handlers.Dataflow; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; using Microsoft.Mcp.Core.Extensions; @@ -26,10 +25,10 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; OpenWorld = false)] public sealed class CreateDataflowCommand( ILogger logger, - IFabricDataflowService dataflowService) : GlobalCommand() + DataflowHandler handler) : GlobalCommand() { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricDataflowService _dataflowService = dataflowService ?? throw new ArgumentNullException(nameof(dataflowService)); + private readonly DataflowHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); protected override void RegisterOptions(Command command) { @@ -56,20 +55,14 @@ public override async Task ExecuteAsync(CommandContext context, } var options = BindOptions(parseResult); - try + var result = await _handler.CreateAsync(options.WorkspaceId, options.DisplayName, options.Description); + if (result.IsSuccess) { - var request = new CreateDataflowRequest - { - DisplayName = options.DisplayName, - Description = options.Description - }; - - var response = await _dataflowService.CreateDataflowAsync(options.WorkspaceId, request); - _logger.LogInformation("Successfully created dataflow '{DisplayName}' in workspace {WorkspaceId}", options.DisplayName, options.WorkspaceId); // Map CreateDataflowResponse to Dataflow for the result + var response = result.Value!.Dataflow; var dataflow = new global::DataFactory.MCP.Models.Dataflow.Dataflow { Id = response.Id, @@ -80,14 +73,14 @@ public override async Task ExecuteAsync(CommandContext context, FolderId = response.FolderId }; - var result = new CreateDataflowCommandResult(dataflow); - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.CreateDataflowCommandResult); + var commandResult = new CreateDataflowCommandResult(dataflow); + context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.CreateDataflowCommandResult); } - catch (Exception ex) + else { - _logger.LogError(ex, "Error creating dataflow '{DisplayName}' in workspace {WorkspaceId}", - options.DisplayName, options.WorkspaceId); - HandleException(context, ex); + _logger.LogError("Error creating dataflow '{DisplayName}' in workspace {WorkspaceId}: {Error}", + options.DisplayName, options.WorkspaceId, result.Error); + HandleException(context, new Exception(result.Error)); } return context.Response; diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs index d207642079..5f86e45060 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using global::DataFactory.MCP.Abstractions.Interfaces; using Fabric.Mcp.Tools.DataFactory.Models; using Fabric.Mcp.Tools.DataFactory.Options; using Fabric.Mcp.Tools.DataFactory.Options.Dataflow; +using global::DataFactory.MCP.Handlers.Dataflow; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; using Microsoft.Mcp.Core.Extensions; @@ -25,10 +25,10 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; OpenWorld = false)] public sealed class ListDataflowsCommand( ILogger logger, - IFabricDataflowService dataflowService) : GlobalCommand() + DataflowHandler handler) : GlobalCommand() { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricDataflowService _dataflowService = dataflowService ?? throw new ArgumentNullException(nameof(dataflowService)); + private readonly DataflowHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); protected override void RegisterOptions(Command command) { @@ -52,20 +52,19 @@ public override async Task ExecuteAsync(CommandContext context, var options = BindOptions(parseResult); - try + var result = await _handler.ListAsync(options.WorkspaceId); + if (result.IsSuccess) { - var response = await _dataflowService.ListDataflowsAsync(options.WorkspaceId); - _logger.LogInformation("Successfully listed {Count} dataflows in workspace {WorkspaceId}", - response.Value.Count, options.WorkspaceId); + result.Value!.DataflowCount, options.WorkspaceId); - var commandResult = new ListDataflowsCommandResult(response.Value, response.Value.Count); + var commandResult = new ListDataflowsCommandResult(result.Value.Dataflows.ToList(), result.Value.DataflowCount); context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.ListDataflowsCommandResult); } - catch (Exception ex) + else { - _logger.LogError(ex, "Error listing dataflows in workspace {WorkspaceId}", options.WorkspaceId); - HandleException(context, ex); + _logger.LogError("Error listing dataflows in workspace {WorkspaceId}: {Error}", options.WorkspaceId, result.Error); + HandleException(context, new Exception(result.Error)); } return context.Response; diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs index e5176ec527..abfc1b5311 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using global::DataFactory.MCP.Abstractions.Interfaces; using Fabric.Mcp.Tools.DataFactory.Models; using Fabric.Mcp.Tools.DataFactory.Options; using Fabric.Mcp.Tools.DataFactory.Options.Pipeline; -using global::DataFactory.MCP.Models.Pipeline; +using global::DataFactory.MCP.Handlers.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; using Microsoft.Mcp.Core.Extensions; @@ -26,10 +25,10 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; OpenWorld = false)] public sealed class CreatePipelineCommand( ILogger logger, - IFabricPipelineService pipelineService) : GlobalCommand() + PipelineHandler handler) : GlobalCommand() { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + private readonly PipelineHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); protected override void RegisterOptions(Command command) { @@ -56,20 +55,14 @@ public override async Task ExecuteAsync(CommandContext context, } var options = BindOptions(parseResult); - try + var result = await _handler.CreateAsync(options.WorkspaceId, options.DisplayName, options.Description); + if (result.IsSuccess) { - var request = new CreatePipelineRequest - { - DisplayName = options.DisplayName, - Description = options.Description - }; - - var response = await _pipelineService.CreatePipelineAsync(options.WorkspaceId, request); - _logger.LogInformation("Successfully created pipeline '{DisplayName}' in workspace {WorkspaceId}", options.DisplayName, options.WorkspaceId); // Map CreatePipelineResponse to Pipeline for the result + var response = result.Value!.Pipeline; var pipeline = new global::DataFactory.MCP.Models.Pipeline.Pipeline { Id = response.Id, @@ -80,14 +73,14 @@ public override async Task ExecuteAsync(CommandContext context, FolderId = response.FolderId }; - var result = new CreatePipelineCommandResult(pipeline); - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.CreatePipelineCommandResult); + var commandResult = new CreatePipelineCommandResult(pipeline); + context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.CreatePipelineCommandResult); } - catch (Exception ex) + else { - _logger.LogError(ex, "Error creating pipeline '{DisplayName}' in workspace {WorkspaceId}", - options.DisplayName, options.WorkspaceId); - HandleException(context, ex); + _logger.LogError("Error creating pipeline '{DisplayName}' in workspace {WorkspaceId}: {Error}", + options.DisplayName, options.WorkspaceId, result.Error); + HandleException(context, new Exception(result.Error)); } return context.Response; diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs index bc9c2128b2..10b343a232 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using global::DataFactory.MCP.Abstractions.Interfaces; using Fabric.Mcp.Tools.DataFactory.Models; using Fabric.Mcp.Tools.DataFactory.Options; using Fabric.Mcp.Tools.DataFactory.Options.Pipeline; +using global::DataFactory.MCP.Handlers.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; using Microsoft.Mcp.Core.Extensions; @@ -25,10 +25,10 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; OpenWorld = false)] public sealed class GetPipelineCommand( ILogger logger, - IFabricPipelineService pipelineService) : GlobalCommand() + PipelineHandler handler) : GlobalCommand() { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + private readonly PipelineHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); protected override void RegisterOptions(Command command) { @@ -53,21 +53,20 @@ public override async Task ExecuteAsync(CommandContext context, } var options = BindOptions(parseResult); - try + var result = await _handler.GetAsync(options.WorkspaceId, options.PipelineId); + if (result.IsSuccess) { - var pipeline = await _pipelineService.GetPipelineAsync(options.WorkspaceId, options.PipelineId); - _logger.LogInformation("Successfully retrieved pipeline {PipelineId} from workspace {WorkspaceId}", options.PipelineId, options.WorkspaceId); - var result = new GetPipelineCommandResult(pipeline); - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.GetPipelineCommandResult); + var commandResult = new GetPipelineCommandResult(result.Value!.Pipeline); + context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.GetPipelineCommandResult); } - catch (Exception ex) + else { - _logger.LogError(ex, "Error getting pipeline {PipelineId} from workspace {WorkspaceId}", - options.PipelineId, options.WorkspaceId); - HandleException(context, ex); + _logger.LogError("Error getting pipeline {PipelineId} from workspace {WorkspaceId}: {Error}", + options.PipelineId, options.WorkspaceId, result.Error); + HandleException(context, new Exception(result.Error)); } return context.Response; diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs index 7939d1673c..be3bf8ef6f 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs @@ -25,10 +25,10 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; OpenWorld = false)] public sealed class ListPipelinesCommand( ILogger logger, - ListPipelinesHandler handler) : GlobalCommand() + PipelineHandler handler) : GlobalCommand() { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly ListPipelinesHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + private readonly PipelineHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); protected override void RegisterOptions(Command command) { @@ -52,13 +52,13 @@ public override async Task ExecuteAsync(CommandContext context, var options = BindOptions(parseResult); - var result = await _handler.ExecuteAsync(options.WorkspaceId); + var result = await _handler.ListAsync(options.WorkspaceId); if (result.IsSuccess) { _logger.LogInformation("Successfully listed {Count} pipelines in workspace {WorkspaceId}", result.Value!.PipelineCount, options.WorkspaceId); - var commandResult = new ListPipelinesCommandResult(result.Value.RawPipelines, result.Value.PipelineCount); + var commandResult = new ListPipelinesCommandResult(result.Value.Pipelines.ToList(), result.Value.PipelineCount); context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.ListPipelinesCommandResult); } else diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs index 78118ef0f9..eac94e5671 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using global::DataFactory.MCP.Abstractions.Interfaces; using Fabric.Mcp.Tools.DataFactory.Models; using Fabric.Mcp.Tools.DataFactory.Options; using Fabric.Mcp.Tools.DataFactory.Options.Pipeline; +using global::DataFactory.MCP.Handlers.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; using Microsoft.Mcp.Core.Extensions; @@ -25,10 +25,10 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; OpenWorld = false)] public sealed class RunPipelineCommand( ILogger logger, - IFabricPipelineService pipelineService) : GlobalCommand() + PipelineHandler handler) : GlobalCommand() { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + private readonly PipelineHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); protected override void RegisterOptions(Command command) { @@ -53,21 +53,20 @@ public override async Task ExecuteAsync(CommandContext context, } var options = BindOptions(parseResult); - try + var result = await _handler.RunAsync(options.WorkspaceId, options.PipelineId); + if (result.IsSuccess) { - var runId = await _pipelineService.RunPipelineAsync(options.WorkspaceId, options.PipelineId); - _logger.LogInformation("Successfully triggered pipeline {PipelineId} in workspace {WorkspaceId}, RunId: {RunId}", - options.PipelineId, options.WorkspaceId, runId); + options.PipelineId, options.WorkspaceId, result.Value!.JobInstanceId); - var result = new RunPipelineCommandResult(runId); - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.RunPipelineCommandResult); + var commandResult = new RunPipelineCommandResult(result.Value.JobInstanceId); + context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.RunPipelineCommandResult); } - catch (Exception ex) + else { - _logger.LogError(ex, "Error running pipeline {PipelineId} in workspace {WorkspaceId}", - options.PipelineId, options.WorkspaceId); - HandleException(context, ex); + _logger.LogError("Error running pipeline {PipelineId} in workspace {WorkspaceId}: {Error}", + options.PipelineId, options.WorkspaceId, result.Error); + HandleException(context, new Exception(result.Error)); } return context.Response; From 7dd454ace725401a671dc148a3bc96d15855f742 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 21:14:31 -0700 Subject: [PATCH 08/21] Switch DataFactory.MCP.Core from project reference to NuGet package Replace ProjectReference to submodule Core project with PackageReference to Microsoft.DataFactory.MCP.Core 0.19.0-beta from nuget.org. Add version entry in Directory.Packages.props for CPM compliance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Packages.props | 1 + .../src/Fabric.Mcp.Tools.DataFactory.csproj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 6150468cfa..59cab15458 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -84,6 +84,7 @@ + diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj b/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj index ddba968f95..9250d07bdf 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj @@ -4,9 +4,9 @@ - + From 2f56ce8bfa0c12bb8c3e4a09b47552e9a84b2707 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 21:16:38 -0700 Subject: [PATCH 09/21] Remove DataFactory.MCP submodule No longer needed - consuming Microsoft.DataFactory.MCP.Core via NuGet package instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitmodules | 4 ---- external/Fabric.Mcp.Tools.DataFactory | 1 - 2 files changed, 5 deletions(-) delete mode 160000 external/Fabric.Mcp.Tools.DataFactory diff --git a/.gitmodules b/.gitmodules index d0be985560..e69de29bb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +0,0 @@ -[submodule "external/Fabric.Mcp.Tools.DataFactory"] - path = external/Fabric.Mcp.Tools.DataFactory - url = https://github.com/microsoft/DataFactory.MCP.git - branch = fabric-mcp-tools-integration diff --git a/external/Fabric.Mcp.Tools.DataFactory b/external/Fabric.Mcp.Tools.DataFactory deleted file mode 160000 index d3e4348c1d..0000000000 --- a/external/Fabric.Mcp.Tools.DataFactory +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d3e4348c1d4f37f6a72f9f034bbbbb4ae5a88f4d From 2373c1a25bd6662caad0a817055321701b644abb Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 21:18:58 -0700 Subject: [PATCH 10/21] Remove .gitmodules and CONTRIBUTING.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitmodules | 0 CONTRIBUTING.md | 856 ------------------------------------------------ 2 files changed, 856 deletions(-) delete mode 100644 .gitmodules delete mode 100644 CONTRIBUTING.md diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index e840416b8c..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,856 +0,0 @@ -# Contributing to Azure MCP - -There are many ways to contribute to the Azure MCP project: reporting bugs, submitting pull requests, and creating suggestions. -After cloning and building the repo, check out the [GitHub project](https://github.com/orgs/Azure/projects/812/views/13) and [issues list](https://github.com/microsoft/mcp/issues). Issues labeled [help wanted](https://github.com/microsoft/mcp/labels/help%20wanted) are good issues to submit a PR for. Issues labeled [good first issue](https://github.com/microsoft/mcp/labels/good%20first%20issue) are great candidates to pick up if you are in the code for the first time. - ->[!IMPORTANT] -If you are contributing significant changes, or if the issue is already assigned to a specific milestone, please discuss with the assignee of the issue first before starting to work on the issue. - -## Table of Contents - -- [Contributing to Azure MCP](#contributing-to-azure-mcp) - - [Table of Contents](#table-of-contents) - - [Getting Started](#getting-started) - - [Prerequisites](#prerequisites) - - [Project Structure](#project-structure) - - [Development Workflow](#development-workflow) - - [Development Process](#development-process) - - [Adding a New Command](#adding-a-new-command) - - [Testing](#testing) - - [Unit Tests](#unit-tests) - - [Cancellation plumbing](#cancellation-plumbing) - - [End-to-end Tests](#end-to-end-tests) - - [Testing Local Build with VS Code](#testing-local-build-with-vs-code) - - [Build the Server](#build-the-server) - - [Run the Azure MCP server in HTTP mode](#run-the-azure-mcp-server-in-http-mode) - - [Configure mcp.json](#configure-mcpjson) - - [Server Modes](#server-modes) - - [Start from IDE](#start-from-ide) - - [Testing Local Build with Docker](#testing-local-build-with-docker) - - [Live Tests](#live-tests) - - [NPX Live Tests](#npx-live-tests) - - [Recording Live Tests](#recording-live-tests) - - [Debugging Live Tests](#debugging-live-tests) - - [Quality and Standards](#quality-and-standards) - - [Code Style](#code-style) - - [Spelling Check](#spelling-check) - - [Requirements](#requirements) - - [AOT Compatibility Analysis](#aot-compatibility-analysis) - - [Running the Analysis](#running-the-analysis) - - [Installing Git Hooks](#installing-git-hooks) - - [Model Context Protocol (MCP)](#model-context-protocol-mcp) - - [Package README](#package-readme) - - [Advanced Configuration](#advanced-configuration) - - [Configuring External MCP Servers](#configuring-external-mcp-servers) - - [Registry Configuration](#registry-configuration) - - [Transport Types](#transport-types) - - [Server Discovery and Namespace Filtering](#server-discovery-and-namespace-filtering) - - [Adding New External MCP Servers](#adding-new-external-mcp-servers) - - [Example External Servers](#example-external-servers) - - [Project Management](#project-management) - - [Pull Request Process](#pull-request-process) - - [Builds and Releases (Internal)](#builds-and-releases-internal) - - [PR Validation](#pr-validation) - - [Support and Community](#support-and-community) - - [Questions and Support](#questions-and-support) - - [Additional Resources](#additional-resources) - - [Code of Conduct](#code-of-conduct) - - [License](#license) - -## Getting Started - -> [!IMPORTANT] -> If you are a **Microsoft employee** then please also review our [Azure Internal Onboarding Documentation](https://aka.ms/azmcp/intake) for getting setup - -### Prerequisites - -1. **VS Code**: Install either [stable](https://code.visualstudio.com/download) or [Insiders](https://code.visualstudio.com/insiders) release -2. **GitHub Copilot**: Install [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) and [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) extensions -3. **Node.js**: Install [Node.js](https://nodejs.org/en/download) 20 or later (ensure `node` and `npm` are in your PATH) -4. **PowerShell**: Install [PowerShell](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) 7.0 or later (required for build and test scripts) - -### Project Structure - -- `core\` - - `Azure.Mcp.Core` - Azure.Mcp.Core core library, depends on Microsoft.Mcp.Core - - `Fabric.Mcp.Core` - Fabric.Mcp.Core, depends on Azure.Mcp.Core (fabric uses azure) - - `Microsoft.Mcp.Core` - Microsoft.Mcp.Core library -- `servers\` - - `{server}.Mcp.Server - Individual servers (e.g. `Azure.Mcp.Server`, `Fabric.Mcp.Server`) - - `src` - Source for the server - - `tests` - Any unit or live tests for the server - - `README.md` - Specific readme for this server - - `CHANGELOG.md` - Specific changelog for this server -- `tools/` - Service-specific implementations - - `{server}.Mcp.Tools.{tool-name}/` - Individual server tools (e.g., `Azure.Mcp.Tools.KeyVault`, `Fabric.Mcp.Tools.Admin`) - - `src` - Service specific code - - `Commands/` - Command implementations - - `Models/` - Service specific models - - `Services/` - Service implementations and interfaces - - `Options/` - Service specific command options - - `tests/` - Service specific tests - - `{server}.Mcp.Tools.{tool-name}.UnitTests/` - Unit tests require no authentication or test resources - - `{server}.Mcp.Tools.{tool-name}.LiveTests/` - Live tests depend on Azure resources and authentication - - `test-resources.bicep` - Infrastructure templates for testing -- `external/` - Git submodules for external project integrations - - `Fabric.Mcp.Tools.DataFactory` - [DataFactory.MCP](https://github.com/microsoft/DataFactory.MCP) submodule (used by `Fabric.Mcp.Server`) - - Run `git submodule update --init --recursive` after cloning to fetch submodule contents -- `eng/` - Shared tools, templates, CLI helpers -- `docs/` - Central documentation and onboarding materials - -## Development Workflow - -### Development Process - -1. Fork the repository -2. Clone with submodules: `git clone --recurse-submodules ` - - If already cloned, run: `git submodule update --init --recursive` -3. Create a feature branch -4. Make your changes -5. Write or update tests -6. Test locally -7. Submit a pull request - -### Adding a New Command - -> [!TIP] -> **Submit One Tool Per Pull Request** -> -> We strongly recommend submitting **one tool per pull request** to streamline the review process and provide better onboarding experience. This approach results in: -> -> - **Faster reviews**: Single tools are easier and quicker to review -> - **Better feedback**: More focused discussions on individual tool implementation -> - **Easier iteration**: Smaller changes mean faster iteration cycles -> - **Incremental progress**: Get your first tool merged to establish baseline, then build upon it -> -> If you're planning to contribute multiple tools, please: -> -> 1. Submit your most important or representative tool as your first PR to establish the code patterns. -> 2. Use that baseline to inform your subsequent tool PRs. - -1. **Create an issue** with title: "Add command: azmcp [namespace] [resource] [operation]" and detailed description - -2. **Set up development environment**: - - Open VS Code Insiders - - Open the Copilot Chat view - - Select "Agent" mode - -3. **Generate the command** using Copilot: - - ```txt - Execute in Copilot Chat: - "create [namespace] [resource] [operation] command using #new-command.md as a reference" - ``` - -4. **Follow implementation guidelines** in [docs/new-command.md](https://github.com/microsoft/mcp/blob/main/servers/Azure.Mcp.Server/docs/new-command.md) - -5. **Update documentation**: - - Add the new command to [/servers/Azure.Mcp.Server/docs/azmcp-commands.md](https://github.com/microsoft/mcp/blob/main/servers/Azure.Mcp.Server/docs/azmcp-commands.md) - - Run `.\eng\scripts\Update-AzCommandsMetadata.ps1` to update tool metadata in azmcp-commands.md (required for CI) - - Add test prompts for the new command in [/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md](https://github.com/microsoft/mcp/blob/main/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md) - - Update [README.md](https://github.com/microsoft/mcp/blob/main/README.md) to mention the new command - -6. **Create a changelog entry** (if your change is a new feature, bug fix, or breaking change): - - Use the generator script to create a changelog entry (see `docs/changelog-entries.md` for details): - ```powershell - # Interactive mode (prompts for server) - ./eng/scripts/New-ChangelogEntry.ps1 - - # Or with all parameters - ./eng/scripts/New-ChangelogEntry.ps1 -ChangelogPath "servers/Azure.Mcp.Server/CHANGELOG.md" -Description -Section -PR - ./eng/scripts/New-ChangelogEntry.ps1 -ChangelogPath "servers/Fabric.Mcp.Server/CHANGELOG.md" -Description -Section -PR - ``` - - Or manually create a YAML file in `servers/{ServerName}/changelog-entries/` - - Not every PR needs a changelog entry - skip for internal refactoring, test-only changes, or minor updates. If unsure, add to the "Other Changes" section or ask a maintainer. - -7. **Add CODEOWNERS entry** in [CODEOWNERS](https://github.com/microsoft/mcp/blob/main/.github/CODEOWNERS) [(example)](https://github.com/microsoft/mcp/commit/08f73efe826d5d47c0f93be5ed9e614740e82091) - -8. **Add new tool to consolidated mode**: - - Open `core/Azure.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json` file, where the tool grouping definition is stored for consolidated mode. In Agent mode, add it to the chat as context. - - Paste the follow prompt for Copilot to generate the change to add the new tool: - ```txt - I have this list of tools which haven't been matched with any consolidated tools in this file. Help me add them to the one with the best matching category and exact matching toolMetadata. Update existing consolidated tools where newly mapped tools are added. If you can't find one, suggest a new consolidated tool. - - - ``` - - Use the following command to find out the correct tool name for your new tool - ``` - cd servers/Azure.Mcp.Server/src/bin/Debug/net10.0 - ./azmcp[.exe] tools list --name --namespace - ``` - - Commit the change. - -9. **Create Pull Request**: - - Reference the issue you created - - Include tests in the `/tests` folder - - Ensure all tests pass - - Follow code style requirements - - Run [`ToolDescriptionEvaluator`](https://github.com/microsoft/mcp/blob/main/eng/tools/ToolDescriptionEvaluator/Quickstart.md) for the new tool description and obtain a score of `0.4` or more and a top 3 ranking for all related test prompts - -## Testing - -Command authors must provide unit tests and end-to-end test prompts. Commands that interact with Azure resources **must** also include live tests with recorded playback coverage (see [Recording Live Tests](#recording-live-tests)). - -### Unit Tests - -Unit tests live under the `/tests` folder. To run tests: - -```pwsh -./eng/scripts/Test-Code.ps1 -``` - -To scope the test run to path substring matches, use: - -```pwsh -./eng/scripts/Test-Code.ps1 -Paths KeyVault, core/Azure -``` - -Requirements: - -- Each command should have unit tests - - The command unit tests should extend `CommandUnitTestsBase` -- Tests should cover success and error scenarios -- Mock external service calls -- Test argument validation - -#### Cancellation plumbing - -To ensure the product code and unit tests can be cancelled quickly, contributors are required to write async methods (any returning `Task`, `ValueTask`, generic variants of those, etc.) to accept and invoke async methods with a `System.Threading.CancellationToken` parameter. The latter is enforced with the [CA2016 analyzer](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2016). - -Mocks created with `NSubstitute.Substitue.For()` and have [methods set up](https://nsubstitute.github.io/help/set-return-value/#for-methods) should be passed `NSubstitute.Arg.Any()` for required `System.Threading.CancellationToken` parameters. The same should be used when [checking for received calls on a mocked object](https://nsubstitute.github.io/help/received-calls/index.html). If the product code is expected to do something interesting with a supplied `System.Threading.CancellationToken` parameter, such as linking with other `System.Threading.CancellationToken`s with [`System.Threading.CancellationTokenSource.CreateLinkedTokenSource`](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtokensource.createlinkedtokensource), then consider testing for that behavior. - -Real product code under unit testing must be passed `Xunit.TestContext.Current.CancellationToken` when async methods are invoked. This is to ensure the tests can end to avoid possible issues with the parent process waiting indefinitely for the test runner executable to exit. - -### End-to-end Tests - -End-to-end tests are performed manually. Command authors must thoroughly test each command to ensure correct tool invocation and results. At least one prompt per tool is required and should be added to `/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md`. - -### Testing Local Build with VS Code - -To run the Azure MCP server from source for local development: - -#### Build the Server - -Build the project at the root directory of this repository: - -```bash -dotnet build -``` - -#### Run the Azure MCP server in HTTP mode - -**Option 1: Using dotnet run (uses launchSettings.json)** - -**Prerequisites: Create launchSettings.json** - -> [!NOTE] -> Internal contributors may skip this step as the `launchSettings.json` file is already provided in the repository. - -Before running the server in HTTP mode, you need to create the `launchSettings.json` file with the `debug-remotemcp` profile: - -1. Create the directory (if it doesn't exist): - ```bash - mkdir -p servers/Azure.Mcp.Server/src/Properties - ``` - -2. Create `servers/Azure.Mcp.Server/src/Properties/launchSettings.json` with the following content: - ```json - { - "profiles": { - "debug-remotemcp": { - "commandName": "Project", - "commandLineArgs": "server start --transport http --outgoing-auth-strategy UseHostingEnvironmentIdentity", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "http://localhost:", - "AzureAd__TenantId": "", - "AzureAd__ClientId": "", - "AzureAd__Instance": "https://login.microsoftonline.com/" - } - } - } - } - ``` - -3. Replace `` and `` with your actual tenant ID and client ID. - -```bash -dotnet run --project servers/Azure.Mcp.Server/src/ --launch-profile debug-remotemcp -``` - -**Option 2: Using the built executable directly** - -Build the project first, then run the executable with the necessary environment variables: - -```powershell -# Set environment variables (PowerShell) -$env:ASPNETCORE_ENVIRONMENT = "Development" -$env:ASPNETCORE_URLS = "http://localhost:" -$env:AzureAd__TenantId = "" -$env:AzureAd__ClientId = "" -$env:AzureAd__Instance = "https://login.microsoftonline.com/" - -# Run the executable -./servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp.exe server start --transport http --outgoing-auth-strategy UseHostingEnvironmentIdentity -``` - -```bash -# Set environment variables (Bash) -export ASPNETCORE_ENVIRONMENT="Development" -export ASPNETCORE_URLS="http://localhost:" -export AzureAd__TenantId="" -export AzureAd__ClientId="" -export AzureAd__Instance="https://login.microsoftonline.com/" - -# Run the executable -./servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp server start --transport http --outgoing-auth-strategy UseHostingEnvironmentIdentity -``` - -> **Note:** The environment variables listed above are taken from the `debug-remotemcp` profile in `launchSettings.json`. Replace `` and `` with your actual Azure AD tenant ID and client ID. These variables configure Azure AD authentication and the server endpoint for HTTP mode operation. -> -> For local development, when running with HTTPS (either via `ASPNETCORE_URLS` or HTTPS redirection), you must generate a self-signed development certificate: -> ->**Windows and macOS:** -> ```bash -> dotnet dev-certs https --trust -> ``` -> -> **Linux:** -> ```bash -> dotnet dev-certs https -> ``` -> -> On Linux, you must manually trust the generated certificate. See the [official documentation](https://learn.microsoft.com/dotnet/core/tools/dotnet-dev-certs) for instructions on how to do this. - -#### Configure mcp.json - -Update your mcp.json to point to the locally built azmcp executable. - -**Stdio Mode:** - -```json -{ - "servers": { - "azure-mcp-server": { - "type": "stdio", - "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", - "args": ["server", "start"] - } - } -} -``` - -**HTTP Mode:** - -```json -{ - "servers": { - "Azure MCP Server": { - "url": "https://localhost:1031/", - "type": "http" - } - } -} -``` - -> [!NOTE] -> For stdio mode, replace `` with the full path to your built executable. -> On **Windows**, use `azmcp.exe`. On **macOS/Linux**, use `azmcp`. -> For HTTP mode, ensure the server is running on the specified port before connecting (port 1031 is the default port configured in launchSettings.json). - -#### Server Modes - -Optional `--namespace` and `--mode` parameters can be used to configure different server modes: - -**Default Mode** (no additional parameters): - -```json -{ - "servers": { - "azure-mcp-server": { - "type": "stdio", - "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", - "args": ["server", "start"] - } - } -} -``` - -**Namespace Mode** (expose specific services): - -```json -{ - "servers": { - "azure-mcp-server": { - "type": "stdio", - "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", - "args": ["server", "start", "--namespace", "storage", "--namespace", "keyvault"] - } - } -} -``` - -**Namespace Proxy Mode** (collapse tools by namespace): - -```json -{ - "servers": { - "azure-mcp-server": { - "type": "stdio", - "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", - "args": ["server", "start", "--mode", "namespace"] - } - } -} -``` - -**Single Tool Proxy Mode** (single "azure" tool with internal routing): - -```json -{ - "servers": { - "azure-mcp-server": { - "type": "stdio", - "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", - "args": ["server", "start", "--mode", "single"] - } - } -} -``` - -**Consolidated Mode** (grouped related operations): -It honors both --read-only and --namespace switches. - -```json -{ - "servers": { - "azure-mcp-server": { - "type": "stdio", - "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", - "args": ["server", "start", "--mode", "consolidated"] - } - } -} -``` - -**Combined Mode** (filter namespaces with any mode): - -```json -{ - "servers": { - "azure-mcp-server": { - "type": "stdio", - "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", - "args": ["server", "start", "--namespace", "storage", "--namespace", "keyvault", "--mode", "namespace"] - } - } -} -``` - -**Specific Tool Mode** (expose only specific tools): - -```json -{ - "servers": { - "azure-mcp-server": { - "type": "stdio", - "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", - "args": ["server", "start", "--tool", "azmcp_storage_account_get", "--tool", "azmcp_subscription_list"] - } - } -} -``` - -> **Server Mode Summary:** -> -> - **Default Mode (Namespace)**: No additional parameters - collapses tools by namespace (current default) -> - **Consolidated Mode**: `--mode consolidated` - exposes consolidated tools grouping related operations, optimized for AI agents. -> - **Namespace Mode**: `--namespace ` - expose specific services -> - **Namespace Proxy Mode**: `--mode namespace` - collapse tools by namespace (useful for VS Code's 128 tool limit) -> - **All Tools Mode**: `--mode all` - expose all ~800+ individual tools -> - **Single Tool Mode**: `--mode single` - single "azure" tool with internal routing -> - **Specific Tool Mode**: `--tool ` - expose only specific tools by name (finest granularity) -> - **Combined Mode**: Multiple options can be used together (`--namespace` + `--mode` etc.) - -#### Start from IDE - -With the configuration in place, you can launch the MCP server directly from your IDE or any tooling that uses `mcp.json`. - -### Testing Local Build with Docker - -To build a local image for testing purposes: - -1. Execute: `./eng/scripts/Build-Docker.ps1 -ServerName "Azure.Mcp.Server"`. -2. Update `mcp.json` to point to locally built Docker image: - - ```json - { - "servers": { - "Azure MCP Server": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "--env-file", - "/full/path/to/.env" - "azure-sdk/azure-mcp:", - ] - } - } - } - ``` - -### Live Tests - -> [!IMPORTANT] -> Live tests are **required** for all commands that interact with Azure resources. -> -> If you are a **Microsoft employee** with Azure source permissions then please review our [Azure Internal Onboarding Documentation](https://aka.ms/azmcp/intake). As part of reviewing community contributions, Azure team members can run live tests by adding this comment to the PR `/azp run mcp - pullrequest - live`. - -Before running live tests: - -- [Install Azure PowerShell](https://learn.microsoft.com/powershell/azure/install-azure-powershell) -- [Install Azure Bicep](https://learn.microsoft.com/azure/azure-resource-manager/bicep/install#install-manually) -- Login to Azure PowerShell: [`Connect-AzAccount`](https://learn.microsoft.com/powershell/azure/authenticate-interactive?view=azps-13.4.0) -- Deploy test resources: - -```pwsh -./eng/scripts/Deploy-TestResources.ps1 -Paths KeyVault -./eng/scripts/Deploy-TestResources.ps1 -Paths Storage, core/Azure -``` - -**Deploy-TestResources.ps1 Parameters:** - -| Parameter | Type | Description | -|---------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------| -| `Paths` | string[] | REQUIRED. Path filters to apply (e.g., `Storage`, `core/`). All filters are applied as substring matches. (e.g., `*Storage*`, `*core/*`) | -| `SubscriptionId` | string | Target subscription ID. If omitted, the current Azure context subscription (from `Get-AzContext`) is used. | -| `ResourceGroupName` | string | Resource group name. Defaults to `{username}-mcp{hash(username)}`. | -| `BaseName` | string | Base name prefix for resources. Defaults to `mcp{hash}`. | -| `Unique` | switch | Use a unique GUID-based hash for this invocation instead of the stable username+subscription hash. | -| `DeleteAfterHours` | int | Hours after which resources are tagged for deletion. Defaults to `12`. | - -Examples: - -```pwsh -# Deploy Storage test resources using current Azure context subscription -./eng/scripts/Deploy-TestResources.ps1 -Paths Storage - -# Deploy Key Vault test resources to a specific subscription and keep for one week -./eng/scripts/Deploy-TestResources.ps1 -Paths KeyVault -SubscriptionId -DeleteAfterHours 168 -Unique -``` - -After deploying test resources, you should have a `.testsettings.json` file with your deployment information in the deployed paths' `/tests` directory. - -Run live tests with: - -```pwsh -./eng/scripts/Test-Code.ps1 -TestType Live -``` - -You can scope tests to specific paths: - -```pwsh -./eng/scripts/Test-Code.ps1 -TestType Live -Paths Storage, KeyVault -``` - -### NPX Live Tests - -You can set the `TestPackage` parameter in `.testsettings.json` to have live tests run `npx` targeting an arbitrary Azure MCP package: - -```json -{ - "TenantId": "a20062a8-ff76-41c2-8a6d-5e843da7b051", - "TenantName": "Your Tenant", - "SubscriptionId": "cd27afdc-9976-4f08-96e9-cad120a91560", - "SubscriptionName": "Your Subscription", - "ResourceGroupName": "rg-abcdefg", - "ResourceBaseName": "t1234567890", - "TestPackage": "@azure/mcp@0.0.10" -} -``` - -To run live tests against the local build of an npm module: - -```pwsh -./eng/scripts/Build-Local.ps1 -``` - -This will produce .tgz files in the `.dist` directory and set the `TestPackage` parameter in the `.testsettings.json` file: - -```json -"TestPackage": "file://D:\\repos\\azure-mcp\\.dist\\wrapper\\azure-mcp-0.0.12-alpha.1746488279.tgz" -``` - -### Recording Live Tests - -All new live tests **must** be recorded for playback. Live tests use the Azure SDK Test Proxy to capture and replay HTTP traffic, ensuring CI can validate tests without live Azure resources. For the full migration guide, recording workflow, sanitizer/matcher configuration, and troubleshooting tips, see [docs/recorded-tests.md](https://github.com/microsoft/mcp/blob/main/docs/recorded-tests.md). - -### Debugging Live Tests - -This section assumes that the necessary Azure resources for live tests are already deployed and that the `.testsettings.json` file with deployment information is located in the area's `/tests/` directory. - -To debug the Azure MCP Server (`azmcp`) when running live tests in VS Code: - -1. Build the package with debug symbols: `./eng/scripts/Build-Local.ps1` -2. Set a breakpoint in a command file (e.g., [`KeyValueListCommand.ExecuteAsync`](https://github.com/microsoft/mcp/blob/4ed650a0507921273acc7b382a79049809ef39c1/src/Commands/AppConfig/KeyValue/KeyValueListCommand.cs#L48)) -3. In VS Code, navigate to a test method (e.g., [`AppConfigCommandTests::Should_list_appconfig_kvs()`](https://github.com/microsoft/mcp/blob/4ed650a0507921273acc7b382a79049809ef39c1/tests/Client/AppConfigCommandTests.cs#L56)), add a breakpoint to `CallToolAsync` call in the test method, then right-click and select **Debug Test** -4. Find the `azmcp` process ID: - - ```shell - pgrep -fl azmcp - ``` - - ```powershell - Get-Process | Where-Object { $_.ProcessName -like "*azmcp*" } | Select-Object Id, ProcessName, Path - ``` - -5. Open the Command Palette (`Cmd+Shift+P` on Mac, `Ctrl+Shift+P` on Windows/Linux), select **Debug: Attach to .NET 5+ or .NET Core process**, and enter the `azmcp` process ID -6. Hit F5 to "Continue" debugging, the debugger should attach to `azmcp` and hit the breakpoint in command file - -## Quality and Standards - -### Code Style - -To ensure consistent code quality, code format checks will run during all PR and CI builds. Run `dotnet format` before submitting to catch format errors early. - -#### Spelling Check - -To ensure consistent spelling across the codebase, run the spelling check before submitting: - -```pwsh -.\eng\common\spelling\Invoke-Cspell.ps1 -``` - -This will check all files for spelling errors using the project's dictionary. Add any new technical terms or proper nouns to `.vscode/cspell.json` if needed. - -#### Requirements - -- Follow C# coding conventions -- No comments in implementation code (code should be self-documenting) -- Use descriptive variable and method names -- Follow the exact file structure and naming conventions -- Use proper error handling patterns -- XML documentation for public APIs -- Follow Model Context Protocol (MCP) patterns - -### AOT Compatibility Analysis - -The AOT compatibility analysis helps identify potential issues that might prevent the Azure MCP Server from working correctly when compiled with AOT or when trimming is enabled. - -#### Running the Analysis - -To run the AOT compatibility analysis locally: - -```pwsh -./eng/scripts/Analyze-AOT-Compact.ps1 -``` - -The HTML report will be generated at `.work/aotCompactReport/aot-compact-report.html` and automatically opened in your default browser. - -To output the report to console, run the analysis with `-OutputFormat Console` argument. - -AOT compatibility warnings typically indicate: - -- Use of reflection without proper annotations -- Serialization of types that might be trimmed -- Dynamic code generation -- Use of `RequiresUnreferencedCodeAttribute` methods without proper precautions - -#### Installing Git Hooks - -You can install our pre-push hook to catch code format issues by automatically running `dotnet format` before each `git push`: - -- `./eng/scripts/Install-GitHooks.ps1` - Installs the pre-push hook into your local repo -- `./eng/scripts/Remove-GitHooks.ps1` - Disables any git hooks in your local repo - -### Model Context Protocol (MCP) - -The Azure MCP Server implements the [Model Context Protocol specification](https://modelcontextprotocol.io). When adding new commands: - -- Follow MCP JSON schema patterns -- Implement proper context handling -- Use standardized response formats -- Handle errors according to MCP specifications -- Provide proper argument suggestions - -### Package README - -A single package README.md could be used to generate context specific content for different package types (npm, nuget, vsix) using html comment annotations to mark sections for removal or insertion whem processed with script at `.\eng\scripts\Process-PackageReadMe.ps1` - -Supported comment annotations: - -- Section Removal - - **Purpose:** Remove one or more lines, or parts of a line of markdown for specified package types. - - **Example:** - ``` - - ...... - various markdown lines to be removed for nuget and npm - ...... - - ``` - -- Section Insert - - **Purpose:** Insert a chunk of text into a line for a specified package type. - - **Example:** - `` - -You can verify that your README.md was annotated correctly using the `Validate-PackageReadme` function: -``` -& "eng\scripts\Process-PackageReadMe.ps1" -Command "validate" -InputReadMePath "" -``` - -To extract README.md for a specific package, run the `Extract-PackageSpecificReadMe` function: -``` -& "eng\scripts\Process-PackageReadMe.ps1" -Command "extract" ` - -InputReadMePath "" ` - -OutputDirectory "" ` - -PackageType "" ` - -InsertPayload "" -``` - -## Advanced Configuration - -### Configuring External MCP Servers - -The Azure MCP Server supports connecting to external MCP servers through an embedded `registry.json` configuration file. This enables the server to act as a proxy, aggregating tools from multiple MCP servers into a single interface. The registry follows the same configuration schema as VS Code's `mcp.json`. - -#### Registry Configuration - -External MCP servers are defined in the embedded resource file `core/Azure.Mcp.Core/src/Areas/Server/Resources/registry.json`. This file contains server configurations that support both SSE (Server-Sent Events) and stdio transport mechanisms, following the standard MCP configuration format. - -The registry structure follows this format: - -```json -{ - "servers": { - "documentation": { - "url": "https://learn.microsoft.com/api/mcp", - "description": "Search official Microsoft/Azure documentation..." - }, - "another-server": { - "type": "stdio", - "command": "path/to/executable", - "args": ["arg1", "arg2"], - "env": { - "ENV_VAR": "value" - }, - "description": "Another MCP server using stdio transport" - } - } -} -``` - -#### Transport Types - -**SSE (Server-Sent Events) Transport:** - -- Use the `url` property to specify the endpoint -- Supports HTTP-based communication with automatic transport mode detection -- Best for web-based MCP servers and remote endpoints - -**Stdio Transport:** - -- Use `type: "stdio"` with the `command` property -- Supports launching external processes that communicate via standard input/output -- Use `args` array for command-line arguments -- Use `env` object for environment variables -- Best for local executables, command-line tools, and local MCP servers - -#### Server Discovery and Namespace Filtering - -External servers are automatically discovered when the Azure MCP Server starts. They can be filtered using the same namespace mechanisms as built-in commands: - -```bash -# Include only specific external servers -azmcp server start --namespace documentation --namespace another-server - -# Use namespace mode to group tools exposed by external servers -azmcp server start --mode namespace -``` - -#### Adding New External MCP Servers - -To add a new external MCP server to the registry: - -1. Edit `core/Azure.Mcp.Core/src/Areas/Server/Resources/registry.json` -2. Add your server configuration under the `servers` object using VS Code's MCP configuration schema -3. Use a unique identifier as the key -4. Provide either a `url` for SSE transport or `type: "stdio"` with `command` for stdio transport -5. Include a descriptive `description` field -6. Rebuild the project to embed the updated registry - -#### Example External Servers - -The current registry includes: - -- **documentation**: Microsoft Learn documentation search via SSE transport -- Additional external servers can be added following the same pattern as VS Code's mcp.json - -External servers integrate seamlessly with the Azure MCP Server's tool aggregation, appearing alongside native Azure commands in the unified tool interface. This allows you to combine local MCP servers, remote MCP endpoints, and Azure-specific tools in a single interface. - -## Project Management - -### Pull Request Process - -1. Update documentation reflecting any changes -2. Add or update tests as needed -3. Reference the original issue -4. Wait for review and address any feedback - -### Builds and Releases (Internal) - -The internal pipeline [azure-mcp](https://dev.azure.com/azure-sdk/internal/_build?definitionId=7571) is used for all official releases and CI builds. On every merge to main, a build will run and will produce a dynamically named prerelease package on the public dev feed, e.g. [@azure/mcp@0.0.10-beta.4799791](https://dev.azure.com/azure-sdk/public/_artifacts/feed/azure-sdk-for-js/Npm/@azure%2Fmcp/overview/0.0.10-beta.4799791). - -Only manual runs of the pipeline sign and publish packages. Building `main` or `hotfix/*` will publish to `npmjs.com`, all other refs will publish to the [public dev feed](https://dev.azure.com/azure-sdk/public/_artifacts/feed/azure-sdk-for-js). - -Packages published to npmjs.com will always use the `@latest` [dist-tag](https://docs.npmjs.com/downloading-and-installing-packages-locally#installing-a-package-with-dist-tags). - -Packages published to the dev feed will use: - -- `@latest` for the latest official/release build -- `@dev` for the latest CI build of main -- `@pre` for any arbitrary pipeline run or feature branch build - -#### PR Validation - -To run live tests for a PR, inspect the PR code for any suspicious changes, then add the comment `/azp run mcp - pullrequest - live` to the pull request. This will queue a PR triggered run which will build, run unit tests, deploy test resources and run live tests. - -If you would like to see the product of a PR as a package on the dev feed, after thoroughly inspecting the change, create a branch in the main repo and manually trigger an [azure - mcp](https://dev.azure.com/azure-sdk/internal/_build?definitionId=7571) pipeline run against that branch. This will queue a manually triggered run which will build, run unit tests, deploy test resources, run live tests, sign and publish the packages to the dev feed. - -Instructions for consuming the package from the dev feed can be found in the "Extensions" tab of the pipeline run page. - -**CI Validation Checks**: - -All PRs automatically run the following validation checks: -- **Code formatting** - Ensures code follows project formatting standards -- **Spelling check** - Validates spelling across the codebase -- **AOT compatibility** - Checks ahead-of-time compilation compatibility -- **Tool metadata verification** - Ensures `azmcp-commands.md` is up-to-date with tool metadata (run `.\eng\scripts\Update-AzCommandsMetadata.ps1` if this fails) - -## Support and Community - -Please see our [support](https://github.com/microsoft/mcp/blob/main/SUPPORT.md) statement. - -### Questions and Support - -We're building this in the open. Your feedback is much appreciated, and will help us shape the future of the Azure MCP server. - -πŸ‘‰ [Open an issue in the public repository](https://github.com/microsoft/mcp/issues/new/choose). - -### Additional Resources - -- [Azure MCP Documentation](https://github.com/microsoft/mcp/blob/main/README.md) -- [Command Implementation Guide](https://github.com/microsoft/mcp/blob/main/servers/Azure.Mcp.Server/docs/new-command.md) -- [VS Code Insiders Download](https://code.visualstudio.com/insiders/) -- [GitHub Copilot Documentation](https://docs.github.com/en/copilot) - -### Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. By participating, you are expected to uphold this code. - -### License - -By contributing, you agree that your contributions will be licensed under the project's [license](https://github.com/microsoft/mcp/blob/main/LICENSE). From 77fdd40ebad04463c64a1fdcd41149cd3e0bd770 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 21:20:39 -0700 Subject: [PATCH 11/21] Restore CONTRIBUTING.md from main Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CONTRIBUTING.md | 851 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 851 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..182511d8ed --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,851 @@ +# Contributing to Azure MCP + +There are many ways to contribute to the Azure MCP project: reporting bugs, submitting pull requests, and creating suggestions. +After cloning and building the repo, check out the [GitHub project](https://github.com/orgs/Azure/projects/812/views/13) and [issues list](https://github.com/microsoft/mcp/issues). Issues labeled [help wanted](https://github.com/microsoft/mcp/labels/help%20wanted) are good issues to submit a PR for. Issues labeled [good first issue](https://github.com/microsoft/mcp/labels/good%20first%20issue) are great candidates to pick up if you are in the code for the first time. + +>[!IMPORTANT] +If you are contributing significant changes, or if the issue is already assigned to a specific milestone, please discuss with the assignee of the issue first before starting to work on the issue. + +## Table of Contents + +- [Contributing to Azure MCP](#contributing-to-azure-mcp) + - [Table of Contents](#table-of-contents) + - [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Project Structure](#project-structure) + - [Development Workflow](#development-workflow) + - [Development Process](#development-process) + - [Adding a New Command](#adding-a-new-command) + - [Testing](#testing) + - [Unit Tests](#unit-tests) + - [Cancellation plumbing](#cancellation-plumbing) + - [End-to-end Tests](#end-to-end-tests) + - [Testing Local Build with VS Code](#testing-local-build-with-vs-code) + - [Build the Server](#build-the-server) + - [Run the Azure MCP server in HTTP mode](#run-the-azure-mcp-server-in-http-mode) + - [Configure mcp.json](#configure-mcpjson) + - [Server Modes](#server-modes) + - [Start from IDE](#start-from-ide) + - [Testing Local Build with Docker](#testing-local-build-with-docker) + - [Live Tests](#live-tests) + - [NPX Live Tests](#npx-live-tests) + - [Recording Live Tests](#recording-live-tests) + - [Debugging Live Tests](#debugging-live-tests) + - [Quality and Standards](#quality-and-standards) + - [Code Style](#code-style) + - [Spelling Check](#spelling-check) + - [Requirements](#requirements) + - [AOT Compatibility Analysis](#aot-compatibility-analysis) + - [Running the Analysis](#running-the-analysis) + - [Installing Git Hooks](#installing-git-hooks) + - [Model Context Protocol (MCP)](#model-context-protocol-mcp) + - [Package README](#package-readme) + - [Advanced Configuration](#advanced-configuration) + - [Configuring External MCP Servers](#configuring-external-mcp-servers) + - [Registry Configuration](#registry-configuration) + - [Transport Types](#transport-types) + - [Server Discovery and Namespace Filtering](#server-discovery-and-namespace-filtering) + - [Adding New External MCP Servers](#adding-new-external-mcp-servers) + - [Example External Servers](#example-external-servers) + - [Project Management](#project-management) + - [Pull Request Process](#pull-request-process) + - [Builds and Releases (Internal)](#builds-and-releases-internal) + - [PR Validation](#pr-validation) + - [Support and Community](#support-and-community) + - [Questions and Support](#questions-and-support) + - [Additional Resources](#additional-resources) + - [Code of Conduct](#code-of-conduct) + - [License](#license) + +## Getting Started + +> [!IMPORTANT] +> If you are a **Microsoft employee** then please also review our [Azure Internal Onboarding Documentation](https://aka.ms/azmcp/intake) for getting setup + +### Prerequisites + +1. **VS Code**: Install either [stable](https://code.visualstudio.com/download) or [Insiders](https://code.visualstudio.com/insiders) release +2. **GitHub Copilot**: Install [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) and [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) extensions +3. **Node.js**: Install [Node.js](https://nodejs.org/en/download) 20 or later (ensure `node` and `npm` are in your PATH) +4. **PowerShell**: Install [PowerShell](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) 7.0 or later (required for build and test scripts) + +### Project Structure + +- `core\` + - `Azure.Mcp.Core` - Azure.Mcp.Core core library, depends on Microsoft.Mcp.Core + - `Fabric.Mcp.Core` - Fabric.Mcp.Core, depends on Azure.Mcp.Core (fabric uses azure) + - `Microsoft.Mcp.Core` - Microsoft.Mcp.Core library +- `servers\` + - `{server}.Mcp.Server - Individual servers (e.g. `Azure.Mcp.Server`, `Fabric.Mcp.Server`) + - `src` - Source for the server + - `tests` - Any unit or live tests for the server + - `README.md` - Specific readme for this server + - `CHANGELOG.md` - Specific changelog for this server +- `tools/` - Service-specific implementations + - `{server}.Mcp.Tools.{tool-name}/` - Individual server tools (e.g., `Azure.Mcp.Tools.KeyVault`, `Fabric.Mcp.Tools.Admin`) + - `src` - Service specific code + - `Commands/` - Command implementations + - `Models/` - Service specific models + - `Services/` - Service implementations and interfaces + - `Options/` - Service specific command options + - `tests/` - Service specific tests + - `{server}.Mcp.Tools.{tool-name}.UnitTests/` - Unit tests require no authentication or test resources + - `{server}.Mcp.Tools.{tool-name}.LiveTests/` - Live tests depend on Azure resources and authentication + - `test-resources.bicep` - Infrastructure templates for testing +- `eng/` - Shared tools, templates, CLI helpers +- `docs/` - Central documentation and onboarding materials + +## Development Workflow + +### Development Process + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Write or update tests +5. Test locally +6. Submit a pull request + +### Adding a New Command + +> [!TIP] +> **Submit One Tool Per Pull Request** +> +> We strongly recommend submitting **one tool per pull request** to streamline the review process and provide better onboarding experience. This approach results in: +> +> - **Faster reviews**: Single tools are easier and quicker to review +> - **Better feedback**: More focused discussions on individual tool implementation +> - **Easier iteration**: Smaller changes mean faster iteration cycles +> - **Incremental progress**: Get your first tool merged to establish baseline, then build upon it +> +> If you're planning to contribute multiple tools, please: +> +> 1. Submit your most important or representative tool as your first PR to establish the code patterns. +> 2. Use that baseline to inform your subsequent tool PRs. + +1. **Create an issue** with title: "Add command: azmcp [namespace] [resource] [operation]" and detailed description + +2. **Set up development environment**: + - Open VS Code Insiders + - Open the Copilot Chat view + - Select "Agent" mode + +3. **Generate the command** using Copilot: + + ```txt + Execute in Copilot Chat: + "create [namespace] [resource] [operation] command using #new-command.md as a reference" + ``` + +4. **Follow implementation guidelines** in [docs/new-command.md](https://github.com/microsoft/mcp/blob/main/servers/Azure.Mcp.Server/docs/new-command.md) + +5. **Update documentation**: + - Add the new command to [/servers/Azure.Mcp.Server/docs/azmcp-commands.md](https://github.com/microsoft/mcp/blob/main/servers/Azure.Mcp.Server/docs/azmcp-commands.md) + - Run `.\eng\scripts\Update-AzCommandsMetadata.ps1` to update tool metadata in azmcp-commands.md (required for CI) + - Add test prompts for the new command in [/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md](https://github.com/microsoft/mcp/blob/main/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md) + - Update [README.md](https://github.com/microsoft/mcp/blob/main/README.md) to mention the new command + +6. **Create a changelog entry** (if your change is a new feature, bug fix, or breaking change): + - Use the generator script to create a changelog entry (see `docs/changelog-entries.md` for details): + ```powershell + # Interactive mode (prompts for server) + ./eng/scripts/New-ChangelogEntry.ps1 + + # Or with all parameters + ./eng/scripts/New-ChangelogEntry.ps1 -ChangelogPath "servers/Azure.Mcp.Server/CHANGELOG.md" -Description -Section -PR + ./eng/scripts/New-ChangelogEntry.ps1 -ChangelogPath "servers/Fabric.Mcp.Server/CHANGELOG.md" -Description -Section -PR + ``` + - Or manually create a YAML file in `servers/{ServerName}/changelog-entries/` + - Not every PR needs a changelog entry - skip for internal refactoring, test-only changes, or minor updates. If unsure, add to the "Other Changes" section or ask a maintainer. + +7. **Add CODEOWNERS entry** in [CODEOWNERS](https://github.com/microsoft/mcp/blob/main/.github/CODEOWNERS) [(example)](https://github.com/microsoft/mcp/commit/08f73efe826d5d47c0f93be5ed9e614740e82091) + +8. **Add new tool to consolidated mode**: + - Open `core/Azure.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json` file, where the tool grouping definition is stored for consolidated mode. In Agent mode, add it to the chat as context. + - Paste the follow prompt for Copilot to generate the change to add the new tool: + ```txt + I have this list of tools which haven't been matched with any consolidated tools in this file. Help me add them to the one with the best matching category and exact matching toolMetadata. Update existing consolidated tools where newly mapped tools are added. If you can't find one, suggest a new consolidated tool. + + + ``` + - Use the following command to find out the correct tool name for your new tool + ``` + cd servers/Azure.Mcp.Server/src/bin/Debug/net10.0 + ./azmcp[.exe] tools list --name --namespace + ``` + - Commit the change. + +9. **Create Pull Request**: + - Reference the issue you created + - Include tests in the `/tests` folder + - Ensure all tests pass + - Follow code style requirements + - Run [`ToolDescriptionEvaluator`](https://github.com/microsoft/mcp/blob/main/eng/tools/ToolDescriptionEvaluator/Quickstart.md) for the new tool description and obtain a score of `0.4` or more and a top 3 ranking for all related test prompts + +## Testing + +Command authors must provide unit tests and end-to-end test prompts. Commands that interact with Azure resources **must** also include live tests with recorded playback coverage (see [Recording Live Tests](#recording-live-tests)). + +### Unit Tests + +Unit tests live under the `/tests` folder. To run tests: + +```pwsh +./eng/scripts/Test-Code.ps1 +``` + +To scope the test run to path substring matches, use: + +```pwsh +./eng/scripts/Test-Code.ps1 -Paths KeyVault, core/Azure +``` + +Requirements: + +- Each command should have unit tests + - The command unit tests should extend `CommandUnitTestsBase` +- Tests should cover success and error scenarios +- Mock external service calls +- Test argument validation + +#### Cancellation plumbing + +To ensure the product code and unit tests can be cancelled quickly, contributors are required to write async methods (any returning `Task`, `ValueTask`, generic variants of those, etc.) to accept and invoke async methods with a `System.Threading.CancellationToken` parameter. The latter is enforced with the [CA2016 analyzer](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2016). + +Mocks created with `NSubstitute.Substitue.For()` and have [methods set up](https://nsubstitute.github.io/help/set-return-value/#for-methods) should be passed `NSubstitute.Arg.Any()` for required `System.Threading.CancellationToken` parameters. The same should be used when [checking for received calls on a mocked object](https://nsubstitute.github.io/help/received-calls/index.html). If the product code is expected to do something interesting with a supplied `System.Threading.CancellationToken` parameter, such as linking with other `System.Threading.CancellationToken`s with [`System.Threading.CancellationTokenSource.CreateLinkedTokenSource`](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtokensource.createlinkedtokensource), then consider testing for that behavior. + +Real product code under unit testing must be passed `Xunit.TestContext.Current.CancellationToken` when async methods are invoked. This is to ensure the tests can end to avoid possible issues with the parent process waiting indefinitely for the test runner executable to exit. + +### End-to-end Tests + +End-to-end tests are performed manually. Command authors must thoroughly test each command to ensure correct tool invocation and results. At least one prompt per tool is required and should be added to `/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md`. + +### Testing Local Build with VS Code + +To run the Azure MCP server from source for local development: + +#### Build the Server + +Build the project at the root directory of this repository: + +```bash +dotnet build +``` + +#### Run the Azure MCP server in HTTP mode + +**Option 1: Using dotnet run (uses launchSettings.json)** + +**Prerequisites: Create launchSettings.json** + +> [!NOTE] +> Internal contributors may skip this step as the `launchSettings.json` file is already provided in the repository. + +Before running the server in HTTP mode, you need to create the `launchSettings.json` file with the `debug-remotemcp` profile: + +1. Create the directory (if it doesn't exist): + ```bash + mkdir -p servers/Azure.Mcp.Server/src/Properties + ``` + +2. Create `servers/Azure.Mcp.Server/src/Properties/launchSettings.json` with the following content: + ```json + { + "profiles": { + "debug-remotemcp": { + "commandName": "Project", + "commandLineArgs": "server start --transport http --outgoing-auth-strategy UseHostingEnvironmentIdentity", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:", + "AzureAd__TenantId": "", + "AzureAd__ClientId": "", + "AzureAd__Instance": "https://login.microsoftonline.com/" + } + } + } + } + ``` + +3. Replace `` and `` with your actual tenant ID and client ID. + +```bash +dotnet run --project servers/Azure.Mcp.Server/src/ --launch-profile debug-remotemcp +``` + +**Option 2: Using the built executable directly** + +Build the project first, then run the executable with the necessary environment variables: + +```powershell +# Set environment variables (PowerShell) +$env:ASPNETCORE_ENVIRONMENT = "Development" +$env:ASPNETCORE_URLS = "http://localhost:" +$env:AzureAd__TenantId = "" +$env:AzureAd__ClientId = "" +$env:AzureAd__Instance = "https://login.microsoftonline.com/" + +# Run the executable +./servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp.exe server start --transport http --outgoing-auth-strategy UseHostingEnvironmentIdentity +``` + +```bash +# Set environment variables (Bash) +export ASPNETCORE_ENVIRONMENT="Development" +export ASPNETCORE_URLS="http://localhost:" +export AzureAd__TenantId="" +export AzureAd__ClientId="" +export AzureAd__Instance="https://login.microsoftonline.com/" + +# Run the executable +./servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp server start --transport http --outgoing-auth-strategy UseHostingEnvironmentIdentity +``` + +> **Note:** The environment variables listed above are taken from the `debug-remotemcp` profile in `launchSettings.json`. Replace `` and `` with your actual Azure AD tenant ID and client ID. These variables configure Azure AD authentication and the server endpoint for HTTP mode operation. +> +> For local development, when running with HTTPS (either via `ASPNETCORE_URLS` or HTTPS redirection), you must generate a self-signed development certificate: +> +>**Windows and macOS:** +> ```bash +> dotnet dev-certs https --trust +> ``` +> +> **Linux:** +> ```bash +> dotnet dev-certs https +> ``` +> +> On Linux, you must manually trust the generated certificate. See the [official documentation](https://learn.microsoft.com/dotnet/core/tools/dotnet-dev-certs) for instructions on how to do this. + +#### Configure mcp.json + +Update your mcp.json to point to the locally built azmcp executable. + +**Stdio Mode:** + +```json +{ + "servers": { + "azure-mcp-server": { + "type": "stdio", + "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", + "args": ["server", "start"] + } + } +} +``` + +**HTTP Mode:** + +```json +{ + "servers": { + "Azure MCP Server": { + "url": "https://localhost:1031/", + "type": "http" + } + } +} +``` + +> [!NOTE] +> For stdio mode, replace `` with the full path to your built executable. +> On **Windows**, use `azmcp.exe`. On **macOS/Linux**, use `azmcp`. +> For HTTP mode, ensure the server is running on the specified port before connecting (port 1031 is the default port configured in launchSettings.json). + +#### Server Modes + +Optional `--namespace` and `--mode` parameters can be used to configure different server modes: + +**Default Mode** (no additional parameters): + +```json +{ + "servers": { + "azure-mcp-server": { + "type": "stdio", + "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", + "args": ["server", "start"] + } + } +} +``` + +**Namespace Mode** (expose specific services): + +```json +{ + "servers": { + "azure-mcp-server": { + "type": "stdio", + "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", + "args": ["server", "start", "--namespace", "storage", "--namespace", "keyvault"] + } + } +} +``` + +**Namespace Proxy Mode** (collapse tools by namespace): + +```json +{ + "servers": { + "azure-mcp-server": { + "type": "stdio", + "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", + "args": ["server", "start", "--mode", "namespace"] + } + } +} +``` + +**Single Tool Proxy Mode** (single "azure" tool with internal routing): + +```json +{ + "servers": { + "azure-mcp-server": { + "type": "stdio", + "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", + "args": ["server", "start", "--mode", "single"] + } + } +} +``` + +**Consolidated Mode** (grouped related operations): +It honors both --read-only and --namespace switches. + +```json +{ + "servers": { + "azure-mcp-server": { + "type": "stdio", + "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", + "args": ["server", "start", "--mode", "consolidated"] + } + } +} +``` + +**Combined Mode** (filter namespaces with any mode): + +```json +{ + "servers": { + "azure-mcp-server": { + "type": "stdio", + "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", + "args": ["server", "start", "--namespace", "storage", "--namespace", "keyvault", "--mode", "namespace"] + } + } +} +``` + +**Specific Tool Mode** (expose only specific tools): + +```json +{ + "servers": { + "azure-mcp-server": { + "type": "stdio", + "command": "/mcp/servers/Azure.Mcp.Server/src/bin/Debug/net10.0/azmcp[.exe]", + "args": ["server", "start", "--tool", "azmcp_storage_account_get", "--tool", "azmcp_subscription_list"] + } + } +} +``` + +> **Server Mode Summary:** +> +> - **Default Mode (Namespace)**: No additional parameters - collapses tools by namespace (current default) +> - **Consolidated Mode**: `--mode consolidated` - exposes consolidated tools grouping related operations, optimized for AI agents. +> - **Namespace Mode**: `--namespace ` - expose specific services +> - **Namespace Proxy Mode**: `--mode namespace` - collapse tools by namespace (useful for VS Code's 128 tool limit) +> - **All Tools Mode**: `--mode all` - expose all ~800+ individual tools +> - **Single Tool Mode**: `--mode single` - single "azure" tool with internal routing +> - **Specific Tool Mode**: `--tool ` - expose only specific tools by name (finest granularity) +> - **Combined Mode**: Multiple options can be used together (`--namespace` + `--mode` etc.) + +#### Start from IDE + +With the configuration in place, you can launch the MCP server directly from your IDE or any tooling that uses `mcp.json`. + +### Testing Local Build with Docker + +To build a local image for testing purposes: + +1. Execute: `./eng/scripts/Build-Docker.ps1 -ServerName "Azure.Mcp.Server"`. +2. Update `mcp.json` to point to locally built Docker image: + + ```json + { + "servers": { + "Azure MCP Server": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "--env-file", + "/full/path/to/.env" + "azure-sdk/azure-mcp:", + ] + } + } + } + ``` + +### Live Tests + +> [!IMPORTANT] +> Live tests are **required** for all commands that interact with Azure resources. +> +> If you are a **Microsoft employee** with Azure source permissions then please review our [Azure Internal Onboarding Documentation](https://aka.ms/azmcp/intake). As part of reviewing community contributions, Azure team members can run live tests by adding this comment to the PR `/azp run mcp - pullrequest - live`. + +Before running live tests: + +- [Install Azure PowerShell](https://learn.microsoft.com/powershell/azure/install-azure-powershell) +- [Install Azure Bicep](https://learn.microsoft.com/azure/azure-resource-manager/bicep/install#install-manually) +- Login to Azure PowerShell: [`Connect-AzAccount`](https://learn.microsoft.com/powershell/azure/authenticate-interactive?view=azps-13.4.0) +- Deploy test resources: + +```pwsh +./eng/scripts/Deploy-TestResources.ps1 -Paths KeyVault +./eng/scripts/Deploy-TestResources.ps1 -Paths Storage, core/Azure +``` + +**Deploy-TestResources.ps1 Parameters:** + +| Parameter | Type | Description | +|---------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------| +| `Paths` | string[] | REQUIRED. Path filters to apply (e.g., `Storage`, `core/`). All filters are applied as substring matches. (e.g., `*Storage*`, `*core/*`) | +| `SubscriptionId` | string | Target subscription ID. If omitted, the current Azure context subscription (from `Get-AzContext`) is used. | +| `ResourceGroupName` | string | Resource group name. Defaults to `{username}-mcp{hash(username)}`. | +| `BaseName` | string | Base name prefix for resources. Defaults to `mcp{hash}`. | +| `Unique` | switch | Use a unique GUID-based hash for this invocation instead of the stable username+subscription hash. | +| `DeleteAfterHours` | int | Hours after which resources are tagged for deletion. Defaults to `12`. | + +Examples: + +```pwsh +# Deploy Storage test resources using current Azure context subscription +./eng/scripts/Deploy-TestResources.ps1 -Paths Storage + +# Deploy Key Vault test resources to a specific subscription and keep for one week +./eng/scripts/Deploy-TestResources.ps1 -Paths KeyVault -SubscriptionId -DeleteAfterHours 168 -Unique +``` + +After deploying test resources, you should have a `.testsettings.json` file with your deployment information in the deployed paths' `/tests` directory. + +Run live tests with: + +```pwsh +./eng/scripts/Test-Code.ps1 -TestType Live +``` + +You can scope tests to specific paths: + +```pwsh +./eng/scripts/Test-Code.ps1 -TestType Live -Paths Storage, KeyVault +``` + +### NPX Live Tests + +You can set the `TestPackage` parameter in `.testsettings.json` to have live tests run `npx` targeting an arbitrary Azure MCP package: + +```json +{ + "TenantId": "a20062a8-ff76-41c2-8a6d-5e843da7b051", + "TenantName": "Your Tenant", + "SubscriptionId": "cd27afdc-9976-4f08-96e9-cad120a91560", + "SubscriptionName": "Your Subscription", + "ResourceGroupName": "rg-abcdefg", + "ResourceBaseName": "t1234567890", + "TestPackage": "@azure/mcp@0.0.10" +} +``` + +To run live tests against the local build of an npm module: + +```pwsh +./eng/scripts/Build-Local.ps1 +``` + +This will produce .tgz files in the `.dist` directory and set the `TestPackage` parameter in the `.testsettings.json` file: + +```json +"TestPackage": "file://D:\\repos\\azure-mcp\\.dist\\wrapper\\azure-mcp-0.0.12-alpha.1746488279.tgz" +``` + +### Recording Live Tests + +All new live tests **must** be recorded for playback. Live tests use the Azure SDK Test Proxy to capture and replay HTTP traffic, ensuring CI can validate tests without live Azure resources. For the full migration guide, recording workflow, sanitizer/matcher configuration, and troubleshooting tips, see [docs/recorded-tests.md](https://github.com/microsoft/mcp/blob/main/docs/recorded-tests.md). + +### Debugging Live Tests + +This section assumes that the necessary Azure resources for live tests are already deployed and that the `.testsettings.json` file with deployment information is located in the area's `/tests/` directory. + +To debug the Azure MCP Server (`azmcp`) when running live tests in VS Code: + +1. Build the package with debug symbols: `./eng/scripts/Build-Local.ps1` +2. Set a breakpoint in a command file (e.g., [`KeyValueListCommand.ExecuteAsync`](https://github.com/microsoft/mcp/blob/4ed650a0507921273acc7b382a79049809ef39c1/src/Commands/AppConfig/KeyValue/KeyValueListCommand.cs#L48)) +3. In VS Code, navigate to a test method (e.g., [`AppConfigCommandTests::Should_list_appconfig_kvs()`](https://github.com/microsoft/mcp/blob/4ed650a0507921273acc7b382a79049809ef39c1/tests/Client/AppConfigCommandTests.cs#L56)), add a breakpoint to `CallToolAsync` call in the test method, then right-click and select **Debug Test** +4. Find the `azmcp` process ID: + + ```shell + pgrep -fl azmcp + ``` + + ```powershell + Get-Process | Where-Object { $_.ProcessName -like "*azmcp*" } | Select-Object Id, ProcessName, Path + ``` + +5. Open the Command Palette (`Cmd+Shift+P` on Mac, `Ctrl+Shift+P` on Windows/Linux), select **Debug: Attach to .NET 5+ or .NET Core process**, and enter the `azmcp` process ID +6. Hit F5 to "Continue" debugging, the debugger should attach to `azmcp` and hit the breakpoint in command file + +## Quality and Standards + +### Code Style + +To ensure consistent code quality, code format checks will run during all PR and CI builds. Run `dotnet format` before submitting to catch format errors early. + +#### Spelling Check + +To ensure consistent spelling across the codebase, run the spelling check before submitting: + +```pwsh +.\eng\common\spelling\Invoke-Cspell.ps1 +``` + +This will check all files for spelling errors using the project's dictionary. Add any new technical terms or proper nouns to `.vscode/cspell.json` if needed. + +#### Requirements + +- Follow C# coding conventions +- No comments in implementation code (code should be self-documenting) +- Use descriptive variable and method names +- Follow the exact file structure and naming conventions +- Use proper error handling patterns +- XML documentation for public APIs +- Follow Model Context Protocol (MCP) patterns + +### AOT Compatibility Analysis + +The AOT compatibility analysis helps identify potential issues that might prevent the Azure MCP Server from working correctly when compiled with AOT or when trimming is enabled. + +#### Running the Analysis + +To run the AOT compatibility analysis locally: + +```pwsh +./eng/scripts/Analyze-AOT-Compact.ps1 +``` + +The HTML report will be generated at `.work/aotCompactReport/aot-compact-report.html` and automatically opened in your default browser. + +To output the report to console, run the analysis with `-OutputFormat Console` argument. + +AOT compatibility warnings typically indicate: + +- Use of reflection without proper annotations +- Serialization of types that might be trimmed +- Dynamic code generation +- Use of `RequiresUnreferencedCodeAttribute` methods without proper precautions + +#### Installing Git Hooks + +You can install our pre-push hook to catch code format issues by automatically running `dotnet format` before each `git push`: + +- `./eng/scripts/Install-GitHooks.ps1` - Installs the pre-push hook into your local repo +- `./eng/scripts/Remove-GitHooks.ps1` - Disables any git hooks in your local repo + +### Model Context Protocol (MCP) + +The Azure MCP Server implements the [Model Context Protocol specification](https://modelcontextprotocol.io). When adding new commands: + +- Follow MCP JSON schema patterns +- Implement proper context handling +- Use standardized response formats +- Handle errors according to MCP specifications +- Provide proper argument suggestions + +### Package README + +A single package README.md could be used to generate context specific content for different package types (npm, nuget, vsix) using html comment annotations to mark sections for removal or insertion whem processed with script at `.\eng\scripts\Process-PackageReadMe.ps1` + +Supported comment annotations: + +- Section Removal + - **Purpose:** Remove one or more lines, or parts of a line of markdown for specified package types. + - **Example:** + ``` + + ...... + various markdown lines to be removed for nuget and npm + ...... + + ``` + +- Section Insert + - **Purpose:** Insert a chunk of text into a line for a specified package type. + - **Example:** + `` + +You can verify that your README.md was annotated correctly using the `Validate-PackageReadme` function: +``` +& "eng\scripts\Process-PackageReadMe.ps1" -Command "validate" -InputReadMePath "" +``` + +To extract README.md for a specific package, run the `Extract-PackageSpecificReadMe` function: +``` +& "eng\scripts\Process-PackageReadMe.ps1" -Command "extract" ` + -InputReadMePath "" ` + -OutputDirectory "" ` + -PackageType "" ` + -InsertPayload "" +``` + +## Advanced Configuration + +### Configuring External MCP Servers + +The Azure MCP Server supports connecting to external MCP servers through an embedded `registry.json` configuration file. This enables the server to act as a proxy, aggregating tools from multiple MCP servers into a single interface. The registry follows the same configuration schema as VS Code's `mcp.json`. + +#### Registry Configuration + +External MCP servers are defined in the embedded resource file `core/Azure.Mcp.Core/src/Areas/Server/Resources/registry.json`. This file contains server configurations that support both SSE (Server-Sent Events) and stdio transport mechanisms, following the standard MCP configuration format. + +The registry structure follows this format: + +```json +{ + "servers": { + "documentation": { + "url": "https://learn.microsoft.com/api/mcp", + "description": "Search official Microsoft/Azure documentation..." + }, + "another-server": { + "type": "stdio", + "command": "path/to/executable", + "args": ["arg1", "arg2"], + "env": { + "ENV_VAR": "value" + }, + "description": "Another MCP server using stdio transport" + } + } +} +``` + +#### Transport Types + +**SSE (Server-Sent Events) Transport:** + +- Use the `url` property to specify the endpoint +- Supports HTTP-based communication with automatic transport mode detection +- Best for web-based MCP servers and remote endpoints + +**Stdio Transport:** + +- Use `type: "stdio"` with the `command` property +- Supports launching external processes that communicate via standard input/output +- Use `args` array for command-line arguments +- Use `env` object for environment variables +- Best for local executables, command-line tools, and local MCP servers + +#### Server Discovery and Namespace Filtering + +External servers are automatically discovered when the Azure MCP Server starts. They can be filtered using the same namespace mechanisms as built-in commands: + +```bash +# Include only specific external servers +azmcp server start --namespace documentation --namespace another-server + +# Use namespace mode to group tools exposed by external servers +azmcp server start --mode namespace +``` + +#### Adding New External MCP Servers + +To add a new external MCP server to the registry: + +1. Edit `core/Azure.Mcp.Core/src/Areas/Server/Resources/registry.json` +2. Add your server configuration under the `servers` object using VS Code's MCP configuration schema +3. Use a unique identifier as the key +4. Provide either a `url` for SSE transport or `type: "stdio"` with `command` for stdio transport +5. Include a descriptive `description` field +6. Rebuild the project to embed the updated registry + +#### Example External Servers + +The current registry includes: + +- **documentation**: Microsoft Learn documentation search via SSE transport +- Additional external servers can be added following the same pattern as VS Code's mcp.json + +External servers integrate seamlessly with the Azure MCP Server's tool aggregation, appearing alongside native Azure commands in the unified tool interface. This allows you to combine local MCP servers, remote MCP endpoints, and Azure-specific tools in a single interface. + +## Project Management + +### Pull Request Process + +1. Update documentation reflecting any changes +2. Add or update tests as needed +3. Reference the original issue +4. Wait for review and address any feedback + +### Builds and Releases (Internal) + +The internal pipeline [azure-mcp](https://dev.azure.com/azure-sdk/internal/_build?definitionId=7571) is used for all official releases and CI builds. On every merge to main, a build will run and will produce a dynamically named prerelease package on the public dev feed, e.g. [@azure/mcp@0.0.10-beta.4799791](https://dev.azure.com/azure-sdk/public/_artifacts/feed/azure-sdk-for-js/Npm/@azure%2Fmcp/overview/0.0.10-beta.4799791). + +Only manual runs of the pipeline sign and publish packages. Building `main` or `hotfix/*` will publish to `npmjs.com`, all other refs will publish to the [public dev feed](https://dev.azure.com/azure-sdk/public/_artifacts/feed/azure-sdk-for-js). + +Packages published to npmjs.com will always use the `@latest` [dist-tag](https://docs.npmjs.com/downloading-and-installing-packages-locally#installing-a-package-with-dist-tags). + +Packages published to the dev feed will use: + +- `@latest` for the latest official/release build +- `@dev` for the latest CI build of main +- `@pre` for any arbitrary pipeline run or feature branch build + +#### PR Validation + +To run live tests for a PR, inspect the PR code for any suspicious changes, then add the comment `/azp run mcp - pullrequest - live` to the pull request. This will queue a PR triggered run which will build, run unit tests, deploy test resources and run live tests. + +If you would like to see the product of a PR as a package on the dev feed, after thoroughly inspecting the change, create a branch in the main repo and manually trigger an [azure - mcp](https://dev.azure.com/azure-sdk/internal/_build?definitionId=7571) pipeline run against that branch. This will queue a manually triggered run which will build, run unit tests, deploy test resources, run live tests, sign and publish the packages to the dev feed. + +Instructions for consuming the package from the dev feed can be found in the "Extensions" tab of the pipeline run page. + +**CI Validation Checks**: + +All PRs automatically run the following validation checks: +- **Code formatting** - Ensures code follows project formatting standards +- **Spelling check** - Validates spelling across the codebase +- **AOT compatibility** - Checks ahead-of-time compilation compatibility +- **Tool metadata verification** - Ensures `azmcp-commands.md` is up-to-date with tool metadata (run `.\eng\scripts\Update-AzCommandsMetadata.ps1` if this fails) + +## Support and Community + +Please see our [support](https://github.com/microsoft/mcp/blob/main/SUPPORT.md) statement. + +### Questions and Support + +We're building this in the open. Your feedback is much appreciated, and will help us shape the future of the Azure MCP server. + +πŸ‘‰ [Open an issue in the public repository](https://github.com/microsoft/mcp/issues/new/choose). + +### Additional Resources + +- [Azure MCP Documentation](https://github.com/microsoft/mcp/blob/main/README.md) +- [Command Implementation Guide](https://github.com/microsoft/mcp/blob/main/servers/Azure.Mcp.Server/docs/new-command.md) +- [VS Code Insiders Download](https://code.visualstudio.com/insiders/) +- [GitHub Copilot Documentation](https://docs.github.com/en/copilot) + +### Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. By participating, you are expected to uphold this code. + +### License + +By contributing, you agree that your contributions will be licensed under the project's [license](https://github.com/microsoft/mcp/blob/main/LICENSE). From a534e07fdf5347d255a6a6240bb0220733d48b33 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 21:34:03 -0700 Subject: [PATCH 12/21] Replace placeholder command IDs with proper GUIDs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Commands/Dataflow/CreateDataflowCommand.cs | 2 +- .../src/Commands/Dataflow/ListDataflowsCommand.cs | 2 +- .../src/Commands/Pipeline/CreatePipelineCommand.cs | 2 +- .../src/Commands/Pipeline/GetPipelineCommand.cs | 2 +- .../src/Commands/Pipeline/ListPipelinesCommand.cs | 2 +- .../src/Commands/Pipeline/RunPipelineCommand.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs index 4b3b7e0154..4daa9e9434 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/CreateDataflowCommand.cs @@ -15,7 +15,7 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; [CommandMetadata( - Id = "a1b2c3d4-2001-4000-8000-000000000002", + Id = "98c74b3b-1813-40db-9abe-30ad90beb236", Name = "create-dataflow", Title = "Create Dataflow", Description = "Creates a new dataflow in a specified Microsoft Fabric workspace.", diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs index 5f86e45060..9688437623 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ListDataflowsCommand.cs @@ -15,7 +15,7 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; [CommandMetadata( - Id = "a1b2c3d4-2001-4000-8000-000000000001", + Id = "07025647-7bd3-4e80-82eb-13bbd603d076", Name = "list-dataflows", Title = "List Dataflows", Description = "Lists all dataflows in a specified Microsoft Fabric workspace.", diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs index abfc1b5311..626d5223e5 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/CreatePipelineCommand.cs @@ -15,7 +15,7 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; [CommandMetadata( - Id = "a1b2c3d4-1001-4000-8000-000000000003", + Id = "faf493a8-0c7d-4347-854a-a85120dd8199", Name = "create-pipeline", Title = "Create Pipeline", Description = "Creates a new pipeline in a Microsoft Fabric workspace. Requires workspace ID and display name. Optionally provide a description.", diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs index 10b343a232..d7c3b07946 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/GetPipelineCommand.cs @@ -15,7 +15,7 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; [CommandMetadata( - Id = "a1b2c3d4-1001-4000-8000-000000000004", + Id = "0d8e07d4-63c2-4a96-b364-01d5db6e660b", Name = "get-pipeline", Title = "Get Pipeline", Description = "Gets details of a specific pipeline in a Microsoft Fabric workspace. Requires workspace ID and pipeline ID.", diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs index be3bf8ef6f..bd1b597668 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/ListPipelinesCommand.cs @@ -15,7 +15,7 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; [CommandMetadata( - Id = "a1b2c3d4-1001-4000-8000-000000000002", + Id = "365d2efc-ce25-4a32-afb1-294daf6e462b", Name = "list-pipelines", Title = "List Pipelines", Description = "Lists all pipelines in a specified Microsoft Fabric workspace. Requires the workspace ID.", diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs index eac94e5671..f3c581772b 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Pipeline/RunPipelineCommand.cs @@ -15,7 +15,7 @@ namespace Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; [CommandMetadata( - Id = "a1b2c3d4-1001-4000-8000-000000000005", + Id = "be699abf-2297-4328-be8c-e93f045236f2", Name = "run-pipeline", Title = "Run Pipeline", Description = "Triggers a run of a specified pipeline in a Microsoft Fabric workspace. Requires workspace ID and pipeline ID. Returns the run instance ID.", From 4267ed2a8511cd9cd742a73dd7a49bbf7d53027b Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 10:35:57 -0700 Subject: [PATCH 13/21] Add execute-query command and update NuGet to 0.19.1-beta - Add ExecuteQueryCommand for executing M (Power Query) expressions against dataflows - Add ExecuteQueryOptions, DataflowId/QueryName/QueryText option definitions - Register ExecuteQueryCommand in DI and command group - Add ExecuteQueryCommandResult to JSON source generation context - Update Microsoft.DataFactory.MCP.Core package version to 0.19.1-beta Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Packages.props | 2 +- .../Commands/Dataflow/ExecuteQueryCommand.cs | 87 +++++++++++++++++++ .../src/DataFactoryAreaSetup.cs | 3 + .../src/Models/DataFactoryJsonContext.cs | 2 + .../Options/DataFactoryOptionDefinitions.cs | 21 +++++ .../Options/Dataflow/ExecuteQueryOptions.cs | 14 +++ 6 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ExecuteQueryCommand.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/ExecuteQueryOptions.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 59cab15458..b50521dec2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -84,7 +84,7 @@ - + diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ExecuteQueryCommand.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ExecuteQueryCommand.cs new file mode 100644 index 0000000000..f0bd46df42 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Commands/Dataflow/ExecuteQueryCommand.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Fabric.Mcp.Tools.DataFactory.Models; +using Fabric.Mcp.Tools.DataFactory.Options; +using Fabric.Mcp.Tools.DataFactory.Options.Dataflow; +using global::DataFactory.MCP.Handlers.Dataflow; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Extensions; +using Microsoft.Mcp.Core.Models.Command; +using Microsoft.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; + +[CommandMetadata( + Id = "b4e2a1f9-6c3d-4a8e-9f12-d5c7b8e3a041", + Name = "execute-query", + Title = "Execute Dataflow Query", + Description = "Executes an M (Power Query) expression against a dataflow in a Microsoft Fabric workspace.", + Destructive = false, + Idempotent = true, + ReadOnly = true, + OpenWorld = false)] +public sealed class ExecuteQueryCommand( + ILogger logger, + DataflowQueryHandler handler) : GlobalCommand() +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly DataflowQueryHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(DataFactoryOptionDefinitions.WorkspaceId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.DataflowId.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.QueryName.AsRequired()); + command.Options.Add(DataFactoryOptionDefinitions.QueryText.AsRequired()); + } + + protected override ExecuteQueryOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.WorkspaceId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.WorkspaceIdName) ?? string.Empty; + options.DataflowId = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.DataflowIdName) ?? string.Empty; + options.QueryName = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.QueryNameName) ?? string.Empty; + options.Query = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.QueryTextName) ?? string.Empty; + 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); + + var result = await _handler.ExecuteQueryAsync( + options.WorkspaceId, + options.DataflowId, + options.QueryName, + options.Query); + + if (result.IsSuccess) + { + _logger.LogInformation("Successfully executed query '{QueryName}' on dataflow {DataflowId}", + options.QueryName, options.DataflowId); + + var commandResult = new ExecuteQueryCommandResult( + result.Value!.Success, + result.Value.Data, + result.Value.Summary); + context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.ExecuteQueryCommandResult); + } + else + { + _logger.LogError("Error executing query '{QueryName}' on dataflow {DataflowId}: {Error}", + options.QueryName, options.DataflowId, result.Error); + HandleException(context, new Exception(result.Error)); + } + + return context.Response; + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs index c4325c3574..dd049b4d5b 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs @@ -27,6 +27,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } public CommandGroup RegisterCommands(IServiceProvider serviceProvider) @@ -38,6 +39,7 @@ public CommandGroup RegisterCommands(IServiceProvider serviceProvider) - List and manage workspaces - Create, get, list, and run pipelines - Work with dataflows and data transformations + - Execute M (Power Query) expressions against dataflows """); group.AddCommand(serviceProvider); @@ -46,6 +48,7 @@ public CommandGroup RegisterCommands(IServiceProvider serviceProvider) group.AddCommand(serviceProvider); group.AddCommand(serviceProvider); group.AddCommand(serviceProvider); + group.AddCommand(serviceProvider); return group; } diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs index 3f641338c2..085bf0285a 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Models/DataFactoryJsonContext.cs @@ -15,6 +15,7 @@ namespace Fabric.Mcp.Tools.DataFactory.Models; [JsonSerializable(typeof(RunPipelineCommandResult))] [JsonSerializable(typeof(ListDataflowsCommandResult))] [JsonSerializable(typeof(CreateDataflowCommandResult))] +[JsonSerializable(typeof(ExecuteQueryCommandResult))] public partial class DataFactoryJsonContext : JsonSerializerContext { } @@ -25,3 +26,4 @@ public sealed record GetPipelineCommandResult(Pipeline Pipeline); public sealed record RunPipelineCommandResult(string? RunId); public sealed record ListDataflowsCommandResult(List Dataflows, int TotalCount); public sealed record CreateDataflowCommandResult(global::DataFactory.MCP.Models.Dataflow.Dataflow Dataflow); +public sealed record ExecuteQueryCommandResult(bool Success, object? Data, global::DataFactory.MCP.Models.Dataflow.Query.QueryResultSummary? Summary); diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs index bb9b41872c..70d736ff74 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/DataFactoryOptionDefinitions.cs @@ -21,6 +21,27 @@ public static class DataFactoryOptionDefinitions Required = true }; + public const string DataflowIdName = "dataflow-id"; + public static readonly Option DataflowId = new($"--{DataflowIdName}") + { + Description = "The ID of the dataflow.", + Required = true + }; + + public const string QueryNameName = "query-name"; + public static readonly Option QueryName = new($"--{QueryNameName}") + { + Description = "The name of the query to execute.", + Required = true + }; + + public const string QueryTextName = "query"; + public static readonly Option QueryText = new($"--{QueryTextName}") + { + Description = "The M (Power Query) expression to execute.", + Required = true + }; + public const string DisplayNameName = "display-name"; public static readonly Option DisplayName = new($"--{DisplayNameName}") { diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/ExecuteQueryOptions.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/ExecuteQueryOptions.cs new file mode 100644 index 0000000000..0957c52c4c --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/Options/Dataflow/ExecuteQueryOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace Fabric.Mcp.Tools.DataFactory.Options.Dataflow; + +public sealed class ExecuteQueryOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; + public string DataflowId { get; set; } = string.Empty; + public string QueryName { get; set; } = string.Empty; + public string Query { get; set; } = string.Empty; +} From 6b79eb0e12de860d599a1bfe1063796751cafa2b Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 11:22:56 -0700 Subject: [PATCH 14/21] chore: bump Microsoft.DataFactory.MCP.Core to 0.20.0-beta Updated to version with source-generated JSON for full ILLinker trim safety (zero IL2026 warnings). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b50521dec2..144fefd84a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -84,7 +84,7 @@ - + From d2c5e422954357324c5047657b4367e97161c131 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 12:49:43 -0700 Subject: [PATCH 15/21] Use DataFactory.MCP.Core 0.19.1-beta (available on DevOps feed) 0.20.0-beta exists on nuget.org but hasn't propagated to the azure-sdk-for-net DevOps feed upstream yet. Using 0.19.1-beta to unblock CI; will bump once 0.20.0-beta is cached. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 144fefd84a..b50521dec2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -84,7 +84,7 @@ - + From 7ad44165d71a122df06ce6de32b79c525c7f7df5 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 13:11:02 -0700 Subject: [PATCH 16/21] Bump DataFactory.MCP.Core to 0.20.0-beta Package is now available on the azure-sdk-for-net DevOps feed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b50521dec2..144fefd84a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -84,7 +84,7 @@ - + From fd459b91240cd48585e83ee27c18f55e82ab18a3 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 13:25:56 -0700 Subject: [PATCH 17/21] Fix smoke test: redirect console logs to stderr The smoke test runs 'fabmcp tools list' and parses stdout as JSON. AuthenticationStateManager logs an info message during DI construction, which pollutes stdout and causes ConvertFrom-Json to fail. Match Azure.Mcp.Server pattern: LogToStandardErrorThreshold = LogLevel.Trace sends all console logs to stderr, keeping stdout clean for JSON output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- servers/Fabric.Mcp.Server/src/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servers/Fabric.Mcp.Server/src/Program.cs b/servers/Fabric.Mcp.Server/src/Program.cs index 86bc125420..ad5eeb2d7f 100644 --- a/servers/Fabric.Mcp.Server/src/Program.cs +++ b/servers/Fabric.Mcp.Server/src/Program.cs @@ -36,7 +36,7 @@ private static async Task Main(string[] args) services.AddLogging(builder => { - builder.AddConsole(); + builder.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace); builder.SetMinimumLevel(LogLevel.Information); }); From 216f72971c24fb5f43132f4c6ce6ede024b313ff Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 14:09:50 -0700 Subject: [PATCH 18/21] Fix analyze: update solution files and import ordering - Regenerate Microsoft.Mcp.slnx and Fabric.Mcp.Server.slnx via Update-Solution.ps1 - Fix IMPORTS ordering in DataFactoryAreaSetup.cs (dotnet format) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Microsoft.Mcp.slnx | 4 ++++ servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx | 8 ++++---- .../src/DataFactoryAreaSetup.cs | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Microsoft.Mcp.slnx b/Microsoft.Mcp.slnx index 0b94242f82..3b4557ebc8 100644 --- a/Microsoft.Mcp.slnx +++ b/Microsoft.Mcp.slnx @@ -505,6 +505,10 @@ + + + + diff --git a/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx b/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx index 7af191a04b..33804a8c20 100644 --- a/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx +++ b/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx @@ -28,6 +28,10 @@ + + + + @@ -42,8 +46,4 @@ - - - - diff --git a/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs b/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs index dd049b4d5b..ecf95f53fc 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs +++ b/tools/Fabric.Mcp.Tools.DataFactory/src/DataFactoryAreaSetup.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using global::DataFactory.MCP.Extensions; using Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; using Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; +using global::DataFactory.MCP.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Mcp.Core.Areas; using Microsoft.Mcp.Core.Commands; From d721b92daa44a6469c528bac6fd53c3745a4938f Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 15:06:17 -0700 Subject: [PATCH 19/21] Add unit tests for DataFactory tools Create test project Fabric.Mcp.Tools.DataFactory.UnitTests with 45 tests: - DataFactoryAreaSetupTests: name, title, service registration - Command tests for all 7 commands: constructor, metadata, options, null guards - Covers Pipeline (list, create, get, run) and Dataflow (list, create, execute-query) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Microsoft.Mcp.slnx | 3 + .../Fabric.Mcp.Server/Fabric.Mcp.Server.slnx | 3 + .../Dataflow/CreateDataflowCommandTests.cs | 69 +++++++++++++++++++ .../Dataflow/ExecuteQueryCommandTests.cs | 69 +++++++++++++++++++ .../Dataflow/ListDataflowsCommandTests.cs | 69 +++++++++++++++++++ .../Pipeline/CreatePipelineCommandTests.cs | 69 +++++++++++++++++++ .../Pipeline/GetPipelineCommandTests.cs | 69 +++++++++++++++++++ .../Pipeline/ListPipelinesCommandTests.cs | 69 +++++++++++++++++++ .../Pipeline/RunPipelineCommandTests.cs | 69 +++++++++++++++++++ .../DataFactoryAreaSetupTests.cs | 52 ++++++++++++++ ...ric.Mcp.Tools.DataFactory.UnitTests.csproj | 28 ++++++++ 11 files changed, 569 insertions(+) create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/CreateDataflowCommandTests.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/ExecuteQueryCommandTests.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/ListDataflowsCommandTests.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/CreatePipelineCommandTests.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/GetPipelineCommandTests.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/ListPipelinesCommandTests.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/RunPipelineCommandTests.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/DataFactoryAreaSetupTests.cs create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Fabric.Mcp.Tools.DataFactory.UnitTests.csproj diff --git a/Microsoft.Mcp.slnx b/Microsoft.Mcp.slnx index 3b4557ebc8..5638f922ef 100644 --- a/Microsoft.Mcp.slnx +++ b/Microsoft.Mcp.slnx @@ -509,6 +509,9 @@ + + + diff --git a/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx b/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx index 33804a8c20..49f15aa967 100644 --- a/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx +++ b/servers/Fabric.Mcp.Server/Fabric.Mcp.Server.slnx @@ -32,6 +32,9 @@ + + + diff --git a/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/CreateDataflowCommandTests.cs b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/CreateDataflowCommandTests.cs new file mode 100644 index 0000000000..1b52525501 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/CreateDataflowCommandTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; +using global::DataFactory.MCP.Handlers.Dataflow; +using Microsoft.Extensions.Logging; +using NSubstitute; + +namespace Fabric.Mcp.Tools.DataFactory.UnitTests.Commands.Dataflow; + +public class CreateDataflowCommandTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly DataflowHandler _handler = new(Substitute.For()); + private readonly CreateDataflowCommand _command; + + public CreateDataflowCommandTests() + { + _command = new CreateDataflowCommand(_logger, _handler); + } + + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + Assert.Equal("create-dataflow", _command.Name); + Assert.Equal("Create Dataflow", _command.Title); + Assert.Contains("Creates a new dataflow", _command.Description); + } + + [Fact] + public void Metadata_HasCorrectProperties() + { + var metadata = _command.Metadata; + Assert.False(metadata.ReadOnly); + Assert.False(metadata.Idempotent); + Assert.False(metadata.Destructive); + Assert.False(metadata.OpenWorld); + Assert.False(metadata.Secret); + Assert.False(metadata.LocalRequired); + } + + [Fact] + public void GetCommand_ReturnsValidCommand() + { + var cmd = _command.GetCommand(); + Assert.Equal("create-dataflow", cmd.Name); + Assert.NotNull(cmd.Description); + } + + [Fact] + public void CommandOptions_ContainsRequiredOptions() + { + var cmd = _command.GetCommand(); + Assert.NotEmpty(cmd.Options); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenLoggerIsNull() + { + Assert.Throws(() => new CreateDataflowCommand(null!, _handler)); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenHandlerIsNull() + { + Assert.Throws(() => new CreateDataflowCommand(_logger, null!)); + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/ExecuteQueryCommandTests.cs b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/ExecuteQueryCommandTests.cs new file mode 100644 index 0000000000..c590110bec --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/ExecuteQueryCommandTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; +using global::DataFactory.MCP.Handlers.Dataflow; +using Microsoft.Extensions.Logging; +using NSubstitute; + +namespace Fabric.Mcp.Tools.DataFactory.UnitTests.Commands.Dataflow; + +public class ExecuteQueryCommandTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly DataflowQueryHandler _handler = new(Substitute.For()); + private readonly ExecuteQueryCommand _command; + + public ExecuteQueryCommandTests() + { + _command = new ExecuteQueryCommand(_logger, _handler); + } + + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + Assert.Equal("execute-query", _command.Name); + Assert.Equal("Execute Dataflow Query", _command.Title); + Assert.Contains("Executes an M (Power Query) expression", _command.Description); + } + + [Fact] + public void Metadata_HasCorrectProperties() + { + var metadata = _command.Metadata; + Assert.True(metadata.ReadOnly); + Assert.True(metadata.Idempotent); + Assert.False(metadata.Destructive); + Assert.False(metadata.OpenWorld); + Assert.False(metadata.Secret); + Assert.False(metadata.LocalRequired); + } + + [Fact] + public void GetCommand_ReturnsValidCommand() + { + var cmd = _command.GetCommand(); + Assert.Equal("execute-query", cmd.Name); + Assert.NotNull(cmd.Description); + } + + [Fact] + public void CommandOptions_ContainsRequiredOptions() + { + var cmd = _command.GetCommand(); + Assert.NotEmpty(cmd.Options); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenLoggerIsNull() + { + Assert.Throws(() => new ExecuteQueryCommand(null!, _handler)); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenHandlerIsNull() + { + Assert.Throws(() => new ExecuteQueryCommand(_logger, null!)); + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/ListDataflowsCommandTests.cs b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/ListDataflowsCommandTests.cs new file mode 100644 index 0000000000..4821391577 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Dataflow/ListDataflowsCommandTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; +using global::DataFactory.MCP.Handlers.Dataflow; +using Microsoft.Extensions.Logging; +using NSubstitute; + +namespace Fabric.Mcp.Tools.DataFactory.UnitTests.Commands.Dataflow; + +public class ListDataflowsCommandTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly DataflowHandler _handler = new(Substitute.For()); + private readonly ListDataflowsCommand _command; + + public ListDataflowsCommandTests() + { + _command = new ListDataflowsCommand(_logger, _handler); + } + + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + Assert.Equal("list-dataflows", _command.Name); + Assert.Equal("List Dataflows", _command.Title); + Assert.Contains("Lists all dataflows", _command.Description); + } + + [Fact] + public void Metadata_HasCorrectProperties() + { + var metadata = _command.Metadata; + Assert.True(metadata.ReadOnly); + Assert.True(metadata.Idempotent); + Assert.False(metadata.Destructive); + Assert.False(metadata.OpenWorld); + Assert.False(metadata.Secret); + Assert.False(metadata.LocalRequired); + } + + [Fact] + public void GetCommand_ReturnsValidCommand() + { + var cmd = _command.GetCommand(); + Assert.Equal("list-dataflows", cmd.Name); + Assert.NotNull(cmd.Description); + } + + [Fact] + public void CommandOptions_ContainsRequiredOptions() + { + var cmd = _command.GetCommand(); + Assert.NotEmpty(cmd.Options); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenLoggerIsNull() + { + Assert.Throws(() => new ListDataflowsCommand(null!, _handler)); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenHandlerIsNull() + { + Assert.Throws(() => new ListDataflowsCommand(_logger, null!)); + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/CreatePipelineCommandTests.cs b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/CreatePipelineCommandTests.cs new file mode 100644 index 0000000000..269b035ea5 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/CreatePipelineCommandTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; +using global::DataFactory.MCP.Handlers.Pipeline; +using Microsoft.Extensions.Logging; +using NSubstitute; + +namespace Fabric.Mcp.Tools.DataFactory.UnitTests.Commands.Pipeline; + +public class CreatePipelineCommandTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly PipelineHandler _handler = new(Substitute.For()); + private readonly CreatePipelineCommand _command; + + public CreatePipelineCommandTests() + { + _command = new CreatePipelineCommand(_logger, _handler); + } + + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + Assert.Equal("create-pipeline", _command.Name); + Assert.Equal("Create Pipeline", _command.Title); + Assert.Contains("Creates a new pipeline", _command.Description); + } + + [Fact] + public void Metadata_HasCorrectProperties() + { + var metadata = _command.Metadata; + Assert.False(metadata.ReadOnly); + Assert.False(metadata.Idempotent); + Assert.False(metadata.Destructive); + Assert.False(metadata.OpenWorld); + Assert.False(metadata.Secret); + Assert.False(metadata.LocalRequired); + } + + [Fact] + public void GetCommand_ReturnsValidCommand() + { + var cmd = _command.GetCommand(); + Assert.Equal("create-pipeline", cmd.Name); + Assert.NotNull(cmd.Description); + } + + [Fact] + public void CommandOptions_ContainsRequiredOptions() + { + var cmd = _command.GetCommand(); + Assert.NotEmpty(cmd.Options); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenLoggerIsNull() + { + Assert.Throws(() => new CreatePipelineCommand(null!, _handler)); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenHandlerIsNull() + { + Assert.Throws(() => new CreatePipelineCommand(_logger, null!)); + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/GetPipelineCommandTests.cs b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/GetPipelineCommandTests.cs new file mode 100644 index 0000000000..0669789520 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/GetPipelineCommandTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; +using global::DataFactory.MCP.Handlers.Pipeline; +using Microsoft.Extensions.Logging; +using NSubstitute; + +namespace Fabric.Mcp.Tools.DataFactory.UnitTests.Commands.Pipeline; + +public class GetPipelineCommandTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly PipelineHandler _handler = new(Substitute.For()); + private readonly GetPipelineCommand _command; + + public GetPipelineCommandTests() + { + _command = new GetPipelineCommand(_logger, _handler); + } + + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + Assert.Equal("get-pipeline", _command.Name); + Assert.Equal("Get Pipeline", _command.Title); + Assert.Contains("Gets details of a specific pipeline", _command.Description); + } + + [Fact] + public void Metadata_HasCorrectProperties() + { + var metadata = _command.Metadata; + Assert.True(metadata.ReadOnly); + Assert.True(metadata.Idempotent); + Assert.False(metadata.Destructive); + Assert.False(metadata.OpenWorld); + Assert.False(metadata.Secret); + Assert.False(metadata.LocalRequired); + } + + [Fact] + public void GetCommand_ReturnsValidCommand() + { + var cmd = _command.GetCommand(); + Assert.Equal("get-pipeline", cmd.Name); + Assert.NotNull(cmd.Description); + } + + [Fact] + public void CommandOptions_ContainsRequiredOptions() + { + var cmd = _command.GetCommand(); + Assert.NotEmpty(cmd.Options); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenLoggerIsNull() + { + Assert.Throws(() => new GetPipelineCommand(null!, _handler)); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenHandlerIsNull() + { + Assert.Throws(() => new GetPipelineCommand(_logger, null!)); + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/ListPipelinesCommandTests.cs b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/ListPipelinesCommandTests.cs new file mode 100644 index 0000000000..9a7e9757f9 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/ListPipelinesCommandTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; +using global::DataFactory.MCP.Handlers.Pipeline; +using Microsoft.Extensions.Logging; +using NSubstitute; + +namespace Fabric.Mcp.Tools.DataFactory.UnitTests.Commands.Pipeline; + +public class ListPipelinesCommandTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly PipelineHandler _handler = new(Substitute.For()); + private readonly ListPipelinesCommand _command; + + public ListPipelinesCommandTests() + { + _command = new ListPipelinesCommand(_logger, _handler); + } + + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + Assert.Equal("list-pipelines", _command.Name); + Assert.Equal("List Pipelines", _command.Title); + Assert.Contains("Lists all pipelines", _command.Description); + } + + [Fact] + public void Metadata_HasCorrectProperties() + { + var metadata = _command.Metadata; + Assert.True(metadata.ReadOnly); + Assert.True(metadata.Idempotent); + Assert.False(metadata.Destructive); + Assert.False(metadata.OpenWorld); + Assert.False(metadata.Secret); + Assert.False(metadata.LocalRequired); + } + + [Fact] + public void GetCommand_ReturnsValidCommand() + { + var cmd = _command.GetCommand(); + Assert.Equal("list-pipelines", cmd.Name); + Assert.NotNull(cmd.Description); + } + + [Fact] + public void CommandOptions_ContainsRequiredOptions() + { + var cmd = _command.GetCommand(); + Assert.NotEmpty(cmd.Options); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenLoggerIsNull() + { + Assert.Throws(() => new ListPipelinesCommand(null!, _handler)); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenHandlerIsNull() + { + Assert.Throws(() => new ListPipelinesCommand(_logger, null!)); + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/RunPipelineCommandTests.cs b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/RunPipelineCommandTests.cs new file mode 100644 index 0000000000..a11049804d --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Commands/Pipeline/RunPipelineCommandTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; +using global::DataFactory.MCP.Handlers.Pipeline; +using Microsoft.Extensions.Logging; +using NSubstitute; + +namespace Fabric.Mcp.Tools.DataFactory.UnitTests.Commands.Pipeline; + +public class RunPipelineCommandTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly PipelineHandler _handler = new(Substitute.For()); + private readonly RunPipelineCommand _command; + + public RunPipelineCommandTests() + { + _command = new RunPipelineCommand(_logger, _handler); + } + + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + Assert.Equal("run-pipeline", _command.Name); + Assert.Equal("Run Pipeline", _command.Title); + Assert.Contains("Triggers a run of a specified pipeline", _command.Description); + } + + [Fact] + public void Metadata_HasCorrectProperties() + { + var metadata = _command.Metadata; + Assert.False(metadata.ReadOnly); + Assert.False(metadata.Idempotent); + Assert.False(metadata.Destructive); + Assert.False(metadata.OpenWorld); + Assert.False(metadata.Secret); + Assert.False(metadata.LocalRequired); + } + + [Fact] + public void GetCommand_ReturnsValidCommand() + { + var cmd = _command.GetCommand(); + Assert.Equal("run-pipeline", cmd.Name); + Assert.NotNull(cmd.Description); + } + + [Fact] + public void CommandOptions_ContainsRequiredOptions() + { + var cmd = _command.GetCommand(); + Assert.NotEmpty(cmd.Options); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenLoggerIsNull() + { + Assert.Throws(() => new RunPipelineCommand(null!, _handler)); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_WhenHandlerIsNull() + { + Assert.Throws(() => new RunPipelineCommand(_logger, null!)); + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/DataFactoryAreaSetupTests.cs b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/DataFactoryAreaSetupTests.cs new file mode 100644 index 0000000000..a28cc16fd5 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/DataFactoryAreaSetupTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Fabric.Mcp.Tools.DataFactory; +using Fabric.Mcp.Tools.DataFactory.Commands.Dataflow; +using Fabric.Mcp.Tools.DataFactory.Commands.Pipeline; +using Microsoft.Extensions.DependencyInjection; + +namespace Fabric.Mcp.Tools.DataFactory.UnitTests; + +public class DataFactoryAreaSetupTests +{ + [Fact] + public void Name_ReturnsCorrectValue() + { + // Arrange + var setup = new DataFactoryAreaSetup(); + + // Act & Assert + Assert.Equal("datafactory", setup.Name); + } + + [Fact] + public void Title_ReturnsCorrectValue() + { + // Arrange + var setup = new DataFactoryAreaSetup(); + + // Act & Assert + Assert.Equal("Microsoft Fabric Data Factory", setup.Title); + } + + [Fact] + public void ConfigureServices_RegistersCommandTypes() + { + // Arrange + var services = new ServiceCollection(); + var setup = new DataFactoryAreaSetup(); + + // Act + setup.ConfigureServices(services); + + // Assert + Assert.Contains(services, s => s.ServiceType == typeof(ListPipelinesCommand)); + Assert.Contains(services, s => s.ServiceType == typeof(CreatePipelineCommand)); + Assert.Contains(services, s => s.ServiceType == typeof(GetPipelineCommand)); + Assert.Contains(services, s => s.ServiceType == typeof(RunPipelineCommand)); + Assert.Contains(services, s => s.ServiceType == typeof(ListDataflowsCommand)); + Assert.Contains(services, s => s.ServiceType == typeof(CreateDataflowCommand)); + Assert.Contains(services, s => s.ServiceType == typeof(ExecuteQueryCommand)); + } +} diff --git a/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Fabric.Mcp.Tools.DataFactory.UnitTests.csproj b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Fabric.Mcp.Tools.DataFactory.UnitTests.csproj new file mode 100644 index 0000000000..4af9b49acf --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Fabric.Mcp.Tools.DataFactory.UnitTests.csproj @@ -0,0 +1,28 @@ + + + + false + true + + + + + + + + + + + + + + + + + + + + + + + From cf30cfa6ddfcc30579d74699a279f790885ccd8a Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Mon, 11 May 2026 08:24:47 -0700 Subject: [PATCH 20/21] docs: add DataFactory README, update CHANGELOG and server README - Create tools/Fabric.Mcp.Tools.DataFactory/README.md with full docs - Add Data Factory tools entry to CHANGELOG under 2.0.0-beta.1 - Add Data Factory Operations table to server README Available Tools - Add TOC entry and overview bullet for Data Factory integration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- servers/Fabric.Mcp.Server/CHANGELOG.md | 4 + servers/Fabric.Mcp.Server/README.md | 14 +++ tools/Fabric.Mcp.Tools.DataFactory/README.md | 112 +++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 tools/Fabric.Mcp.Tools.DataFactory/README.md diff --git a/servers/Fabric.Mcp.Server/CHANGELOG.md b/servers/Fabric.Mcp.Server/CHANGELOG.md index 6a60b59ebf..84d0703c9c 100644 --- a/servers/Fabric.Mcp.Server/CHANGELOG.md +++ b/servers/Fabric.Mcp.Server/CHANGELOG.md @@ -9,10 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features Added +- **Data Factory Tools**: Added 7 new commands for managing Microsoft Fabric Data Factory resources through MCP. Includes pipeline operations (list, create, get, run) and Dataflow Gen2 operations (list, create, execute query). Powered by the [Microsoft.DataFactory.MCP.Core](https://www.nuget.org/packages/Microsoft.DataFactory.MCP.Core) NuGet package. [[#2584](https://github.com/microsoft/mcp/pull/2584)] + ### Breaking Changes ### Bugs Fixed +- Fixed console logging polluting stdout which caused smoke test failures on macOS. Console logs are now redirected to stderr via `LogToStandardErrorThreshold`. [[#2584](https://github.com/microsoft/mcp/pull/2584)] + ### Other Changes ## 1.0.0 (2026-04-14) diff --git a/servers/Fabric.Mcp.Server/README.md b/servers/Fabric.Mcp.Server/README.md index fc16e1586e..aade145c49 100644 --- a/servers/Fabric.Mcp.Server/README.md +++ b/servers/Fabric.Mcp.Server/README.md @@ -35,6 +35,7 @@ A local-first Model Context Protocol (MCP) server that provides AI agents with c - [API Documentation & Best Practices](#api-documentation--best-practices) - [OneLake Data Operations](#onelake-data-operations) - [Core Fabric Operations](#core-fabric-operations) + - [Data Factory Operations](#data-factory-operations) - [Support and Reference](#support-and-reference) - [Documentation](#documentation) - [Feedback and Support](#feedback-and-support) @@ -53,6 +54,7 @@ Key capabilities: - **Item Definition Knowledge**: JSON schemas for every Fabric item type (Lakehouses, pipelines, semantic models, notebooks, etc.) - **Built-in Best Practices**: Embedded guidance on pagination, error handling, and recommended patterns - **Local-First Security**: Runs entirely on your machineβ€”never connects to your Fabric environment +- **Data Factory Integration**: Pipeline and Dataflow Gen2 management with M query execution # Installation @@ -260,6 +262,18 @@ The Fabric MCP Server exposes tools organized into three categories: |-----------|-------------| | `core_create-item` | Creates new Fabric items (Lakehouses, Notebooks, etc.). | +### Data Factory Operations + +| Tool Name | Description | +|-----------|-------------| +| `datafactory_list-pipelines` | Lists all pipelines in a Microsoft Fabric workspace. | +| `datafactory_create-pipeline` | Creates a new pipeline in a workspace. | +| `datafactory_get-pipeline` | Gets details of a specific pipeline. | +| `datafactory_run-pipeline` | Runs a pipeline on demand. | +| `datafactory_list-dataflows` | Lists all Dataflow Gen2 items in a workspace. | +| `datafactory_create-dataflow` | Creates a new Dataflow Gen2 item. | +| `datafactory_execute-query` | Executes an M (Power Query) query against a dataflow. | + > Always verify available commands via `--help`. Command names and availability may change between releases. diff --git a/tools/Fabric.Mcp.Tools.DataFactory/README.md b/tools/Fabric.Mcp.Tools.DataFactory/README.md new file mode 100644 index 0000000000..3798b254e8 --- /dev/null +++ b/tools/Fabric.Mcp.Tools.DataFactory/README.md @@ -0,0 +1,112 @@ +# Fabric.Mcp.Tools.DataFactory + +Microsoft Fabric Data Factory MCP (Model Context Protocol) Tools β€” Manage pipelines and dataflows through AI agents and MCP clients. + +## Overview + +Data Factory is Microsoft Fabric's cloud-scale data integration service for creating, scheduling, and orchestrating ETL/ELT workflows. This MCP tool provides operations for working with Data Factory resources, enabling AI agents to: + +- List, create, and get pipeline details +- Run pipelines on demand +- List and create Dataflow Gen2 items +- Execute M (Power Query) queries against dataflows + +**Features:** +- 7 Data Factory commands with full MCP integration +- Pipeline management: list, create, get, run +- Dataflow Gen2 management: list, create +- Dataflow query execution with M/Power Query support +- Robust error handling via `ToolResult` pattern +- Unit tested with 45 tests covering metadata, constructors, and options + +## Prerequisites + +- Microsoft Fabric workspace with Data Factory capabilities +- Azure authentication (Azure CLI or managed identity) +- Access to the target Fabric workspace + +## Authentication + +The tool uses Azure authentication via the Fabric MCP Server's authentication infrastructure. Ensure you're logged in: + +```bash +az login +``` + +## Available Commands + +### Pipeline Commands + +| Command | Description | Read Only | +|---------|-------------|-----------| +| `datafactory_list-pipelines` | Lists all pipelines in a workspace | βœ“ | +| `datafactory_create-pipeline` | Creates a new pipeline | βœ— | +| `datafactory_get-pipeline` | Gets details of a specific pipeline | βœ“ | +| `datafactory_run-pipeline` | Runs a pipeline on demand | βœ— | + +### Dataflow Commands + +| Command | Description | Read Only | +|---------|-------------|-----------| +| `datafactory_list-dataflows` | Lists all Dataflow Gen2 items in a workspace | βœ“ | +| `datafactory_create-dataflow` | Creates a new Dataflow Gen2 item | βœ— | +| `datafactory_execute-query` | Executes an M query against a dataflow | βœ“ | + +## Example Prompts + +- "List all pipelines in my workspace" +- "Create a new pipeline called 'Daily ETL'" +- "Run the pipeline with ID abc-123 in workspace xyz" +- "List all dataflows in my workspace" +- "Execute this M query against my dataflow: `let Source = Sql.Database(\"server\", \"db\") in Source`" + +## Architecture + +The DataFactory tools are built on the [Microsoft.DataFactory.MCP.Core](https://www.nuget.org/packages/Microsoft.DataFactory.MCP.Core) NuGet package, which provides: + +- **Handlers**: `PipelineHandler`, `DataflowHandler`, `DataflowQueryHandler` β€” business logic with comprehensive error handling +- **Services**: HTTP clients for Fabric REST API communication +- **Models**: Strongly-typed DTOs for API request/response + +Commands follow the MCP framework pattern: +``` +Command (MCP schema + validation) β†’ Handler (business logic + error handling) β†’ Service (HTTP API call) +``` + +## Development + +### Building + +```bash +cd tools/Fabric.Mcp.Tools.DataFactory +dotnet build src/Fabric.Mcp.Tools.DataFactory.csproj +``` + +### Testing + +```bash +dotnet test tests/Fabric.Mcp.Tools.DataFactory.UnitTests/Fabric.Mcp.Tools.DataFactory.UnitTests.csproj +``` + +### Project Structure + +``` +tools/Fabric.Mcp.Tools.DataFactory/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ Commands/ +β”‚ β”‚ β”œβ”€β”€ Pipeline/ # Pipeline CRUD + run commands +β”‚ β”‚ └── Dataflow/ # Dataflow CRUD + query commands +β”‚ β”œβ”€β”€ Models/ # Result DTOs and JSON context +β”‚ β”œβ”€β”€ Options/ # Command option definitions +β”‚ └── DataFactoryAreaSetup.cs # DI registration +└── tests/ + └── Fabric.Mcp.Tools.DataFactory.UnitTests/ + β”œβ”€β”€ DataFactoryAreaSetupTests.cs + └── Commands/ # Per-command unit tests +``` + +## Related + +- [Microsoft Fabric Data Factory documentation](https://learn.microsoft.com/fabric/data-factory/) +- [DataFactory.MCP.Core NuGet package](https://www.nuget.org/packages/Microsoft.DataFactory.MCP.Core) +- [Fabric MCP Server](../../servers/Fabric.Mcp.Server/README.md) From 0737bd53f81b9ad241d69b2de964ffc310d0c093 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Mon, 11 May 2026 09:48:11 -0700 Subject: [PATCH 21/21] docs: fix broken relative link, remove PR refs from CHANGELOG - Replace relative path with absolute GitHub URL in DataFactory README - Remove [[#2584]] references from CHANGELOG (added by CI after merge) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- servers/Fabric.Mcp.Server/CHANGELOG.md | 4 ++-- tools/Fabric.Mcp.Tools.DataFactory/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/servers/Fabric.Mcp.Server/CHANGELOG.md b/servers/Fabric.Mcp.Server/CHANGELOG.md index 84d0703c9c..a6c1b5b8b3 100644 --- a/servers/Fabric.Mcp.Server/CHANGELOG.md +++ b/servers/Fabric.Mcp.Server/CHANGELOG.md @@ -9,13 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features Added -- **Data Factory Tools**: Added 7 new commands for managing Microsoft Fabric Data Factory resources through MCP. Includes pipeline operations (list, create, get, run) and Dataflow Gen2 operations (list, create, execute query). Powered by the [Microsoft.DataFactory.MCP.Core](https://www.nuget.org/packages/Microsoft.DataFactory.MCP.Core) NuGet package. [[#2584](https://github.com/microsoft/mcp/pull/2584)] +- **Data Factory Tools**: Added 7 new commands for managing Microsoft Fabric Data Factory resources through MCP. Includes pipeline operations (list, create, get, run) and Dataflow Gen2 operations (list, create, execute query). Powered by the [Microsoft.DataFactory.MCP.Core](https://www.nuget.org/packages/Microsoft.DataFactory.MCP.Core) NuGet package. ### Breaking Changes ### Bugs Fixed -- Fixed console logging polluting stdout which caused smoke test failures on macOS. Console logs are now redirected to stderr via `LogToStandardErrorThreshold`. [[#2584](https://github.com/microsoft/mcp/pull/2584)] +- Fixed console logging polluting stdout which caused smoke test failures on macOS. Console logs are now redirected to stderr via `LogToStandardErrorThreshold`. ### Other Changes diff --git a/tools/Fabric.Mcp.Tools.DataFactory/README.md b/tools/Fabric.Mcp.Tools.DataFactory/README.md index 3798b254e8..7e5d1d4c69 100644 --- a/tools/Fabric.Mcp.Tools.DataFactory/README.md +++ b/tools/Fabric.Mcp.Tools.DataFactory/README.md @@ -109,4 +109,4 @@ tools/Fabric.Mcp.Tools.DataFactory/ - [Microsoft Fabric Data Factory documentation](https://learn.microsoft.com/fabric/data-factory/) - [DataFactory.MCP.Core NuGet package](https://www.nuget.org/packages/Microsoft.DataFactory.MCP.Core) -- [Fabric MCP Server](../../servers/Fabric.Mcp.Server/README.md) +- [Fabric MCP Server](https://github.com/microsoft/mcp/tree/main/servers/Fabric.Mcp.Server)