From 51f7747ad4e00fb6b4c844f450e1b6e3f5bf57c7 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 13:01:17 -0700 Subject: [PATCH 01/27] Make DataFactory.MCP.Core AOT-compatible Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Configuration/JsonSerializerOptionsProvider.cs | 1 - DataFactory.MCP.Core/DataFactory.MCP.Core.csproj | 3 +++ .../Extensions/HttpResponseMessageExtensions.cs | 6 ++++++ DataFactory.MCP.Core/Models/Capacity/Capacity.cs | 2 +- DataFactory.MCP.Core/Models/Capacity/CapacityState.cs | 2 +- .../Models/Connection/ConnectionEnums.cs | 10 +++++----- .../Models/Connection/ConnectionJsonConverter.cs | 4 +++- .../Models/Gateway/GatewayJsonConverter.cs | 2 ++ DataFactory.MCP.Core/Models/Workspace/WorkspaceType.cs | 2 +- 9 files changed, 22 insertions(+), 10 deletions(-) diff --git a/DataFactory.MCP.Core/Configuration/JsonSerializerOptionsProvider.cs b/DataFactory.MCP.Core/Configuration/JsonSerializerOptionsProvider.cs index 7e66fa9d..8fd7d55b 100644 --- a/DataFactory.MCP.Core/Configuration/JsonSerializerOptionsProvider.cs +++ b/DataFactory.MCP.Core/Configuration/JsonSerializerOptionsProvider.cs @@ -23,7 +23,6 @@ public static class JsonSerializerOptionsProvider DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Converters = { - new JsonStringEnumConverter(), new ConnectionJsonConverter(), new GatewayJsonConverter() } diff --git a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj index ffb90496..f1b7f9a7 100644 --- a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj +++ b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj @@ -10,6 +10,9 @@ Microsoft.DataFactory.MCP.Core AI; MCP; server; library Core library for DataFactory MCP server - contains services, tools, and models. + true + true + true Microsoft-Fabric.png README.md diff --git a/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs b/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs index 256c78e0..f4fc9ba5 100644 --- a/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs @@ -50,6 +50,7 @@ public static class HttpResponseMessageExtensions /// Cancellation token /// The deserialized object /// Thrown when the response indicates failure +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public static async Task ReadAsJsonAsync( this HttpResponseMessage response, JsonSerializerOptions? options = null, @@ -66,6 +67,7 @@ public static class HttpResponseMessageExtensions return JsonSerializer.Deserialize(content, options ?? JsonSerializerOptionsProvider.FabricApi); } +#pragma warning restore IL2026, IL3050 /// /// Reads and deserializes the response content as JSON, returning a default value on failure. @@ -77,6 +79,7 @@ public static class HttpResponseMessageExtensions /// JSON serializer options (uses FabricApi options if null) /// Cancellation token /// The deserialized object or default value on failure +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public static async Task ReadAsJsonOrDefaultAsync( this HttpResponseMessage response, T defaultValue, @@ -98,6 +101,7 @@ public static async Task ReadAsJsonOrDefaultAsync( return JsonSerializer.Deserialize(content, options ?? JsonSerializerOptionsProvider.FabricApi) ?? defaultValue; } +#pragma warning restore IL2026, IL3050 /// /// Ensures the response is successful, throwing a detailed FabricApiException on failure. @@ -135,6 +139,7 @@ public static async Task EnsureSuccessOrThrowAsync( /// /// Tries to read the response as JSON, returning success/failure result. /// +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public static async Task<(bool Success, T? Value, FabricApiException? Error)> TryReadAsJsonAsync( this HttpResponseMessage response, JsonSerializerOptions? options = null, @@ -156,6 +161,7 @@ public static async Task EnsureSuccessOrThrowAsync( var value = JsonSerializer.Deserialize(content, options ?? JsonSerializerOptionsProvider.FabricApi); return (true, value, null); } +#pragma warning restore IL2026, IL3050 /// /// Checks if the response indicates a transient failure that could be retried. diff --git a/DataFactory.MCP.Core/Models/Capacity/Capacity.cs b/DataFactory.MCP.Core/Models/Capacity/Capacity.cs index 6d0256e7..1746ef10 100644 --- a/DataFactory.MCP.Core/Models/Capacity/Capacity.cs +++ b/DataFactory.MCP.Core/Models/Capacity/Capacity.cs @@ -35,6 +35,6 @@ public class Capacity /// The capacity state /// [JsonPropertyName("state")] - [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public CapacityState State { get; set; } } \ No newline at end of file diff --git a/DataFactory.MCP.Core/Models/Capacity/CapacityState.cs b/DataFactory.MCP.Core/Models/Capacity/CapacityState.cs index cb56b5e8..a9cea24c 100644 --- a/DataFactory.MCP.Core/Models/Capacity/CapacityState.cs +++ b/DataFactory.MCP.Core/Models/Capacity/CapacityState.cs @@ -5,7 +5,7 @@ namespace DataFactory.MCP.Models.Capacity; /// /// A capacity state. Additional capacity states may be added over time. /// -[JsonConverter(typeof(JsonStringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum CapacityState { /// diff --git a/DataFactory.MCP.Core/Models/Connection/ConnectionEnums.cs b/DataFactory.MCP.Core/Models/Connection/ConnectionEnums.cs index d52fc5ad..99f5682e 100644 --- a/DataFactory.MCP.Core/Models/Connection/ConnectionEnums.cs +++ b/DataFactory.MCP.Core/Models/Connection/ConnectionEnums.cs @@ -5,7 +5,7 @@ namespace DataFactory.MCP.Models.Connection; /// /// The connectivity type of the connection /// -[JsonConverter(typeof(JsonStringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum ConnectivityType { ShareableCloud, @@ -20,7 +20,7 @@ public enum ConnectivityType /// /// The credential type of the connection /// -[JsonConverter(typeof(JsonStringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum CredentialType { Windows, @@ -37,7 +37,7 @@ public enum CredentialType /// /// The connection encryption type of the connection /// -[JsonConverter(typeof(JsonStringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum ConnectionEncryption { Encrypted, @@ -48,7 +48,7 @@ public enum ConnectionEncryption /// /// The privacy level setting of the connection /// -[JsonConverter(typeof(JsonStringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum PrivacyLevel { None, @@ -60,7 +60,7 @@ public enum PrivacyLevel /// /// The single sign-on type of the connection /// -[JsonConverter(typeof(JsonStringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum SingleSignOnType { None, diff --git a/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs b/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs index ca52b4f4..41632d84 100644 --- a/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs +++ b/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs @@ -6,6 +6,7 @@ namespace DataFactory.MCP.Models.Connection; /// /// JSON converter for handling polymorphic Connection types based on ConnectivityType /// +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public class ConnectionJsonConverter : JsonConverter { public override Connection? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -45,4 +46,5 @@ public override void Write(Utf8JsonWriter writer, Connection value, JsonSerializ jsonOptions.Converters.Remove(this); // Prevent infinite recursion JsonSerializer.Serialize(writer, value, value.GetType(), jsonOptions); } -} \ No newline at end of file +} +#pragma warning restore IL2026, IL3050 \ No newline at end of file diff --git a/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs b/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs index 7e1ee756..2232d89b 100644 --- a/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs +++ b/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs @@ -6,6 +6,7 @@ namespace DataFactory.MCP.Models.Gateway; /// /// Custom JSON converter for Gateway polymorphic deserialization /// +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public class GatewayJsonConverter : JsonConverter { public override Gateway Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -36,3 +37,4 @@ public override void Write(Utf8JsonWriter writer, Gateway value, JsonSerializerO JsonSerializer.Serialize(writer, value, value.GetType(), options); } } +#pragma warning restore IL2026, IL3050 diff --git a/DataFactory.MCP.Core/Models/Workspace/WorkspaceType.cs b/DataFactory.MCP.Core/Models/Workspace/WorkspaceType.cs index 204bae87..148062b4 100644 --- a/DataFactory.MCP.Core/Models/Workspace/WorkspaceType.cs +++ b/DataFactory.MCP.Core/Models/Workspace/WorkspaceType.cs @@ -5,7 +5,7 @@ namespace DataFactory.MCP.Models.Workspace; /// /// A workspace type. Additional workspace types may be added over time. /// -[JsonConverter(typeof(JsonStringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum WorkspaceType { /// From 0843119e8eb48bb16f89d7efb329b5540301dd17 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 14:02:12 -0700 Subject: [PATCH 02/27] Add DataFactory.MCP.Fabric adapter project for Fabric MCP Server integration Create a new adapter project that bridges DataFactory.MCP.Core services to Microsoft.Mcp.Core's IAreaSetup/GlobalCommand pattern used by Fabric.Mcp.Server. - DataFactoryAreaSetup implements IAreaSetup (single-line integration) - 5 commands: ListWorkspaces, ListPipelines, CreatePipeline, GetPipeline, RunPipeline - AOT-compatible with JsonSerializerContext source generators - Delegates all business logic to Core services (no duplication) - Follows exact Fabric.Mcp.Tools.Core pattern Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Commands/CreatePipelineCommand.cs | 101 ++++++++++++++++++ .../Commands/GetPipelineCommand.cs | 80 ++++++++++++++ .../Commands/ListPipelinesCommand.cs | 76 +++++++++++++ .../Commands/ListWorkspacesCommand.cs | 75 +++++++++++++ .../Commands/RunPipelineCommand.cs | 80 ++++++++++++++ .../DataFactory.MCP.Fabric.csproj | 17 +++ .../DataFactoryAreaSetup.cs | 49 +++++++++ DataFactory.MCP.Fabric/GlobalUsings.cs | 3 + .../Models/DataFactoryJsonContext.cs | 26 +++++ .../Options/DataFactoryOptionDefinitions.cs | 44 ++++++++ 10 files changed, 551 insertions(+) create mode 100644 DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs create mode 100644 DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs create mode 100644 DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs create mode 100644 DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs create mode 100644 DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs create mode 100644 DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj create mode 100644 DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs create mode 100644 DataFactory.MCP.Fabric/GlobalUsings.cs create mode 100644 DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs create mode 100644 DataFactory.MCP.Fabric/Options/DataFactoryOptionDefinitions.cs diff --git a/DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs new file mode 100644 index 00000000..6cd1bc70 --- /dev/null +++ b/DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Fabric.Models; +using DataFactory.MCP.Fabric.Options; +using 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 DataFactory.MCP.Fabric.Commands; + +[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 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; + } +} + +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/DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs new file mode 100644 index 00000000..1ff35d35 --- /dev/null +++ b/DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Fabric.Models; +using DataFactory.MCP.Fabric.Options; +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 DataFactory.MCP.Fabric.Commands; + +[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; + } +} + +public sealed class GetPipelineOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; + public string PipelineId { get; set; } = string.Empty; +} diff --git a/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs b/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs new file mode 100644 index 00000000..3cd6718d --- /dev/null +++ b/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Fabric.Models; +using DataFactory.MCP.Fabric.Options; +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 DataFactory.MCP.Fabric.Commands; + +[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, + 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()); + } + + 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); + try + { + var response = await _pipelineService.ListPipelinesAsync(options.WorkspaceId); + + _logger.LogInformation("Successfully listed {Count} pipelines in workspace {WorkspaceId}", + response.Value.Count, options.WorkspaceId); + + var result = new ListPipelinesCommandResult(response.Value, response.Value.Count); + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.ListPipelinesCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error listing pipelines in workspace {WorkspaceId}", options.WorkspaceId); + HandleException(context, ex); + } + + return context.Response; + } +} + +public sealed class ListPipelinesOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; +} diff --git a/DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs b/DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs new file mode 100644 index 00000000..ec2466d8 --- /dev/null +++ b/DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Fabric.Models; +using DataFactory.MCP.Fabric.Options; +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 DataFactory.MCP.Fabric.Commands; + +[CommandMetadata( + Id = "a1b2c3d4-1001-4000-8000-000000000001", + Name = "list-workspaces", + Title = "List Workspaces", + Description = "Lists all Microsoft Fabric workspaces accessible to the current user. Optionally filter by role (Admin, Member, Contributor, Viewer).", + Destructive = false, + Idempotent = true, + ReadOnly = true, + OpenWorld = false)] +public sealed class ListWorkspacesCommand( + 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()); + } + + protected override ListWorkspacesOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.Roles = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.RolesName); + 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); + + _logger.LogInformation("Successfully listed {Count} workspaces", response.Value.Count); + + var result = new ListWorkspacesCommandResult(response.Value, response.Value.Count); + context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.ListWorkspacesCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error listing workspaces"); + HandleException(context, ex); + } + + return context.Response; + } +} + +public sealed class ListWorkspacesOptions : GlobalOptions +{ + public string? Roles { get; set; } +} diff --git a/DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs new file mode 100644 index 00000000..39011a7b --- /dev/null +++ b/DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Fabric.Models; +using DataFactory.MCP.Fabric.Options; +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 DataFactory.MCP.Fabric.Commands; + +[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; + } +} + +public sealed class RunPipelineOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; + public string PipelineId { get; set; } = string.Empty; +} diff --git a/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj b/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj new file mode 100644 index 00000000..d8d33fc0 --- /dev/null +++ b/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj @@ -0,0 +1,17 @@ + + + net10.0 + true + true + true + enable + enable + DataFactory.MCP.Fabric + + + + + + + diff --git a/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs b/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs new file mode 100644 index 00000000..6a074774 --- /dev/null +++ b/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Extensions; +using DataFactory.MCP.Fabric.Commands; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Mcp.Core.Areas; +using Microsoft.Mcp.Core.Commands; + +namespace DataFactory.MCP.Fabric; + +public class DataFactoryAreaSetup : IAreaSetup +{ + public string Name => "datafactory"; + public string Title => "Microsoft Fabric Data Factory"; + + public void ConfigureServices(IServiceCollection services) + { + // Register DataFactory.MCP.Core services (auth, HttpClients, all service implementations) + services.AddDataFactoryMcpServices(); + + // Register command instances + 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); + + return group; + } +} diff --git a/DataFactory.MCP.Fabric/GlobalUsings.cs b/DataFactory.MCP.Fabric/GlobalUsings.cs new file mode 100644 index 00000000..ce50a919 --- /dev/null +++ b/DataFactory.MCP.Fabric/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using System.CommandLine; +global using System.CommandLine.Parsing; +global using System.Text.Json; diff --git a/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs b/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs new file mode 100644 index 00000000..9c462a25 --- /dev/null +++ b/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using DataFactory.MCP.Models.Pipeline; +using DataFactory.MCP.Models.Workspace; + +namespace DataFactory.MCP.Fabric.Models; + +[JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(ListWorkspacesCommandResult))] +[JsonSerializable(typeof(ListPipelinesCommandResult))] +[JsonSerializable(typeof(CreatePipelineCommandResult))] +[JsonSerializable(typeof(GetPipelineCommandResult))] +[JsonSerializable(typeof(RunPipelineCommandResult))] +public partial class DataFactoryJsonContext : JsonSerializerContext +{ +} + +public sealed record ListWorkspacesCommandResult(List Workspaces, int TotalCount); +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); diff --git a/DataFactory.MCP.Fabric/Options/DataFactoryOptionDefinitions.cs b/DataFactory.MCP.Fabric/Options/DataFactoryOptionDefinitions.cs new file mode 100644 index 00000000..c7259548 --- /dev/null +++ b/DataFactory.MCP.Fabric/Options/DataFactoryOptionDefinitions.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.CommandLine; + +namespace DataFactory.MCP.Fabric.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 + }; +} From 87c04aa85c78aaa9da19198995f78d4f83a4a797 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 14:07:41 -0700 Subject: [PATCH 03/27] Fix Microsoft.Mcp.Core reference for submodule and standalone builds Use conditional ItemGroup to resolve Microsoft.Mcp.Core: - When used as submodule in mcp repo: references ../../../core/... - When built standalone: references ../../mcp/core/... Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj b/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj index d8d33fc0..0ec95c09 100644 --- a/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj +++ b/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj @@ -10,8 +10,13 @@ - + + + + + + + From 9b0d8d698df082fe4363a8860fe9993ce3c649cf Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 14:58:13 -0700 Subject: [PATCH 04/27] Prototype shared handler pattern for ListPipelines Extract business logic into ListPipelinesHandler in Core, making both the SDK tool and Fabric command thin delegators. This proves the handler pattern reduces duplication before applying to all tools. New files: - Core/Handlers/ToolResult.cs - Framework-agnostic result type - Core/Handlers/Pipeline/ListPipelinesHandler.cs - All business logic - Core/Handlers/ToolResultExtensions.cs - Error type mapping Modified: - PipelineTool.ListPipelinesAsync: 45 lines -> 8 lines - ListPipelinesCommand.ExecuteAsync: delegates to handler - ServiceCollectionExtensions: register handler in DI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Extensions/ServiceCollectionExtensions.cs | 3 + .../Handlers/Pipeline/ListPipelinesHandler.cs | 66 +++++++++++++++++++ DataFactory.MCP.Core/Handlers/ToolResult.cs | 17 +++++ .../Handlers/ToolResultExtensions.cs | 23 +++++++ .../Tools/Pipeline/PipelineTool.cs | 57 ++++++---------- .../Commands/ListPipelinesCommand.cs | 24 +++---- 6 files changed, 140 insertions(+), 50 deletions(-) create mode 100644 DataFactory.MCP.Core/Handlers/Pipeline/ListPipelinesHandler.cs create mode 100644 DataFactory.MCP.Core/Handlers/ToolResult.cs create mode 100644 DataFactory.MCP.Core/Handlers/ToolResultExtensions.cs diff --git a/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs b/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs index 0f9483a1..bb5d77f8 100644 --- a/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs @@ -15,6 +15,7 @@ using DataFactory.MCP.Tools.Dataflow; using DataFactory.MCP.Tools.CopyJob; using DataFactory.MCP.Tools.Pipeline; +using DataFactory.MCP.Handlers.Pipeline; namespace DataFactory.MCP.Extensions; @@ -67,6 +68,8 @@ public static IServiceCollection AddDataFactoryMcpServices(this IServiceCollecti .AddSingleton() // Pipeline service .AddSingleton() + // Pipeline handlers (shared handler pattern) + .AddSingleton() // Copy Job service .AddSingleton() // Session accessor for background notifications diff --git a/DataFactory.MCP.Core/Handlers/Pipeline/ListPipelinesHandler.cs b/DataFactory.MCP.Core/Handlers/Pipeline/ListPipelinesHandler.cs new file mode 100644 index 00000000..b45000f3 --- /dev/null +++ b/DataFactory.MCP.Core/Handlers/Pipeline/ListPipelinesHandler.cs @@ -0,0 +1,66 @@ +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Extensions; +using DataFactory.MCP.Models.Pipeline; + +namespace DataFactory.MCP.Handlers.Pipeline; + +public record ListPipelinesResult( + string WorkspaceId, + int PipelineCount, + string? ContinuationToken, + string? ContinuationUri, + bool HasMoreResults, + IEnumerable Pipelines, + List RawPipelines); + +public class ListPipelinesHandler +{ + private readonly IFabricPipelineService _pipelineService; + private readonly IValidationService _validationService; + + public ListPipelinesHandler( + IFabricPipelineService pipelineService, + IValidationService validationService) + { + _pipelineService = pipelineService; + _validationService = validationService; + } + + public async Task> ExecuteAsync( + string workspaceId, string? continuationToken = null) + { + try + { + _validationService.ValidateRequiredString(workspaceId, nameof(workspaceId)); + + var response = await _pipelineService.ListPipelinesAsync(workspaceId, continuationToken); + + var result = new ListPipelinesResult( + WorkspaceId: workspaceId, + PipelineCount: response.Value.Count, + ContinuationToken: response.ContinuationToken, + ContinuationUri: response.ContinuationUri, + HasMoreResults: !string.IsNullOrEmpty(response.ContinuationToken), + Pipelines: response.Value.Select(p => p.ToFormattedInfo()).ToList(), + RawPipelines: response.Value); + + return ToolResult.Success(result); + } + catch (ArgumentException ex) + { + return ToolResult.Failure(ex.Message, "validation"); + } + catch (UnauthorizedAccessException ex) + { + return ToolResult.Failure(ex.ToAuthenticationError().Message!, "auth"); + } + catch (HttpRequestException ex) + { + return ToolResult.Failure(ex.ToHttpError().Message!, "http"); + } + catch (Exception ex) + { + return ToolResult.Failure(ex.Message, "operation"); + } + } +} diff --git a/DataFactory.MCP.Core/Handlers/ToolResult.cs b/DataFactory.MCP.Core/Handlers/ToolResult.cs new file mode 100644 index 00000000..438c9ed7 --- /dev/null +++ b/DataFactory.MCP.Core/Handlers/ToolResult.cs @@ -0,0 +1,17 @@ +namespace DataFactory.MCP.Handlers; + +/// +/// Represents the result of a tool handler execution. +/// Framework-agnostic — both SDK tools and Fabric commands consume this. +/// +public class ToolResult +{ + public bool IsSuccess { get; init; } + public T? Value { get; init; } + public string? Error { get; init; } + public string? ErrorType { get; init; } // "validation", "auth", "http", "operation" + + public static ToolResult Success(T value) => new() { IsSuccess = true, Value = value }; + public static ToolResult Failure(string error, string errorType = "operation") + => new() { IsSuccess = false, Error = error, ErrorType = errorType }; +} diff --git a/DataFactory.MCP.Core/Handlers/ToolResultExtensions.cs b/DataFactory.MCP.Core/Handlers/ToolResultExtensions.cs new file mode 100644 index 00000000..ad11e5b5 --- /dev/null +++ b/DataFactory.MCP.Core/Handlers/ToolResultExtensions.cs @@ -0,0 +1,23 @@ +using DataFactory.MCP.Models.Common.Responses.Errors; + +namespace DataFactory.MCP.Handlers; + +/// +/// Extension methods for converting ToolResult failures to MCP error responses. +/// +public static class ToolResultExtensions +{ + /// + /// Converts a failed ToolResult to the appropriate MCP error response object. + /// + public static object ToErrorResponse(this ToolResult result, string operation = "executing tool") + { + return result.ErrorType switch + { + "validation" => new McpValidationErrorResponse(result.Error ?? "Validation failed"), + "auth" => new McpAuthenticationErrorResponse(result.Error ?? "Authentication failed"), + "http" => new McpHttpErrorResponse(result.Error ?? "HTTP request failed"), + _ => new McpOperationErrorResponse(result.Error ?? "Unknown error", operation), + }; + } +} diff --git a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs index fe0dccb5..c8e0c21c 100644 --- a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs +++ b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs @@ -4,6 +4,8 @@ using System.Text.Json; using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Extensions; +using DataFactory.MCP.Handlers; +using DataFactory.MCP.Handlers.Pipeline; using DataFactory.MCP.Models.Pipeline; using DataFactory.MCP.Models.Pipeline.Definition; using DataFactory.MCP.Models.Pipeline.Schedule; @@ -19,13 +21,16 @@ public class PipelineTool { private readonly IFabricPipelineService _pipelineService; private readonly IValidationService _validationService; + private readonly ListPipelinesHandler _listPipelinesHandler; public PipelineTool( IFabricPipelineService pipelineService, - IValidationService validationService) + IValidationService validationService, + ListPipelinesHandler listPipelinesHandler) { _pipelineService = pipelineService; _validationService = validationService; + _listPipelinesHandler = listPipelinesHandler; } [McpServerTool, Description(@"Returns a list of Pipelines from the specified workspace. This API supports pagination.")] @@ -33,45 +38,21 @@ public async Task ListPipelinesAsync( [Description("The workspace ID to list pipelines from (required)")] string workspaceId, [Description("A token for retrieving the next page of results (optional)")] string? continuationToken = null) { - try + var result = await _listPipelinesHandler.ExecuteAsync(workspaceId, continuationToken); + if (result.IsSuccess) { - _validationService.ValidateRequiredString(workspaceId, nameof(workspaceId)); - - var response = await _pipelineService.ListPipelinesAsync(workspaceId, continuationToken); - - if (!response.Value.Any()) - { - return $"No pipelines found in workspace '{workspaceId}'."; - } - - var result = new + var value = result.Value!; + return new { - WorkspaceId = workspaceId, - PipelineCount = response.Value.Count, - ContinuationToken = response.ContinuationToken, - ContinuationUri = response.ContinuationUri, - HasMoreResults = !string.IsNullOrEmpty(response.ContinuationToken), - Pipelines = response.Value.Select(p => p.ToFormattedInfo()) - }; - - return result.ToMcpJson(); - } - catch (ArgumentException ex) - { - return ex.ToValidationError().ToMcpJson(); - } - catch (UnauthorizedAccessException ex) - { - return ex.ToAuthenticationError().ToMcpJson(); - } - catch (HttpRequestException ex) - { - return ex.ToHttpError().ToMcpJson(); - } - catch (Exception ex) - { - return ex.ToOperationError("listing pipelines").ToMcpJson(); - } + value.WorkspaceId, + value.PipelineCount, + value.ContinuationToken, + value.ContinuationUri, + value.HasMoreResults, + value.Pipelines + }.ToMcpJson(); + } + return result.ToErrorResponse("listing pipelines").ToMcpJson(); } [McpServerTool, Description(@"Creates a Pipeline in the specified workspace.")] diff --git a/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs b/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs index 3cd6718d..d15f822f 100644 --- a/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Fabric.Models; using DataFactory.MCP.Fabric.Options; +using DataFactory.MCP.Handlers.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; using Microsoft.Mcp.Core.Extensions; @@ -24,10 +24,10 @@ namespace DataFactory.MCP.Fabric.Commands; OpenWorld = false)] public sealed class ListPipelinesCommand( ILogger logger, - IFabricPipelineService pipelineService) : GlobalCommand() + ListPipelinesHandler handler) : GlobalCommand() { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - private readonly IFabricPipelineService _pipelineService = pipelineService ?? throw new ArgumentNullException(nameof(pipelineService)); + private readonly ListPipelinesHandler _handler = handler ?? throw new ArgumentNullException(nameof(handler)); protected override void RegisterOptions(Command command) { @@ -50,20 +50,20 @@ public override async Task ExecuteAsync(CommandContext context, } var options = BindOptions(parseResult); - try - { - var response = await _pipelineService.ListPipelinesAsync(options.WorkspaceId); + var result = await _handler.ExecuteAsync(options.WorkspaceId); + if (result.IsSuccess) + { _logger.LogInformation("Successfully listed {Count} pipelines in workspace {WorkspaceId}", - response.Value.Count, options.WorkspaceId); + result.Value!.PipelineCount, options.WorkspaceId); - var result = new ListPipelinesCommandResult(response.Value, response.Value.Count); - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.ListPipelinesCommandResult); + var commandResult = new ListPipelinesCommandResult(result.Value.RawPipelines, result.Value.PipelineCount); + context.Response.Results = ResponseResult.Create(commandResult, DataFactoryJsonContext.Default.ListPipelinesCommandResult); } - catch (Exception ex) + else { - _logger.LogError(ex, "Error listing pipelines in workspace {WorkspaceId}", options.WorkspaceId); - HandleException(context, ex); + _logger.LogError("Error listing pipelines in workspace {WorkspaceId}: {Error}", options.WorkspaceId, result.Error); + HandleException(context, new Exception(result.Error)); } return context.Response; From d5981859d53858973a0a8a23f40f1d35b4a6fd82 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 15:05:23 -0700 Subject: [PATCH 05/27] Extract Options classes into separate files under Options/ Move each GlobalOptions subclass from its command file into a dedicated file in DataFactory.MCP.Fabric/Options/ for cleaner separation of concerns. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Commands/CreatePipelineCommand.cs | 7 ------- .../Commands/GetPipelineCommand.cs | 6 ------ .../Commands/ListPipelinesCommand.cs | 5 ----- .../Commands/ListWorkspacesCommand.cs | 5 ----- .../Commands/RunPipelineCommand.cs | 6 ------ .../Options/CreatePipelineOptions.cs | 13 +++++++++++++ .../Options/GetPipelineOptions.cs | 12 ++++++++++++ .../Options/ListPipelinesOptions.cs | 11 +++++++++++ .../Options/ListWorkspacesOptions.cs | 11 +++++++++++ .../Options/RunPipelineOptions.cs | 12 ++++++++++++ 10 files changed, 59 insertions(+), 29 deletions(-) create mode 100644 DataFactory.MCP.Fabric/Options/CreatePipelineOptions.cs create mode 100644 DataFactory.MCP.Fabric/Options/GetPipelineOptions.cs create mode 100644 DataFactory.MCP.Fabric/Options/ListPipelinesOptions.cs create mode 100644 DataFactory.MCP.Fabric/Options/ListWorkspacesOptions.cs create mode 100644 DataFactory.MCP.Fabric/Options/RunPipelineOptions.cs diff --git a/DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs index 6cd1bc70..c7e35beb 100644 --- a/DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs @@ -92,10 +92,3 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } } - -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/DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs index 1ff35d35..5d6583f2 100644 --- a/DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs @@ -72,9 +72,3 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } } - -public sealed class GetPipelineOptions : GlobalOptions -{ - public string WorkspaceId { get; set; } = string.Empty; - public string PipelineId { get; set; } = string.Empty; -} diff --git a/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs b/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs index d15f822f..663370a8 100644 --- a/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs @@ -69,8 +69,3 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } } - -public sealed class ListPipelinesOptions : GlobalOptions -{ - public string WorkspaceId { get; set; } = string.Empty; -} diff --git a/DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs b/DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs index ec2466d8..20371d01 100644 --- a/DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs @@ -68,8 +68,3 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } } - -public sealed class ListWorkspacesOptions : GlobalOptions -{ - public string? Roles { get; set; } -} diff --git a/DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs index 39011a7b..9575fcdb 100644 --- a/DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs @@ -72,9 +72,3 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } } - -public sealed class RunPipelineOptions : GlobalOptions -{ - public string WorkspaceId { get; set; } = string.Empty; - public string PipelineId { get; set; } = string.Empty; -} diff --git a/DataFactory.MCP.Fabric/Options/CreatePipelineOptions.cs b/DataFactory.MCP.Fabric/Options/CreatePipelineOptions.cs new file mode 100644 index 00000000..a67b93b2 --- /dev/null +++ b/DataFactory.MCP.Fabric/Options/CreatePipelineOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace DataFactory.MCP.Fabric.Options; + +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/DataFactory.MCP.Fabric/Options/GetPipelineOptions.cs b/DataFactory.MCP.Fabric/Options/GetPipelineOptions.cs new file mode 100644 index 00000000..43e1aa19 --- /dev/null +++ b/DataFactory.MCP.Fabric/Options/GetPipelineOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace DataFactory.MCP.Fabric.Options; + +public sealed class GetPipelineOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; + public string PipelineId { get; set; } = string.Empty; +} diff --git a/DataFactory.MCP.Fabric/Options/ListPipelinesOptions.cs b/DataFactory.MCP.Fabric/Options/ListPipelinesOptions.cs new file mode 100644 index 00000000..565d99f3 --- /dev/null +++ b/DataFactory.MCP.Fabric/Options/ListPipelinesOptions.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace DataFactory.MCP.Fabric.Options; + +public sealed class ListPipelinesOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; +} diff --git a/DataFactory.MCP.Fabric/Options/ListWorkspacesOptions.cs b/DataFactory.MCP.Fabric/Options/ListWorkspacesOptions.cs new file mode 100644 index 00000000..740cdf25 --- /dev/null +++ b/DataFactory.MCP.Fabric/Options/ListWorkspacesOptions.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace DataFactory.MCP.Fabric.Options; + +public sealed class ListWorkspacesOptions : GlobalOptions +{ + public string? Roles { get; set; } +} diff --git a/DataFactory.MCP.Fabric/Options/RunPipelineOptions.cs b/DataFactory.MCP.Fabric/Options/RunPipelineOptions.cs new file mode 100644 index 00000000..5b8432cf --- /dev/null +++ b/DataFactory.MCP.Fabric/Options/RunPipelineOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace DataFactory.MCP.Fabric.Options; + +public sealed class RunPipelineOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; + public string PipelineId { get; set; } = string.Empty; +} From 7c51eb37a933355f40c68131982e98978e3d4b58 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 15:09:21 -0700 Subject: [PATCH 06/27] Organize commands and options into domain folders Mirror the Core/Tools structure (Pipeline/, Workspace/) in the Fabric adapter project for commands and options. Structure: Commands/Pipeline/ - ListPipelines, CreatePipeline, GetPipeline, RunPipeline Commands/Workspace/ - ListWorkspaces Options/Pipeline/ - Pipeline-specific options Options/Workspace/ - Workspace-specific options Options/ - Shared option definitions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Commands/{ => Pipeline}/CreatePipelineCommand.cs | 5 +++-- .../Commands/{ => Pipeline}/GetPipelineCommand.cs | 3 ++- .../Commands/{ => Pipeline}/ListPipelinesCommand.cs | 3 ++- .../Commands/{ => Pipeline}/RunPipelineCommand.cs | 3 ++- .../Commands/{ => Workspace}/ListWorkspacesCommand.cs | 3 ++- DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs | 3 ++- .../Options/{ => Pipeline}/CreatePipelineOptions.cs | 2 +- .../Options/{ => Pipeline}/GetPipelineOptions.cs | 2 +- .../Options/{ => Pipeline}/ListPipelinesOptions.cs | 2 +- .../Options/{ => Pipeline}/RunPipelineOptions.cs | 2 +- .../Options/{ => Workspace}/ListWorkspacesOptions.cs | 2 +- 11 files changed, 18 insertions(+), 12 deletions(-) rename DataFactory.MCP.Fabric/Commands/{ => Pipeline}/CreatePipelineCommand.cs (95%) rename DataFactory.MCP.Fabric/Commands/{ => Pipeline}/GetPipelineCommand.cs (96%) rename DataFactory.MCP.Fabric/Commands/{ => Pipeline}/ListPipelinesCommand.cs (96%) rename DataFactory.MCP.Fabric/Commands/{ => Pipeline}/RunPipelineCommand.cs (96%) rename DataFactory.MCP.Fabric/Commands/{ => Workspace}/ListWorkspacesCommand.cs (96%) rename DataFactory.MCP.Fabric/Options/{ => Pipeline}/CreatePipelineOptions.cs (86%) rename DataFactory.MCP.Fabric/Options/{ => Pipeline}/GetPipelineOptions.cs (85%) rename DataFactory.MCP.Fabric/Options/{ => Pipeline}/ListPipelinesOptions.cs (82%) rename DataFactory.MCP.Fabric/Options/{ => Pipeline}/RunPipelineOptions.cs (85%) rename DataFactory.MCP.Fabric/Options/{ => Workspace}/ListWorkspacesOptions.cs (80%) diff --git a/DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/Pipeline/CreatePipelineCommand.cs similarity index 95% rename from DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs rename to DataFactory.MCP.Fabric/Commands/Pipeline/CreatePipelineCommand.cs index c7e35beb..02422885 100644 --- a/DataFactory.MCP.Fabric/Commands/CreatePipelineCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/Pipeline/CreatePipelineCommand.cs @@ -4,6 +4,7 @@ using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Fabric.Models; using DataFactory.MCP.Fabric.Options; +using DataFactory.MCP.Fabric.Options.Pipeline; using DataFactory.MCP.Models.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; @@ -12,7 +13,7 @@ using Microsoft.Mcp.Core.Models.Option; using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Commands; +namespace DataFactory.MCP.Fabric.Commands.Pipeline; [CommandMetadata( Id = "a1b2c3d4-1001-4000-8000-000000000003", @@ -69,7 +70,7 @@ public override async Task ExecuteAsync(CommandContext context, options.DisplayName, options.WorkspaceId); // Map CreatePipelineResponse to Pipeline for the result - var pipeline = new Pipeline + var pipeline = new DataFactory.MCP.Models.Pipeline.Pipeline { Id = response.Id, DisplayName = response.DisplayName, diff --git a/DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/Pipeline/GetPipelineCommand.cs similarity index 96% rename from DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs rename to DataFactory.MCP.Fabric/Commands/Pipeline/GetPipelineCommand.cs index 5d6583f2..c7c4a43f 100644 --- a/DataFactory.MCP.Fabric/Commands/GetPipelineCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/Pipeline/GetPipelineCommand.cs @@ -4,6 +4,7 @@ using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Fabric.Models; using DataFactory.MCP.Fabric.Options; +using DataFactory.MCP.Fabric.Options.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; using Microsoft.Mcp.Core.Extensions; @@ -11,7 +12,7 @@ using Microsoft.Mcp.Core.Models.Option; using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Commands; +namespace DataFactory.MCP.Fabric.Commands.Pipeline; [CommandMetadata( Id = "a1b2c3d4-1001-4000-8000-000000000004", diff --git a/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs b/DataFactory.MCP.Fabric/Commands/Pipeline/ListPipelinesCommand.cs similarity index 96% rename from DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs rename to DataFactory.MCP.Fabric/Commands/Pipeline/ListPipelinesCommand.cs index 663370a8..ca7bbf14 100644 --- a/DataFactory.MCP.Fabric/Commands/ListPipelinesCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/Pipeline/ListPipelinesCommand.cs @@ -3,6 +3,7 @@ using DataFactory.MCP.Fabric.Models; using DataFactory.MCP.Fabric.Options; +using DataFactory.MCP.Fabric.Options.Pipeline; using DataFactory.MCP.Handlers.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; @@ -11,7 +12,7 @@ using Microsoft.Mcp.Core.Models.Option; using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Commands; +namespace DataFactory.MCP.Fabric.Commands.Pipeline; [CommandMetadata( Id = "a1b2c3d4-1001-4000-8000-000000000002", diff --git a/DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/Pipeline/RunPipelineCommand.cs similarity index 96% rename from DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs rename to DataFactory.MCP.Fabric/Commands/Pipeline/RunPipelineCommand.cs index 9575fcdb..b95ebb00 100644 --- a/DataFactory.MCP.Fabric/Commands/RunPipelineCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/Pipeline/RunPipelineCommand.cs @@ -4,6 +4,7 @@ using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Fabric.Models; using DataFactory.MCP.Fabric.Options; +using DataFactory.MCP.Fabric.Options.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; using Microsoft.Mcp.Core.Extensions; @@ -11,7 +12,7 @@ using Microsoft.Mcp.Core.Models.Option; using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Commands; +namespace DataFactory.MCP.Fabric.Commands.Pipeline; [CommandMetadata( Id = "a1b2c3d4-1001-4000-8000-000000000005", diff --git a/DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs b/DataFactory.MCP.Fabric/Commands/Workspace/ListWorkspacesCommand.cs similarity index 96% rename from DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs rename to DataFactory.MCP.Fabric/Commands/Workspace/ListWorkspacesCommand.cs index 20371d01..46e8d58d 100644 --- a/DataFactory.MCP.Fabric/Commands/ListWorkspacesCommand.cs +++ b/DataFactory.MCP.Fabric/Commands/Workspace/ListWorkspacesCommand.cs @@ -4,6 +4,7 @@ using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Fabric.Models; using DataFactory.MCP.Fabric.Options; +using DataFactory.MCP.Fabric.Options.Workspace; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; using Microsoft.Mcp.Core.Extensions; @@ -11,7 +12,7 @@ using Microsoft.Mcp.Core.Models.Option; using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Commands; +namespace DataFactory.MCP.Fabric.Commands.Workspace; [CommandMetadata( Id = "a1b2c3d4-1001-4000-8000-000000000001", diff --git a/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs b/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs index 6a074774..ed2f1bbb 100644 --- a/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs +++ b/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs @@ -2,7 +2,8 @@ // Licensed under the MIT License. using DataFactory.MCP.Extensions; -using DataFactory.MCP.Fabric.Commands; +using DataFactory.MCP.Fabric.Commands.Pipeline; +using DataFactory.MCP.Fabric.Commands.Workspace; using Microsoft.Extensions.DependencyInjection; using Microsoft.Mcp.Core.Areas; using Microsoft.Mcp.Core.Commands; diff --git a/DataFactory.MCP.Fabric/Options/CreatePipelineOptions.cs b/DataFactory.MCP.Fabric/Options/Pipeline/CreatePipelineOptions.cs similarity index 86% rename from DataFactory.MCP.Fabric/Options/CreatePipelineOptions.cs rename to DataFactory.MCP.Fabric/Options/Pipeline/CreatePipelineOptions.cs index a67b93b2..93f76cf5 100644 --- a/DataFactory.MCP.Fabric/Options/CreatePipelineOptions.cs +++ b/DataFactory.MCP.Fabric/Options/Pipeline/CreatePipelineOptions.cs @@ -3,7 +3,7 @@ using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Options; +namespace DataFactory.MCP.Fabric.Options.Pipeline; public sealed class CreatePipelineOptions : GlobalOptions { diff --git a/DataFactory.MCP.Fabric/Options/GetPipelineOptions.cs b/DataFactory.MCP.Fabric/Options/Pipeline/GetPipelineOptions.cs similarity index 85% rename from DataFactory.MCP.Fabric/Options/GetPipelineOptions.cs rename to DataFactory.MCP.Fabric/Options/Pipeline/GetPipelineOptions.cs index 43e1aa19..3bfc9d0a 100644 --- a/DataFactory.MCP.Fabric/Options/GetPipelineOptions.cs +++ b/DataFactory.MCP.Fabric/Options/Pipeline/GetPipelineOptions.cs @@ -3,7 +3,7 @@ using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Options; +namespace DataFactory.MCP.Fabric.Options.Pipeline; public sealed class GetPipelineOptions : GlobalOptions { diff --git a/DataFactory.MCP.Fabric/Options/ListPipelinesOptions.cs b/DataFactory.MCP.Fabric/Options/Pipeline/ListPipelinesOptions.cs similarity index 82% rename from DataFactory.MCP.Fabric/Options/ListPipelinesOptions.cs rename to DataFactory.MCP.Fabric/Options/Pipeline/ListPipelinesOptions.cs index 565d99f3..5144df14 100644 --- a/DataFactory.MCP.Fabric/Options/ListPipelinesOptions.cs +++ b/DataFactory.MCP.Fabric/Options/Pipeline/ListPipelinesOptions.cs @@ -3,7 +3,7 @@ using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Options; +namespace DataFactory.MCP.Fabric.Options.Pipeline; public sealed class ListPipelinesOptions : GlobalOptions { diff --git a/DataFactory.MCP.Fabric/Options/RunPipelineOptions.cs b/DataFactory.MCP.Fabric/Options/Pipeline/RunPipelineOptions.cs similarity index 85% rename from DataFactory.MCP.Fabric/Options/RunPipelineOptions.cs rename to DataFactory.MCP.Fabric/Options/Pipeline/RunPipelineOptions.cs index 5b8432cf..bab64927 100644 --- a/DataFactory.MCP.Fabric/Options/RunPipelineOptions.cs +++ b/DataFactory.MCP.Fabric/Options/Pipeline/RunPipelineOptions.cs @@ -3,7 +3,7 @@ using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Options; +namespace DataFactory.MCP.Fabric.Options.Pipeline; public sealed class RunPipelineOptions : GlobalOptions { diff --git a/DataFactory.MCP.Fabric/Options/ListWorkspacesOptions.cs b/DataFactory.MCP.Fabric/Options/Workspace/ListWorkspacesOptions.cs similarity index 80% rename from DataFactory.MCP.Fabric/Options/ListWorkspacesOptions.cs rename to DataFactory.MCP.Fabric/Options/Workspace/ListWorkspacesOptions.cs index 740cdf25..c2e7bec7 100644 --- a/DataFactory.MCP.Fabric/Options/ListWorkspacesOptions.cs +++ b/DataFactory.MCP.Fabric/Options/Workspace/ListWorkspacesOptions.cs @@ -3,7 +3,7 @@ using Microsoft.Mcp.Core.Options; -namespace DataFactory.MCP.Fabric.Options; +namespace DataFactory.MCP.Fabric.Options.Workspace; public sealed class ListWorkspacesOptions : GlobalOptions { From f021aee856e4c78bab0b5c51ae987c63ca3430bc Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 15:12:27 -0700 Subject: [PATCH 07/27] Remove ListWorkspacesCommand and related files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workspace listing doesn't need a Fabric command wrapper — the SDK tool handles it fine. Removes command, options, and result type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Workspace/ListWorkspacesCommand.cs | 71 ------------------- .../DataFactoryAreaSetup.cs | 3 - .../Models/DataFactoryJsonContext.cs | 3 - .../Workspace/ListWorkspacesOptions.cs | 11 --- 4 files changed, 88 deletions(-) delete mode 100644 DataFactory.MCP.Fabric/Commands/Workspace/ListWorkspacesCommand.cs delete mode 100644 DataFactory.MCP.Fabric/Options/Workspace/ListWorkspacesOptions.cs diff --git a/DataFactory.MCP.Fabric/Commands/Workspace/ListWorkspacesCommand.cs b/DataFactory.MCP.Fabric/Commands/Workspace/ListWorkspacesCommand.cs deleted file mode 100644 index 46e8d58d..00000000 --- a/DataFactory.MCP.Fabric/Commands/Workspace/ListWorkspacesCommand.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using DataFactory.MCP.Fabric.Models; -using DataFactory.MCP.Fabric.Options; -using DataFactory.MCP.Fabric.Options.Workspace; -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 DataFactory.MCP.Fabric.Commands.Workspace; - -[CommandMetadata( - Id = "a1b2c3d4-1001-4000-8000-000000000001", - Name = "list-workspaces", - Title = "List Workspaces", - Description = "Lists all Microsoft Fabric workspaces accessible to the current user. Optionally filter by role (Admin, Member, Contributor, Viewer).", - Destructive = false, - Idempotent = true, - ReadOnly = true, - OpenWorld = false)] -public sealed class ListWorkspacesCommand( - 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()); - } - - protected override ListWorkspacesOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Roles = parseResult.GetValueOrDefault(DataFactoryOptionDefinitions.RolesName); - 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); - - _logger.LogInformation("Successfully listed {Count} workspaces", response.Value.Count); - - var result = new ListWorkspacesCommandResult(response.Value, response.Value.Count); - context.Response.Results = ResponseResult.Create(result, DataFactoryJsonContext.Default.ListWorkspacesCommandResult); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error listing workspaces"); - HandleException(context, ex); - } - - return context.Response; - } -} diff --git a/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs b/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs index ed2f1bbb..5a20b315 100644 --- a/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs +++ b/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs @@ -3,7 +3,6 @@ using DataFactory.MCP.Extensions; using DataFactory.MCP.Fabric.Commands.Pipeline; -using DataFactory.MCP.Fabric.Commands.Workspace; using Microsoft.Extensions.DependencyInjection; using Microsoft.Mcp.Core.Areas; using Microsoft.Mcp.Core.Commands; @@ -21,7 +20,6 @@ public void ConfigureServices(IServiceCollection services) services.AddDataFactoryMcpServices(); // Register command instances - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -39,7 +37,6 @@ public CommandGroup RegisterCommands(IServiceProvider serviceProvider) - Work with dataflows and data transformations """); - group.AddCommand(serviceProvider); group.AddCommand(serviceProvider); group.AddCommand(serviceProvider); group.AddCommand(serviceProvider); diff --git a/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs b/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs index 9c462a25..3a271033 100644 --- a/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs +++ b/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs @@ -3,14 +3,12 @@ using System.Text.Json.Serialization; using DataFactory.MCP.Models.Pipeline; -using DataFactory.MCP.Models.Workspace; namespace DataFactory.MCP.Fabric.Models; [JsonSourceGenerationOptions( PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] -[JsonSerializable(typeof(ListWorkspacesCommandResult))] [JsonSerializable(typeof(ListPipelinesCommandResult))] [JsonSerializable(typeof(CreatePipelineCommandResult))] [JsonSerializable(typeof(GetPipelineCommandResult))] @@ -19,7 +17,6 @@ public partial class DataFactoryJsonContext : JsonSerializerContext { } -public sealed record ListWorkspacesCommandResult(List Workspaces, int TotalCount); public sealed record ListPipelinesCommandResult(List Pipelines, int TotalCount); public sealed record CreatePipelineCommandResult(Pipeline Pipeline); public sealed record GetPipelineCommandResult(Pipeline Pipeline); diff --git a/DataFactory.MCP.Fabric/Options/Workspace/ListWorkspacesOptions.cs b/DataFactory.MCP.Fabric/Options/Workspace/ListWorkspacesOptions.cs deleted file mode 100644 index c2e7bec7..00000000 --- a/DataFactory.MCP.Fabric/Options/Workspace/ListWorkspacesOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Mcp.Core.Options; - -namespace DataFactory.MCP.Fabric.Options.Workspace; - -public sealed class ListWorkspacesOptions : GlobalOptions -{ - public string? Roles { get; set; } -} From df831c689bc391f3e1c8c075e390d69a00ebd6cd Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 15:17:31 -0700 Subject: [PATCH 08/27] Add ListDataflows and CreateDataflow Fabric commands - ListDataflowsCommand: lists dataflows in a workspace - CreateDataflowCommand: creates a new dataflow with name/description - Options classes in Options/Dataflow/ folder - Result records in DataFactoryJsonContext - Registered in DataFactoryAreaSetup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Dataflow/CreateDataflowCommand.cs | 95 +++++++++++++++++++ .../Commands/Dataflow/ListDataflowsCommand.cs | 73 ++++++++++++++ .../DataFactoryAreaSetup.cs | 5 + .../Models/DataFactoryJsonContext.cs | 4 + .../Options/Dataflow/CreateDataflowOptions.cs | 13 +++ .../Options/Dataflow/ListDataflowsOptions.cs | 11 +++ 6 files changed, 201 insertions(+) create mode 100644 DataFactory.MCP.Fabric/Commands/Dataflow/CreateDataflowCommand.cs create mode 100644 DataFactory.MCP.Fabric/Commands/Dataflow/ListDataflowsCommand.cs create mode 100644 DataFactory.MCP.Fabric/Options/Dataflow/CreateDataflowOptions.cs create mode 100644 DataFactory.MCP.Fabric/Options/Dataflow/ListDataflowsOptions.cs diff --git a/DataFactory.MCP.Fabric/Commands/Dataflow/CreateDataflowCommand.cs b/DataFactory.MCP.Fabric/Commands/Dataflow/CreateDataflowCommand.cs new file mode 100644 index 00000000..59d7c3e8 --- /dev/null +++ b/DataFactory.MCP.Fabric/Commands/Dataflow/CreateDataflowCommand.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Fabric.Models; +using DataFactory.MCP.Fabric.Options; +using DataFactory.MCP.Fabric.Options.Dataflow; +using 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 DataFactory.MCP.Fabric.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 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/DataFactory.MCP.Fabric/Commands/Dataflow/ListDataflowsCommand.cs b/DataFactory.MCP.Fabric/Commands/Dataflow/ListDataflowsCommand.cs new file mode 100644 index 00000000..0bf75db7 --- /dev/null +++ b/DataFactory.MCP.Fabric/Commands/Dataflow/ListDataflowsCommand.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Fabric.Models; +using DataFactory.MCP.Fabric.Options; +using DataFactory.MCP.Fabric.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 DataFactory.MCP.Fabric.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/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs b/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs index 5a20b315..b7516cea 100644 --- a/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs +++ b/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using DataFactory.MCP.Extensions; +using DataFactory.MCP.Fabric.Commands.Dataflow; using DataFactory.MCP.Fabric.Commands.Pipeline; using Microsoft.Extensions.DependencyInjection; using Microsoft.Mcp.Core.Areas; @@ -24,6 +25,8 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } public CommandGroup RegisterCommands(IServiceProvider serviceProvider) @@ -41,6 +44,8 @@ public CommandGroup RegisterCommands(IServiceProvider serviceProvider) group.AddCommand(serviceProvider); group.AddCommand(serviceProvider); group.AddCommand(serviceProvider); + group.AddCommand(serviceProvider); + group.AddCommand(serviceProvider); return group; } diff --git a/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs b/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs index 3a271033..e34c100e 100644 --- a/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs +++ b/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs @@ -13,6 +13,8 @@ namespace DataFactory.MCP.Fabric.Models; [JsonSerializable(typeof(CreatePipelineCommandResult))] [JsonSerializable(typeof(GetPipelineCommandResult))] [JsonSerializable(typeof(RunPipelineCommandResult))] +[JsonSerializable(typeof(ListDataflowsCommandResult))] +[JsonSerializable(typeof(CreateDataflowCommandResult))] public partial class DataFactoryJsonContext : JsonSerializerContext { } @@ -21,3 +23,5 @@ public sealed record ListPipelinesCommandResult(List Pipelines, int To 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(DataFactory.MCP.Models.Dataflow.Dataflow Dataflow); diff --git a/DataFactory.MCP.Fabric/Options/Dataflow/CreateDataflowOptions.cs b/DataFactory.MCP.Fabric/Options/Dataflow/CreateDataflowOptions.cs new file mode 100644 index 00000000..67452b1f --- /dev/null +++ b/DataFactory.MCP.Fabric/Options/Dataflow/CreateDataflowOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace DataFactory.MCP.Fabric.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/DataFactory.MCP.Fabric/Options/Dataflow/ListDataflowsOptions.cs b/DataFactory.MCP.Fabric/Options/Dataflow/ListDataflowsOptions.cs new file mode 100644 index 00000000..8a5fa639 --- /dev/null +++ b/DataFactory.MCP.Fabric/Options/Dataflow/ListDataflowsOptions.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Mcp.Core.Options; + +namespace DataFactory.MCP.Fabric.Options.Dataflow; + +public sealed class ListDataflowsOptions : GlobalOptions +{ + public string WorkspaceId { get; set; } = string.Empty; +} From 0b25987b018f6928d53d9998277f42114a742087 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 15:32:58 -0700 Subject: [PATCH 09/27] Remove DataFactory.MCP.Fabric - moved to mcp repo as Fabric.Mcp.Tools.DataFactory --- .../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 --------------- .../DataFactory.MCP.Fabric.csproj | 22 ----- .../DataFactoryAreaSetup.cs | 52 ---------- DataFactory.MCP.Fabric/GlobalUsings.cs | 3 - .../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 --- 17 files changed, 705 deletions(-) delete mode 100644 DataFactory.MCP.Fabric/Commands/Dataflow/CreateDataflowCommand.cs delete mode 100644 DataFactory.MCP.Fabric/Commands/Dataflow/ListDataflowsCommand.cs delete mode 100644 DataFactory.MCP.Fabric/Commands/Pipeline/CreatePipelineCommand.cs delete mode 100644 DataFactory.MCP.Fabric/Commands/Pipeline/GetPipelineCommand.cs delete mode 100644 DataFactory.MCP.Fabric/Commands/Pipeline/ListPipelinesCommand.cs delete mode 100644 DataFactory.MCP.Fabric/Commands/Pipeline/RunPipelineCommand.cs delete mode 100644 DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj delete mode 100644 DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs delete mode 100644 DataFactory.MCP.Fabric/GlobalUsings.cs delete mode 100644 DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs delete mode 100644 DataFactory.MCP.Fabric/Options/DataFactoryOptionDefinitions.cs delete mode 100644 DataFactory.MCP.Fabric/Options/Dataflow/CreateDataflowOptions.cs delete mode 100644 DataFactory.MCP.Fabric/Options/Dataflow/ListDataflowsOptions.cs delete mode 100644 DataFactory.MCP.Fabric/Options/Pipeline/CreatePipelineOptions.cs delete mode 100644 DataFactory.MCP.Fabric/Options/Pipeline/GetPipelineOptions.cs delete mode 100644 DataFactory.MCP.Fabric/Options/Pipeline/ListPipelinesOptions.cs delete mode 100644 DataFactory.MCP.Fabric/Options/Pipeline/RunPipelineOptions.cs diff --git a/DataFactory.MCP.Fabric/Commands/Dataflow/CreateDataflowCommand.cs b/DataFactory.MCP.Fabric/Commands/Dataflow/CreateDataflowCommand.cs deleted file mode 100644 index 59d7c3e8..00000000 --- a/DataFactory.MCP.Fabric/Commands/Dataflow/CreateDataflowCommand.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using DataFactory.MCP.Fabric.Models; -using DataFactory.MCP.Fabric.Options; -using DataFactory.MCP.Fabric.Options.Dataflow; -using 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 DataFactory.MCP.Fabric.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 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/DataFactory.MCP.Fabric/Commands/Dataflow/ListDataflowsCommand.cs b/DataFactory.MCP.Fabric/Commands/Dataflow/ListDataflowsCommand.cs deleted file mode 100644 index 0bf75db7..00000000 --- a/DataFactory.MCP.Fabric/Commands/Dataflow/ListDataflowsCommand.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using DataFactory.MCP.Fabric.Models; -using DataFactory.MCP.Fabric.Options; -using DataFactory.MCP.Fabric.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 DataFactory.MCP.Fabric.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/DataFactory.MCP.Fabric/Commands/Pipeline/CreatePipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/Pipeline/CreatePipelineCommand.cs deleted file mode 100644 index 02422885..00000000 --- a/DataFactory.MCP.Fabric/Commands/Pipeline/CreatePipelineCommand.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using DataFactory.MCP.Fabric.Models; -using DataFactory.MCP.Fabric.Options; -using DataFactory.MCP.Fabric.Options.Pipeline; -using 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 DataFactory.MCP.Fabric.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 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/DataFactory.MCP.Fabric/Commands/Pipeline/GetPipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/Pipeline/GetPipelineCommand.cs deleted file mode 100644 index c7c4a43f..00000000 --- a/DataFactory.MCP.Fabric/Commands/Pipeline/GetPipelineCommand.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using DataFactory.MCP.Fabric.Models; -using DataFactory.MCP.Fabric.Options; -using DataFactory.MCP.Fabric.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 DataFactory.MCP.Fabric.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/DataFactory.MCP.Fabric/Commands/Pipeline/ListPipelinesCommand.cs b/DataFactory.MCP.Fabric/Commands/Pipeline/ListPipelinesCommand.cs deleted file mode 100644 index ca7bbf14..00000000 --- a/DataFactory.MCP.Fabric/Commands/Pipeline/ListPipelinesCommand.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Fabric.Models; -using DataFactory.MCP.Fabric.Options; -using DataFactory.MCP.Fabric.Options.Pipeline; -using 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 DataFactory.MCP.Fabric.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/DataFactory.MCP.Fabric/Commands/Pipeline/RunPipelineCommand.cs b/DataFactory.MCP.Fabric/Commands/Pipeline/RunPipelineCommand.cs deleted file mode 100644 index b95ebb00..00000000 --- a/DataFactory.MCP.Fabric/Commands/Pipeline/RunPipelineCommand.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Abstractions.Interfaces; -using DataFactory.MCP.Fabric.Models; -using DataFactory.MCP.Fabric.Options; -using DataFactory.MCP.Fabric.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 DataFactory.MCP.Fabric.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/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj b/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj deleted file mode 100644 index 0ec95c09..00000000 --- a/DataFactory.MCP.Fabric/DataFactory.MCP.Fabric.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - net10.0 - true - true - true - enable - enable - DataFactory.MCP.Fabric - - - - - - - - - - - - - diff --git a/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs b/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs deleted file mode 100644 index b7516cea..00000000 --- a/DataFactory.MCP.Fabric/DataFactoryAreaSetup.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using DataFactory.MCP.Extensions; -using DataFactory.MCP.Fabric.Commands.Dataflow; -using DataFactory.MCP.Fabric.Commands.Pipeline; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Areas; -using Microsoft.Mcp.Core.Commands; - -namespace DataFactory.MCP.Fabric; - -public class DataFactoryAreaSetup : IAreaSetup -{ - public string Name => "datafactory"; - public string Title => "Microsoft Fabric Data Factory"; - - public void ConfigureServices(IServiceCollection services) - { - // Register 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/DataFactory.MCP.Fabric/GlobalUsings.cs b/DataFactory.MCP.Fabric/GlobalUsings.cs deleted file mode 100644 index ce50a919..00000000 --- a/DataFactory.MCP.Fabric/GlobalUsings.cs +++ /dev/null @@ -1,3 +0,0 @@ -global using System.CommandLine; -global using System.CommandLine.Parsing; -global using System.Text.Json; diff --git a/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs b/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs deleted file mode 100644 index e34c100e..00000000 --- a/DataFactory.MCP.Fabric/Models/DataFactoryJsonContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using DataFactory.MCP.Models.Pipeline; - -namespace DataFactory.MCP.Fabric.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(DataFactory.MCP.Models.Dataflow.Dataflow Dataflow); diff --git a/DataFactory.MCP.Fabric/Options/DataFactoryOptionDefinitions.cs b/DataFactory.MCP.Fabric/Options/DataFactoryOptionDefinitions.cs deleted file mode 100644 index c7259548..00000000 --- a/DataFactory.MCP.Fabric/Options/DataFactoryOptionDefinitions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.CommandLine; - -namespace DataFactory.MCP.Fabric.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/DataFactory.MCP.Fabric/Options/Dataflow/CreateDataflowOptions.cs b/DataFactory.MCP.Fabric/Options/Dataflow/CreateDataflowOptions.cs deleted file mode 100644 index 67452b1f..00000000 --- a/DataFactory.MCP.Fabric/Options/Dataflow/CreateDataflowOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Mcp.Core.Options; - -namespace DataFactory.MCP.Fabric.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/DataFactory.MCP.Fabric/Options/Dataflow/ListDataflowsOptions.cs b/DataFactory.MCP.Fabric/Options/Dataflow/ListDataflowsOptions.cs deleted file mode 100644 index 8a5fa639..00000000 --- a/DataFactory.MCP.Fabric/Options/Dataflow/ListDataflowsOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Mcp.Core.Options; - -namespace DataFactory.MCP.Fabric.Options.Dataflow; - -public sealed class ListDataflowsOptions : GlobalOptions -{ - public string WorkspaceId { get; set; } = string.Empty; -} diff --git a/DataFactory.MCP.Fabric/Options/Pipeline/CreatePipelineOptions.cs b/DataFactory.MCP.Fabric/Options/Pipeline/CreatePipelineOptions.cs deleted file mode 100644 index 93f76cf5..00000000 --- a/DataFactory.MCP.Fabric/Options/Pipeline/CreatePipelineOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Mcp.Core.Options; - -namespace DataFactory.MCP.Fabric.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/DataFactory.MCP.Fabric/Options/Pipeline/GetPipelineOptions.cs b/DataFactory.MCP.Fabric/Options/Pipeline/GetPipelineOptions.cs deleted file mode 100644 index 3bfc9d0a..00000000 --- a/DataFactory.MCP.Fabric/Options/Pipeline/GetPipelineOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Mcp.Core.Options; - -namespace DataFactory.MCP.Fabric.Options.Pipeline; - -public sealed class GetPipelineOptions : GlobalOptions -{ - public string WorkspaceId { get; set; } = string.Empty; - public string PipelineId { get; set; } = string.Empty; -} diff --git a/DataFactory.MCP.Fabric/Options/Pipeline/ListPipelinesOptions.cs b/DataFactory.MCP.Fabric/Options/Pipeline/ListPipelinesOptions.cs deleted file mode 100644 index 5144df14..00000000 --- a/DataFactory.MCP.Fabric/Options/Pipeline/ListPipelinesOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Mcp.Core.Options; - -namespace DataFactory.MCP.Fabric.Options.Pipeline; - -public sealed class ListPipelinesOptions : GlobalOptions -{ - public string WorkspaceId { get; set; } = string.Empty; -} diff --git a/DataFactory.MCP.Fabric/Options/Pipeline/RunPipelineOptions.cs b/DataFactory.MCP.Fabric/Options/Pipeline/RunPipelineOptions.cs deleted file mode 100644 index bab64927..00000000 --- a/DataFactory.MCP.Fabric/Options/Pipeline/RunPipelineOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Mcp.Core.Options; - -namespace DataFactory.MCP.Fabric.Options.Pipeline; - -public sealed class RunPipelineOptions : GlobalOptions -{ - public string WorkspaceId { get; set; } = string.Empty; - public string PipelineId { get; set; } = string.Empty; -} From d3e4348c1d4f37f6a72f9f034bbbbb4ae5a88f4d Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 16:11:00 -0700 Subject: [PATCH 10/27] Unified domain handlers: PipelineHandler + DataflowHandler - Replace ListPipelinesHandler with unified PipelineHandler (List/Create/Get/Run) - Add DataflowHandler (List/Create) - Update PipelineTool and DataflowTool to delegate to handlers - Update DI registrations - All business logic now lives in handlers, tools are thin shims Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Extensions/ServiceCollectionExtensions.cs | 5 +- .../Handlers/Dataflow/DataflowHandler.cs | 89 ++++++++++ .../Handlers/Pipeline/ListPipelinesHandler.cs | 66 ------- .../Handlers/Pipeline/PipelineHandler.cs | 162 ++++++++++++++++++ .../Tools/Dataflow/DataflowTool.cs | 96 +++-------- .../Tools/Pipeline/PipelineTool.cs | 136 ++++----------- .../Infrastructure/McpTestFixture.cs | 6 + 7 files changed, 318 insertions(+), 242 deletions(-) create mode 100644 DataFactory.MCP.Core/Handlers/Dataflow/DataflowHandler.cs delete mode 100644 DataFactory.MCP.Core/Handlers/Pipeline/ListPipelinesHandler.cs create mode 100644 DataFactory.MCP.Core/Handlers/Pipeline/PipelineHandler.cs diff --git a/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs b/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs index bb5d77f8..1aa07d9a 100644 --- a/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs @@ -16,6 +16,7 @@ using DataFactory.MCP.Tools.CopyJob; using DataFactory.MCP.Tools.Pipeline; using DataFactory.MCP.Handlers.Pipeline; +using DataFactory.MCP.Handlers.Dataflow; namespace DataFactory.MCP.Extensions; @@ -69,7 +70,9 @@ public static IServiceCollection AddDataFactoryMcpServices(this IServiceCollecti // Pipeline service .AddSingleton() // Pipeline handlers (shared handler pattern) - .AddSingleton() + .AddSingleton() + // Dataflow handlers + .AddSingleton() // Copy Job service .AddSingleton() // Session accessor for background notifications diff --git a/DataFactory.MCP.Core/Handlers/Dataflow/DataflowHandler.cs b/DataFactory.MCP.Core/Handlers/Dataflow/DataflowHandler.cs new file mode 100644 index 00000000..c4071427 --- /dev/null +++ b/DataFactory.MCP.Core/Handlers/Dataflow/DataflowHandler.cs @@ -0,0 +1,89 @@ +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Models; +using DataFactory.MCP.Models.Dataflow; + +namespace DataFactory.MCP.Handlers.Dataflow; + +public record ListDataflowsResult( + string WorkspaceId, + int DataflowCount, + string? ContinuationToken, + string? ContinuationUri, + bool HasMoreResults, + IReadOnlyList Dataflows); + +public record CreateDataflowResult(CreateDataflowResponse Dataflow); + +public class DataflowHandler(IFabricDataflowService dataflowService) +{ + public async Task> ListAsync(string workspaceId, string? continuationToken = null) + { + if (string.IsNullOrWhiteSpace(workspaceId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("workspaceId"), "validation"); + + try + { + var response = await dataflowService.ListDataflowsAsync(workspaceId, continuationToken); + var result = new ListDataflowsResult( + WorkspaceId: workspaceId, + DataflowCount: response.Value.Count, + ContinuationToken: response.ContinuationToken, + ContinuationUri: response.ContinuationUri, + HasMoreResults: !string.IsNullOrEmpty(response.ContinuationToken), + Dataflows: response.Value); + return ToolResult.Success(result); + } + catch (UnauthorizedAccessException ex) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + return ToolResult.Failure("Authentication failed. Please check your credentials.", "auth"); + } + catch (HttpRequestException ex) + { + return ToolResult.Failure($"Failed to list dataflows: {ex.Message}", "http"); + } + catch (Exception ex) + { + return ToolResult.Failure($"Unexpected error listing dataflows: {ex.Message}", "operation"); + } + } + + public async Task> CreateAsync(string workspaceId, string displayName, string? description = null, string? folderId = null) + { + if (string.IsNullOrWhiteSpace(workspaceId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("workspaceId"), "validation"); + if (string.IsNullOrWhiteSpace(displayName)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("displayName"), "validation"); + + try + { + var request = new CreateDataflowRequest + { + DisplayName = displayName, + Description = description, + FolderId = folderId + }; + var dataflow = await dataflowService.CreateDataflowAsync(workspaceId, request); + return ToolResult.Success(new CreateDataflowResult(dataflow)); + } + catch (UnauthorizedAccessException ex) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + return ToolResult.Failure("Authentication failed. Please check your credentials.", "auth"); + } + catch (HttpRequestException ex) + { + return ToolResult.Failure($"Failed to create dataflow: {ex.Message}", "http"); + } + catch (Exception ex) + { + return ToolResult.Failure($"Unexpected error creating dataflow: {ex.Message}", "operation"); + } + } +} diff --git a/DataFactory.MCP.Core/Handlers/Pipeline/ListPipelinesHandler.cs b/DataFactory.MCP.Core/Handlers/Pipeline/ListPipelinesHandler.cs deleted file mode 100644 index b45000f3..00000000 --- a/DataFactory.MCP.Core/Handlers/Pipeline/ListPipelinesHandler.cs +++ /dev/null @@ -1,66 +0,0 @@ -using DataFactory.MCP.Abstractions.Interfaces; -using DataFactory.MCP.Extensions; -using DataFactory.MCP.Models.Pipeline; - -namespace DataFactory.MCP.Handlers.Pipeline; - -public record ListPipelinesResult( - string WorkspaceId, - int PipelineCount, - string? ContinuationToken, - string? ContinuationUri, - bool HasMoreResults, - IEnumerable Pipelines, - List RawPipelines); - -public class ListPipelinesHandler -{ - private readonly IFabricPipelineService _pipelineService; - private readonly IValidationService _validationService; - - public ListPipelinesHandler( - IFabricPipelineService pipelineService, - IValidationService validationService) - { - _pipelineService = pipelineService; - _validationService = validationService; - } - - public async Task> ExecuteAsync( - string workspaceId, string? continuationToken = null) - { - try - { - _validationService.ValidateRequiredString(workspaceId, nameof(workspaceId)); - - var response = await _pipelineService.ListPipelinesAsync(workspaceId, continuationToken); - - var result = new ListPipelinesResult( - WorkspaceId: workspaceId, - PipelineCount: response.Value.Count, - ContinuationToken: response.ContinuationToken, - ContinuationUri: response.ContinuationUri, - HasMoreResults: !string.IsNullOrEmpty(response.ContinuationToken), - Pipelines: response.Value.Select(p => p.ToFormattedInfo()).ToList(), - RawPipelines: response.Value); - - return ToolResult.Success(result); - } - catch (ArgumentException ex) - { - return ToolResult.Failure(ex.Message, "validation"); - } - catch (UnauthorizedAccessException ex) - { - return ToolResult.Failure(ex.ToAuthenticationError().Message!, "auth"); - } - catch (HttpRequestException ex) - { - return ToolResult.Failure(ex.ToHttpError().Message!, "http"); - } - catch (Exception ex) - { - return ToolResult.Failure(ex.Message, "operation"); - } - } -} diff --git a/DataFactory.MCP.Core/Handlers/Pipeline/PipelineHandler.cs b/DataFactory.MCP.Core/Handlers/Pipeline/PipelineHandler.cs new file mode 100644 index 00000000..e8b1be8a --- /dev/null +++ b/DataFactory.MCP.Core/Handlers/Pipeline/PipelineHandler.cs @@ -0,0 +1,162 @@ +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Models; +using DataFactory.MCP.Models.Pipeline; + +namespace DataFactory.MCP.Handlers.Pipeline; + +public record ListPipelinesResult( + string WorkspaceId, + int PipelineCount, + string? ContinuationToken, + string? ContinuationUri, + bool HasMoreResults, + IReadOnlyList Pipelines); + +public record CreatePipelineResult(CreatePipelineResponse Pipeline); + +public record GetPipelineResult(Models.Pipeline.Pipeline Pipeline); + +public record RunPipelineResult(string? LocationUrl, string? JobInstanceId); + +public class PipelineHandler(IFabricPipelineService pipelineService) +{ + public async Task> ListAsync(string workspaceId, string? continuationToken = null) + { + if (string.IsNullOrWhiteSpace(workspaceId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("workspaceId"), "validation"); + + try + { + var response = await pipelineService.ListPipelinesAsync(workspaceId, continuationToken); + var result = new ListPipelinesResult( + WorkspaceId: workspaceId, + PipelineCount: response.Value.Count, + ContinuationToken: response.ContinuationToken, + ContinuationUri: response.ContinuationUri, + HasMoreResults: !string.IsNullOrEmpty(response.ContinuationToken), + Pipelines: response.Value); + return ToolResult.Success(result); + } + catch (UnauthorizedAccessException ex) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) + { + return ToolResult.Failure($"Failed to list pipelines: {ex.Message}", "http"); + } + catch (Exception ex) + { + return ToolResult.Failure($"Unexpected error listing pipelines: {ex.Message}", "operation"); + } + } + + public async Task> CreateAsync(string workspaceId, string displayName, string? description = null, string? folderId = null) + { + if (string.IsNullOrWhiteSpace(workspaceId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("workspaceId"), "validation"); + if (string.IsNullOrWhiteSpace(displayName)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("displayName"), "validation"); + + try + { + var request = new CreatePipelineRequest + { + DisplayName = displayName, + Description = description, + FolderId = folderId + }; + var pipeline = await pipelineService.CreatePipelineAsync(workspaceId, request); + return ToolResult.Success(new CreatePipelineResult(pipeline)); + } + catch (UnauthorizedAccessException ex) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) + { + return ToolResult.Failure($"Failed to create pipeline: {ex.Message}", "http"); + } + catch (Exception ex) + { + return ToolResult.Failure($"Unexpected error creating pipeline: {ex.Message}", "operation"); + } + } + + public async Task> GetAsync(string workspaceId, string pipelineId) + { + if (string.IsNullOrWhiteSpace(workspaceId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("workspaceId"), "validation"); + if (string.IsNullOrWhiteSpace(pipelineId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("pipelineId"), "validation"); + + try + { + var pipeline = await pipelineService.GetPipelineAsync(workspaceId, pipelineId); + return ToolResult.Success(new GetPipelineResult(pipeline)); + } + catch (UnauthorizedAccessException ex) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) + { + return ToolResult.Failure($"Failed to get pipeline: {ex.Message}", "http"); + } + catch (Exception ex) + { + return ToolResult.Failure($"Unexpected error getting pipeline: {ex.Message}", "operation"); + } + } + + public async Task> RunAsync(string workspaceId, string pipelineId, object? executionData = null) + { + if (string.IsNullOrWhiteSpace(workspaceId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("workspaceId"), "validation"); + if (string.IsNullOrWhiteSpace(pipelineId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("pipelineId"), "validation"); + + try + { + var location = await pipelineService.RunPipelineAsync(workspaceId, pipelineId, executionData); + + // Extract job instance ID from the Location header URL + string? jobInstanceId = null; + if (!string.IsNullOrEmpty(location)) + { + var segments = new Uri(location).Segments; + jobInstanceId = segments.LastOrDefault()?.TrimEnd('/'); + } + + return ToolResult.Success(new RunPipelineResult(location, jobInstanceId)); + } + catch (UnauthorizedAccessException ex) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + return ToolResult.Failure(string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) + { + return ToolResult.Failure($"Failed to run pipeline: {ex.Message}", "http"); + } + catch (Exception ex) + { + return ToolResult.Failure($"Unexpected error running pipeline: {ex.Message}", "operation"); + } + } +} diff --git a/DataFactory.MCP.Core/Tools/Dataflow/DataflowTool.cs b/DataFactory.MCP.Core/Tools/Dataflow/DataflowTool.cs index 1640b107..5a32f419 100644 --- a/DataFactory.MCP.Core/Tools/Dataflow/DataflowTool.cs +++ b/DataFactory.MCP.Core/Tools/Dataflow/DataflowTool.cs @@ -2,6 +2,8 @@ using System.ComponentModel; using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Extensions; +using DataFactory.MCP.Handlers; +using DataFactory.MCP.Handlers.Dataflow; using DataFactory.MCP.Models.Dataflow; namespace DataFactory.MCP.Tools.Dataflow; @@ -16,15 +18,18 @@ public class DataflowTool private readonly IFabricDataflowService _dataflowService; private readonly IFabricConnectionService _connectionService; private readonly IValidationService _validationService; + private readonly DataflowHandler _dataflowHandler; public DataflowTool( IFabricDataflowService dataflowService, IFabricConnectionService connectionService, - IValidationService validationService) + IValidationService validationService, + DataflowHandler dataflowHandler) { _dataflowService = dataflowService; _connectionService = connectionService; _validationService = validationService; + _dataflowHandler = dataflowHandler; } [McpServerTool, Description(@"Returns a list of Dataflows from the specified workspace. This API supports pagination.")] @@ -32,45 +37,21 @@ public async Task ListDataflowsAsync( [Description("The workspace ID to list dataflows from (required)")] string workspaceId, [Description("A token for retrieving the next page of results (optional)")] string? continuationToken = null) { - try + var result = await _dataflowHandler.ListAsync(workspaceId, continuationToken); + if (result.IsSuccess) { - _validationService.ValidateRequiredString(workspaceId, nameof(workspaceId)); - - var response = await _dataflowService.ListDataflowsAsync(workspaceId, continuationToken); - - if (!response.Value.Any()) - { - return $"No dataflows found in workspace '{workspaceId}'."; - } - - var result = new + var value = result.Value!; + return new { - WorkspaceId = workspaceId, - DataflowCount = response.Value.Count, - ContinuationToken = response.ContinuationToken, - ContinuationUri = response.ContinuationUri, - HasMoreResults = !string.IsNullOrEmpty(response.ContinuationToken), - Dataflows = response.Value.Select(d => d.ToFormattedInfo()) - }; - - return result.ToMcpJson(); - } - catch (ArgumentException ex) - { - return ex.ToValidationError().ToMcpJson(); - } - catch (UnauthorizedAccessException ex) - { - return ex.ToAuthenticationError().ToMcpJson(); - } - catch (HttpRequestException ex) - { - return ex.ToHttpError().ToMcpJson(); - } - catch (Exception ex) - { - return ex.ToOperationError("listing dataflows").ToMcpJson(); + value.WorkspaceId, + value.DataflowCount, + value.ContinuationToken, + value.ContinuationUri, + value.HasMoreResults, + Dataflows = value.Dataflows.Select(d => d.ToFormattedInfo()) + }.ToMcpJson(); } + return result.ToErrorResponse("listing dataflows").ToMcpJson(); } [McpServerTool, Description(@"Creates a Dataflow in the specified workspace. The workspace must be on a supported Fabric capacity.")] @@ -80,18 +61,11 @@ public async Task CreateDataflowAsync( [Description("The Dataflow description (optional, max 256 characters)")] string? description = null, [Description("The folder ID where the dataflow will be created (optional, defaults to workspace root)")] string? folderId = null) { - try + var result = await _dataflowHandler.CreateAsync(workspaceId, displayName, description, folderId); + if (result.IsSuccess) { - var request = new CreateDataflowRequest - { - DisplayName = displayName, - Description = description, - FolderId = folderId - }; - - var response = await _dataflowService.CreateDataflowAsync(workspaceId, request); - - var result = new + var response = result.Value!.Dataflow; + return new { Success = true, Message = $"Dataflow '{displayName}' created successfully", @@ -102,31 +76,9 @@ public async Task CreateDataflowAsync( WorkspaceId = response.WorkspaceId, FolderId = response.FolderId, CreatedAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") - }; - - return result.ToMcpJson(); - } - catch (ArgumentException ex) - { - return ex.ToValidationError().ToMcpJson(); - } - catch (UnauthorizedAccessException ex) - { - return ex.ToAuthenticationError().ToMcpJson(); - } - catch (HttpRequestException ex) when (ex.Message.Contains("403") || ex.Message.Contains("Forbidden")) - { - return new HttpRequestException("Access denied or feature not available. The workspace must be on a supported Fabric capacity to create dataflows.") - .ToHttpError().ToMcpJson(); - } - catch (HttpRequestException ex) - { - return ex.ToHttpError().ToMcpJson(); - } - catch (Exception ex) - { - return ex.ToOperationError("creating dataflow").ToMcpJson(); + }.ToMcpJson(); } + return result.ToErrorResponse("creating dataflow").ToMcpJson(); } [McpServerTool, Description(@"Adds or replaces connections in an existing dataflow, or clears all connections. When clearExisting is true with no connectionIds, all connections are removed. When clearExisting is true with connectionIds, existing connections are replaced. When clearExisting is false (default), connections are appended.")] diff --git a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs index c8e0c21c..da470b40 100644 --- a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs +++ b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs @@ -21,16 +21,16 @@ public class PipelineTool { private readonly IFabricPipelineService _pipelineService; private readonly IValidationService _validationService; - private readonly ListPipelinesHandler _listPipelinesHandler; + private readonly PipelineHandler _pipelineHandler; public PipelineTool( IFabricPipelineService pipelineService, IValidationService validationService, - ListPipelinesHandler listPipelinesHandler) + PipelineHandler pipelineHandler) { _pipelineService = pipelineService; _validationService = validationService; - _listPipelinesHandler = listPipelinesHandler; + _pipelineHandler = pipelineHandler; } [McpServerTool, Description(@"Returns a list of Pipelines from the specified workspace. This API supports pagination.")] @@ -38,7 +38,7 @@ public async Task ListPipelinesAsync( [Description("The workspace ID to list pipelines from (required)")] string workspaceId, [Description("A token for retrieving the next page of results (optional)")] string? continuationToken = null) { - var result = await _listPipelinesHandler.ExecuteAsync(workspaceId, continuationToken); + var result = await _pipelineHandler.ListAsync(workspaceId, continuationToken); if (result.IsSuccess) { var value = result.Value!; @@ -49,7 +49,7 @@ public async Task ListPipelinesAsync( value.ContinuationToken, value.ContinuationUri, value.HasMoreResults, - value.Pipelines + Pipelines = value.Pipelines.Select(p => p.ToFormattedInfo()) }.ToMcpJson(); } return result.ToErrorResponse("listing pipelines").ToMcpJson(); @@ -62,18 +62,11 @@ public async Task CreatePipelineAsync( [Description("The Pipeline description (optional, max 256 characters)")] string? description = null, [Description("The folder ID where the pipeline will be created (optional, defaults to workspace root)")] string? folderId = null) { - try + var result = await _pipelineHandler.CreateAsync(workspaceId, displayName, description, folderId); + if (result.IsSuccess) { - var request = new CreatePipelineRequest - { - DisplayName = displayName, - Description = description, - FolderId = folderId - }; - - var response = await _pipelineService.CreatePipelineAsync(workspaceId, request); - - var result = new + var response = result.Value!.Pipeline; + return new { Success = true, Message = $"Pipeline '{displayName}' created successfully", @@ -84,26 +77,9 @@ public async Task CreatePipelineAsync( WorkspaceId = response.WorkspaceId, FolderId = response.FolderId, CreatedAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") - }; - - return result.ToMcpJson(); - } - catch (ArgumentException ex) - { - return ex.ToValidationError().ToMcpJson(); - } - catch (UnauthorizedAccessException ex) - { - return ex.ToAuthenticationError().ToMcpJson(); - } - catch (HttpRequestException ex) - { - return ex.ToHttpError().ToMcpJson(); - } - catch (Exception ex) - { - return ex.ToOperationError("creating pipeline").ToMcpJson(); + }.ToMcpJson(); } + return result.ToErrorResponse("creating pipeline").ToMcpJson(); } [McpServerTool, Description(@"Gets the metadata of a Pipeline by ID.")] @@ -111,31 +87,12 @@ public async Task GetPipelineAsync( [Description("The workspace ID containing the pipeline (required)")] string workspaceId, [Description("The pipeline ID to retrieve (required)")] string pipelineId) { - try - { - _validationService.ValidateRequiredString(workspaceId, nameof(workspaceId)); - _validationService.ValidateRequiredString(pipelineId, nameof(pipelineId)); - - var pipeline = await _pipelineService.GetPipelineAsync(workspaceId, pipelineId); - - return pipeline.ToFormattedInfo().ToMcpJson(); - } - catch (ArgumentException ex) - { - return ex.ToValidationError().ToMcpJson(); - } - catch (UnauthorizedAccessException ex) - { - return ex.ToAuthenticationError().ToMcpJson(); - } - catch (HttpRequestException ex) - { - return ex.ToHttpError().ToMcpJson(); - } - catch (Exception ex) + var result = await _pipelineHandler.GetAsync(workspaceId, pipelineId); + if (result.IsSuccess) { - return ex.ToOperationError("getting pipeline").ToMcpJson(); + return result.Value!.Pipeline.ToFormattedInfo().ToMcpJson(); } + return result.ToErrorResponse("getting pipeline").ToMcpJson(); } [McpServerTool, Description(@"Gets the definition of a Pipeline. The definition contains the pipeline JSON configuration with base64-encoded parts.")] @@ -310,63 +267,36 @@ public async Task RunPipelineAsync( [Description("The pipeline ID to run (required)")] string pipelineId, [Description("Optional execution data as JSON string for parameterized pipeline runs (optional)")] string? executionDataJson = null) { - try + object? executionData = null; + if (!string.IsNullOrEmpty(executionDataJson)) { - _validationService.ValidateRequiredString(workspaceId, nameof(workspaceId)); - _validationService.ValidateRequiredString(pipelineId, nameof(pipelineId)); - - object? executionData = null; - if (!string.IsNullOrEmpty(executionDataJson)) + try { - try - { - executionData = JsonSerializer.Deserialize(executionDataJson); - } - catch (JsonException ex) - { - throw new ArgumentException($"Invalid executionData JSON format: {ex.Message}"); - } + executionData = JsonSerializer.Deserialize(executionDataJson); } - - var location = await _pipelineService.RunPipelineAsync(workspaceId, pipelineId, executionData); - - // Extract job instance ID from the Location header URL - string? jobInstanceId = null; - if (!string.IsNullOrEmpty(location)) + catch (JsonException ex) { - var segments = new Uri(location).Segments; - jobInstanceId = segments.LastOrDefault()?.TrimEnd('/'); + return ToolResult.Failure($"Invalid executionData JSON format: {ex.Message}", "validation") + .ToErrorResponse("running pipeline").ToMcpJson(); } + } - var result = new + var result = await _pipelineHandler.RunAsync(workspaceId, pipelineId, executionData); + if (result.IsSuccess) + { + var value = result.Value!; + return new { Success = true, - Message = $"Pipeline run triggered successfully", + Message = "Pipeline run triggered successfully", PipelineId = pipelineId, WorkspaceId = workspaceId, - JobInstanceId = jobInstanceId, - LocationUrl = location, + JobInstanceId = value.JobInstanceId, + LocationUrl = value.LocationUrl, Hint = "Use GetPipelineRunStatusAsync with the jobInstanceId to check the run status" - }; - - return result.ToMcpJson(); - } - catch (ArgumentException ex) - { - return ex.ToValidationError().ToMcpJson(); - } - catch (UnauthorizedAccessException ex) - { - return ex.ToAuthenticationError().ToMcpJson(); - } - catch (HttpRequestException ex) - { - return ex.ToHttpError().ToMcpJson(); - } - catch (Exception ex) - { - return ex.ToOperationError("running pipeline").ToMcpJson(); + }.ToMcpJson(); } + return result.ToErrorResponse("running pipeline").ToMcpJson(); } [McpServerTool, Description(@"Gets the status of a Pipeline run (job instance). Use the jobInstanceId returned from RunPipelineAsync to check the run status. Possible statuses: NotStarted, InProgress, Completed, Failed, Cancelled, Deduped.")] diff --git a/DataFactory.MCP.Tests/Infrastructure/McpTestFixture.cs b/DataFactory.MCP.Tests/Infrastructure/McpTestFixture.cs index 672e4585..def92a80 100644 --- a/DataFactory.MCP.Tests/Infrastructure/McpTestFixture.cs +++ b/DataFactory.MCP.Tests/Infrastructure/McpTestFixture.cs @@ -5,6 +5,8 @@ using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Abstractions.Interfaces.DMTSv2; using DataFactory.MCP.Configuration; +using DataFactory.MCP.Handlers.Dataflow; +using DataFactory.MCP.Handlers.Pipeline; using DataFactory.MCP.Infrastructure.Http; using DataFactory.MCP.Services; using DataFactory.MCP.Services.Authentication; @@ -96,6 +98,10 @@ public McpTestFixture() services.AddSingleton(); services.AddScoped(); + // Register handlers + services.AddScoped(); + services.AddScoped(); + // Register tools services.AddScoped(); services.AddScoped(); From 241abef6a4eb8d306608d68581068bc4a0b2b2f1 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Wed, 6 May 2026 20:20:33 -0700 Subject: [PATCH 11/27] Add DataFactory.MCP.Core to NuGet pack-and-sign pipeline - Add Core project to path triggers - Add restore, build, sign, and pack steps for Core - Core .nupkg outputs to same directory for OneBranch auto-publish Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/pack-and-sign-nugets.yaml | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/.pipelines/pack-and-sign-nugets.yaml b/.pipelines/pack-and-sign-nugets.yaml index 501ff345..ca07420c 100644 --- a/.pipelines/pack-and-sign-nugets.yaml +++ b/.pipelines/pack-and-sign-nugets.yaml @@ -15,6 +15,7 @@ trigger: paths: include: - DataFactory.MCP/* + - DataFactory.MCP.Core/* pr: branches: @@ -24,6 +25,7 @@ pr: paths: include: - DataFactory.MCP/* + - DataFactory.MCP.Core/* parameters: - name: "debug" @@ -155,6 +157,45 @@ extends: Get-ChildItem "$mcpAppsDir/dist" -Recurse | Select-Object FullName | Format-Table -AutoSize | Out-String | Write-Host pwsh: true + # --- DataFactory.MCP.Core (library package) --- + - task: DotNetCoreCLI@2 + displayName: "Restore Core packages" + inputs: + command: "restore" + projects: "$(Build.SourcesDirectory)/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj" + feedsToUse: "config" + nugetConfigPath: "$(Build.SourcesDirectory)/nuget.private.config" + verbosityRestore: "Normal" + noCache: true + retryCountOnTaskFailure: 3 + + - task: DotNetCoreCLI@2 + displayName: "Build Core project" + inputs: + command: "build" + projects: "$(Build.SourcesDirectory)/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj" + arguments: "--configuration $(BuildConfiguration) --no-restore" + + - task: onebranch.pipeline.signing@1 + displayName: "Sign Core binary files" + inputs: + command: "sign" + signing_profile: "external_distribution" + files_to_sign: | + **/DataFactory.MCP.Core.dll; + search_root: "$(Build.SourcesDirectory)\\DataFactory.MCP.Core\\bin\\$(BuildConfiguration)\\net10.0" + + - task: DotNetCoreCLI@2 + displayName: "Pack Core NuGet package" + inputs: + command: "pack" + packagesToPack: "$(Build.SourcesDirectory)/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj" + configuration: "$(BuildConfiguration)" + outputDir: "$(OUTPUTROOT)" + nobuild: true + includeSymbols: true + + # --- DataFactory.MCP (tool package) --- - task: DotNetCoreCLI@2 displayName: "Restore packages" inputs: From b19ab072c5ec581d109f116f620bdcd863302605 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 09:10:23 -0700 Subject: [PATCH 12/27] Remove AOT/trim compatibility flags from Core package The Core library uses reflection-based System.Text.Json and DataAnnotations validation which are not trim-safe. Removing IsAotCompatible/EnableTrimAnalyzer flags so consuming projects with PublishTrimmed=true don't fail. Bump version to 0.19.1-beta. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- DataFactory.MCP.Core/DataFactory.MCP.Core.csproj | 4 +--- Directory.Build.props | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj index f1b7f9a7..19016a9f 100644 --- a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj +++ b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj @@ -10,9 +10,7 @@ Microsoft.DataFactory.MCP.Core AI; MCP; server; library Core library for DataFactory MCP server - contains services, tools, and models. - true - true - true + false Microsoft-Fabric.png README.md diff --git a/Directory.Build.props b/Directory.Build.props index e58806f6..797e6bb7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ 0 19 - 0 + 1 beta From 25cbebb803fd2c91f2caaa8469426815d6e89683 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 09:46:48 -0700 Subject: [PATCH 13/27] Suppress trim analysis warnings for IL linker compatibility Add #pragma warning disable IL2026/IL3050 around JsonSerializer and DataAnnotations usage to suppress trim analyzer warnings at build time. Bump version to 0.19.1-beta. Note: These pragmas suppress Roslyn analyzer warnings. For full ILLinker compatibility when consumed as a NuGet package, these will need to be replaced with [UnconditionalSuppressMessage] attributes in a follow-up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs | 2 ++ .../Configuration/FeatureFlagRegistration.cs | 4 ++++ DataFactory.MCP.Core/DataFactory.MCP.Core.csproj | 4 +++- DataFactory.MCP.Core/Extensions/JsonExtensions.cs | 2 ++ .../Services/BackgroundTasks/DataflowRefreshService.cs | 2 ++ .../Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs | 6 ++++++ .../Services/DataflowDefinitionProcessor.cs | 6 ++++++ DataFactory.MCP.Core/Services/McpUserNotificationService.cs | 2 ++ DataFactory.MCP.Core/Services/ValidationService.cs | 2 ++ DataFactory.MCP.Core/Tools/ConnectionsTool.cs | 2 ++ DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs | 4 ++++ DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs | 4 ++++ 12 files changed, 39 insertions(+), 1 deletion(-) diff --git a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs index 27f91f7b..c23aa055 100644 --- a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs +++ b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs @@ -54,6 +54,7 @@ protected void ValidateGuids(params (string value, string name)[] guids) return await response.ReadAsJsonAsync(JsonOptions); } +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access protected async Task PostAsync(string endpoint, object request) where T : class { var url = FabricUrlBuilder.ForFabricApi() @@ -150,4 +151,5 @@ protected async Task PostNoContentAsync(string endpoint, object request) error?.StatusCode, error?.ResponseContent); return false; } +#pragma warning restore IL2026, IL3050 } diff --git a/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs b/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs index cb35fa11..0dc0134c 100644 --- a/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs +++ b/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs @@ -29,15 +29,19 @@ public static IMcpServerBuilder RegisterToolWithFeatureFlag( ILogger logger) where T : class { // Check both configuration parsing and direct args for flexibility +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access var isEnabled = configuration.GetValue(featureFlag) || args.Contains($"--{featureFlag}"); +#pragma warning restore IL2026 logger.LogInformation("Feature flag '{FeatureFlag}' is {Status}", featureFlag, isEnabled ? "ENABLED" : "DISABLED"); if (isEnabled) { logger.LogInformation("Registering {ToolName}...", toolName); +#pragma warning disable IL2091 // Generic type argument does not satisfy 'DynamicallyAccessedMemberTypes' constraint mcpBuilder.WithTools(); +#pragma warning restore IL2091 logger.LogInformation("{ToolName} registered successfully", toolName); } else diff --git a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj index 19016a9f..f1b7f9a7 100644 --- a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj +++ b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj @@ -10,7 +10,9 @@ Microsoft.DataFactory.MCP.Core AI; MCP; server; library Core library for DataFactory MCP server - contains services, tools, and models. - false + true + true + true Microsoft-Fabric.png README.md diff --git a/DataFactory.MCP.Core/Extensions/JsonExtensions.cs b/DataFactory.MCP.Core/Extensions/JsonExtensions.cs index bf9a2ad0..c660fa60 100644 --- a/DataFactory.MCP.Core/Extensions/JsonExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/JsonExtensions.cs @@ -13,8 +13,10 @@ public static class JsonExtensions /// /// The object to serialize /// The JSON string representation +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public static string ToMcpJson(this object obj) { return JsonSerializer.Serialize(obj, JsonSerializerOptionsProvider.McpResponse); } +#pragma warning restore IL2026, IL3050 } \ No newline at end of file diff --git a/DataFactory.MCP.Core/Services/BackgroundTasks/DataflowRefreshService.cs b/DataFactory.MCP.Core/Services/BackgroundTasks/DataflowRefreshService.cs index 472f5b8d..0a7a35cf 100644 --- a/DataFactory.MCP.Core/Services/BackgroundTasks/DataflowRefreshService.cs +++ b/DataFactory.MCP.Core/Services/BackgroundTasks/DataflowRefreshService.cs @@ -61,6 +61,7 @@ public async Task StartRefreshAsync( }; } +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task GetStatusAsync(DataflowRefreshContext context) { var httpClient = _httpClientFactory.CreateClient(HttpClientNames.FabricApi); @@ -90,4 +91,5 @@ public async Task GetStatusAsync(DataflowRefreshContext c FailureReason = jobInstance.FailureReason?.Message }; } +#pragma warning restore IL2026, IL3050 } diff --git a/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs b/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs index 4584a546..a1ddf797 100644 --- a/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs +++ b/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs @@ -68,8 +68,11 @@ public async Task StartAsync() }; } + // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access +#pragma warning disable IL2026, IL3050 var jsonContent = System.Text.Json.JsonSerializer.Serialize(request, JsonSerializerOptionsProvider.FabricApi); +#pragma warning restore IL2026, IL3050 var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); _logger.LogInformation("Starting dataflow refresh: POST {Url}", url); @@ -153,8 +156,11 @@ public async Task CheckStatusAsync() var response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); + // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access +#pragma warning disable IL2026, IL3050 var jobInstance = await response.Content.ReadFromJsonAsync( JsonSerializerOptionsProvider.FabricApi); +#pragma warning restore IL2026, IL3050 if (jobInstance == null) { diff --git a/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs b/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs index ee808f85..e19bc92c 100644 --- a/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs +++ b/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs @@ -64,6 +64,7 @@ public DecodedDataflowDefinition DecodeDefinition(DataflowDefinition rawDefiniti return decoded; } +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public DataflowDefinition AddConnectionsToDefinition( DataflowDefinition definition, IEnumerable<(Connection Connection, string ConnectionId, string? ClusterId)> connections, @@ -168,7 +169,9 @@ private Dictionary CreateUpdatedQueryMetadataWithConnections( metadataDict["connections"] = connectionsList; return metadataDict; } +#pragma warning restore IL2026, IL3050 +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public DataflowDefinition AddOrUpdateQueryInDefinition( DataflowDefinition definition, string queryName, @@ -215,6 +218,7 @@ public DataflowDefinition AddOrUpdateQueryInDefinition( return definition; } +#pragma warning restore IL2026, IL3050 /// /// Extracts the destination query name from a [DataDestinations] attribute. @@ -464,6 +468,7 @@ private Dictionary CreateUpdatedQueryMetadataWithQuery( return metadataDict; } +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public DataflowDefinition SyncMashupInDefinition( DataflowDefinition definition, string newMashupDocument, @@ -503,6 +508,7 @@ public DataflowDefinition SyncMashupInDefinition( return definition; } +#pragma warning restore IL2026, IL3050 private Dictionary SyncQueryMetadata( JsonElement currentMetadata, diff --git a/DataFactory.MCP.Core/Services/McpUserNotificationService.cs b/DataFactory.MCP.Core/Services/McpUserNotificationService.cs index c4c67a99..c6045af0 100644 --- a/DataFactory.MCP.Core/Services/McpUserNotificationService.cs +++ b/DataFactory.MCP.Core/Services/McpUserNotificationService.cs @@ -49,12 +49,14 @@ public async Task NotifyAsync(string title, string message, NotificationLevel le timestamp = DateTime.UtcNow.ToString("o") }; +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access var notificationParams = new LoggingMessageNotificationParams { Level = mcpLevel, Logger = "UserNotification", Data = JsonSerializer.SerializeToElement(data) }; +#pragma warning restore IL2026, IL3050 await session.SendNotificationAsync( NotificationMethods.LoggingMessageNotification, diff --git a/DataFactory.MCP.Core/Services/ValidationService.cs b/DataFactory.MCP.Core/Services/ValidationService.cs index 069352c8..127a5c37 100644 --- a/DataFactory.MCP.Core/Services/ValidationService.cs +++ b/DataFactory.MCP.Core/Services/ValidationService.cs @@ -9,6 +9,7 @@ namespace DataFactory.MCP.Services; /// public class ValidationService : IValidationService { +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public void ValidateAndThrow(T obj, string parameterName) where T : class { if (obj == null) @@ -39,6 +40,7 @@ public IList Validate(T obj) where T : class return validationResults; } +#pragma warning restore IL2026 public void ValidateRequiredString(string value, string parameterName, int? maxLength = null) { diff --git a/DataFactory.MCP.Core/Tools/ConnectionsTool.cs b/DataFactory.MCP.Core/Tools/ConnectionsTool.cs index 36929beb..bb01f87f 100644 --- a/DataFactory.MCP.Core/Tools/ConnectionsTool.cs +++ b/DataFactory.MCP.Core/Tools/ConnectionsTool.cs @@ -151,6 +151,7 @@ public async Task GetConnectionAsync( } [McpServerTool, Description(@"Creates a new data source connection. Supports cloud, on-premises (gateway), and virtual network connectivity types.")] +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task CreateConnectionAsync( [Description("Display name for the connection")] string connectionName, [Description("Connection type identifier, e.g. 'SQL', 'Web', 'AzureBlobs', etc.")] string connectionType = "SQL", @@ -263,4 +264,5 @@ public async Task CreateConnectionAsync( return ex.ToOperationError("creating connection").ToMcpJson(); } } +#pragma warning restore IL2026, IL3050 } diff --git a/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs b/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs index 80d5db64..b952fc42 100644 --- a/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs +++ b/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs @@ -324,6 +324,7 @@ public async Task UpdateCopyJobDefinitionAsync( } [McpServerTool, Description(@"Runs a Copy Job on demand. Returns a job instance ID that can be used to track the run status with GetCopyJobRunStatusAsync.")] +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task RunCopyJobAsync( [Description("The workspace ID containing the copy job (required)")] string workspaceId, [Description("The copy job ID to run (required)")] string copyJobId, @@ -387,6 +388,7 @@ public async Task RunCopyJobAsync( return ex.ToOperationError("running copy job").ToMcpJson(); } } +#pragma warning restore IL2026, IL3050 [McpServerTool, Description(@"Gets the status of a Copy Job run (job instance). Use the jobInstanceId returned from RunCopyJobAsync to check the run status. Possible statuses: NotStarted, InProgress, Completed, Failed, Cancelled, Deduped.")] public async Task GetCopyJobRunStatusAsync( @@ -437,6 +439,7 @@ public async Task GetCopyJobRunStatusAsync( } [McpServerTool, Description(@"Creates a schedule for a Copy Job. Supports Cron (interval-based), Daily, Weekly, and Monthly schedule types. An item can have up to 20 schedules.")] +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task CreateCopyJobScheduleAsync( [Description("The workspace ID containing the copy job (required)")] string workspaceId, [Description("The copy job ID to schedule (required)")] string copyJobId, @@ -504,6 +507,7 @@ public async Task CreateCopyJobScheduleAsync( return ex.ToOperationError("creating copy job schedule").ToMcpJson(); } } +#pragma warning restore IL2026, IL3050 [McpServerTool, Description(@"Lists all schedules for a Copy Job. Returns the schedule configurations, status, and owner information.")] public async Task ListCopyJobSchedulesAsync( diff --git a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs index da470b40..d313f5c3 100644 --- a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs +++ b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs @@ -262,6 +262,7 @@ public async Task UpdatePipelineDefinitionAsync( } [McpServerTool, Description(@"Runs a Pipeline on demand. Returns a job instance ID that can be used to track the run status with GetPipelineRunStatusAsync.")] +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task RunPipelineAsync( [Description("The workspace ID containing the pipeline (required)")] string workspaceId, [Description("The pipeline ID to run (required)")] string pipelineId, @@ -298,6 +299,7 @@ public async Task RunPipelineAsync( } return result.ToErrorResponse("running pipeline").ToMcpJson(); } +#pragma warning restore IL2026, IL3050 [McpServerTool, Description(@"Gets the status of a Pipeline run (job instance). Use the jobInstanceId returned from RunPipelineAsync to check the run status. Possible statuses: NotStarted, InProgress, Completed, Failed, Cancelled, Deduped.")] public async Task GetPipelineRunStatusAsync( @@ -348,6 +350,7 @@ public async Task GetPipelineRunStatusAsync( } [McpServerTool, Description(@"Creates a schedule for a Pipeline. Supports Cron (interval-based), Daily, Weekly, and Monthly schedule types. An item can have up to 20 schedules.")] +#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task CreatePipelineScheduleAsync( [Description("The workspace ID containing the pipeline (required)")] string workspaceId, [Description("The pipeline ID to schedule (required)")] string pipelineId, @@ -415,6 +418,7 @@ public async Task CreatePipelineScheduleAsync( return ex.ToOperationError("creating pipeline schedule").ToMcpJson(); } } +#pragma warning restore IL2026, IL3050 [McpServerTool, Description(@"Lists all schedules for a Pipeline. Returns the schedule configurations, status, and owner information.")] public async Task ListPipelineSchedulesAsync( From fbf2a89bbfe3d80cbafaee3ffc95f07ec5dcdfdb Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 09:50:47 -0700 Subject: [PATCH 14/27] Move IL trim suppressions to project-level NoWarn Replace per-file #pragma warning disable/restore IL2026/IL3050 with a single entry in the csproj. Cleaner files, same effect. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs | 2 -- .../Configuration/FeatureFlagRegistration.cs | 4 +--- DataFactory.MCP.Core/DataFactory.MCP.Core.csproj | 1 + .../Extensions/HttpResponseMessageExtensions.cs | 6 ------ DataFactory.MCP.Core/Extensions/JsonExtensions.cs | 4 +--- .../Models/Connection/ConnectionJsonConverter.cs | 2 -- .../Models/Gateway/GatewayJsonConverter.cs | 2 -- .../Services/BackgroundTasks/DataflowRefreshService.cs | 2 -- .../Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs | 4 ---- .../Services/DataflowDefinitionProcessor.cs | 8 +------- .../Services/McpUserNotificationService.cs | 2 -- DataFactory.MCP.Core/Services/ValidationService.cs | 4 +--- DataFactory.MCP.Core/Tools/ConnectionsTool.cs | 2 -- DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs | 4 ---- DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs | 4 ---- 15 files changed, 5 insertions(+), 46 deletions(-) diff --git a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs index c23aa055..27f91f7b 100644 --- a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs +++ b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs @@ -54,7 +54,6 @@ protected void ValidateGuids(params (string value, string name)[] guids) return await response.ReadAsJsonAsync(JsonOptions); } -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access protected async Task PostAsync(string endpoint, object request) where T : class { var url = FabricUrlBuilder.ForFabricApi() @@ -151,5 +150,4 @@ protected async Task PostNoContentAsync(string endpoint, object request) error?.StatusCode, error?.ResponseContent); return false; } -#pragma warning restore IL2026, IL3050 } diff --git a/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs b/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs index 0dc0134c..30b51836 100644 --- a/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs +++ b/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs @@ -29,10 +29,8 @@ public static IMcpServerBuilder RegisterToolWithFeatureFlag( ILogger logger) where T : class { // Check both configuration parsing and direct args for flexibility -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access var isEnabled = configuration.GetValue(featureFlag) || args.Contains($"--{featureFlag}"); -#pragma warning restore IL2026 logger.LogInformation("Feature flag '{FeatureFlag}' is {Status}", featureFlag, isEnabled ? "ENABLED" : "DISABLED"); @@ -51,4 +49,4 @@ public static IMcpServerBuilder RegisterToolWithFeatureFlag( return mcpBuilder; } -} \ No newline at end of file +} diff --git a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj index f1b7f9a7..fe67dab7 100644 --- a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj +++ b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj @@ -13,6 +13,7 @@ true true true + $(NoWarn);IL2026;IL3050 Microsoft-Fabric.png README.md diff --git a/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs b/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs index f4fc9ba5..256c78e0 100644 --- a/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs @@ -50,7 +50,6 @@ public static class HttpResponseMessageExtensions /// Cancellation token /// The deserialized object /// Thrown when the response indicates failure -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public static async Task ReadAsJsonAsync( this HttpResponseMessage response, JsonSerializerOptions? options = null, @@ -67,7 +66,6 @@ public static class HttpResponseMessageExtensions return JsonSerializer.Deserialize(content, options ?? JsonSerializerOptionsProvider.FabricApi); } -#pragma warning restore IL2026, IL3050 /// /// Reads and deserializes the response content as JSON, returning a default value on failure. @@ -79,7 +77,6 @@ public static class HttpResponseMessageExtensions /// JSON serializer options (uses FabricApi options if null) /// Cancellation token /// The deserialized object or default value on failure -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public static async Task ReadAsJsonOrDefaultAsync( this HttpResponseMessage response, T defaultValue, @@ -101,7 +98,6 @@ public static async Task ReadAsJsonOrDefaultAsync( return JsonSerializer.Deserialize(content, options ?? JsonSerializerOptionsProvider.FabricApi) ?? defaultValue; } -#pragma warning restore IL2026, IL3050 /// /// Ensures the response is successful, throwing a detailed FabricApiException on failure. @@ -139,7 +135,6 @@ public static async Task EnsureSuccessOrThrowAsync( /// /// Tries to read the response as JSON, returning success/failure result. /// -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public static async Task<(bool Success, T? Value, FabricApiException? Error)> TryReadAsJsonAsync( this HttpResponseMessage response, JsonSerializerOptions? options = null, @@ -161,7 +156,6 @@ public static async Task EnsureSuccessOrThrowAsync( var value = JsonSerializer.Deserialize(content, options ?? JsonSerializerOptionsProvider.FabricApi); return (true, value, null); } -#pragma warning restore IL2026, IL3050 /// /// Checks if the response indicates a transient failure that could be retried. diff --git a/DataFactory.MCP.Core/Extensions/JsonExtensions.cs b/DataFactory.MCP.Core/Extensions/JsonExtensions.cs index c660fa60..add60ab6 100644 --- a/DataFactory.MCP.Core/Extensions/JsonExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/JsonExtensions.cs @@ -13,10 +13,8 @@ public static class JsonExtensions /// /// The object to serialize /// The JSON string representation -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public static string ToMcpJson(this object obj) { return JsonSerializer.Serialize(obj, JsonSerializerOptionsProvider.McpResponse); } -#pragma warning restore IL2026, IL3050 -} \ No newline at end of file +} diff --git a/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs b/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs index 41632d84..363e8775 100644 --- a/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs +++ b/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs @@ -6,7 +6,6 @@ namespace DataFactory.MCP.Models.Connection; /// /// JSON converter for handling polymorphic Connection types based on ConnectivityType /// -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public class ConnectionJsonConverter : JsonConverter { public override Connection? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -47,4 +46,3 @@ public override void Write(Utf8JsonWriter writer, Connection value, JsonSerializ JsonSerializer.Serialize(writer, value, value.GetType(), jsonOptions); } } -#pragma warning restore IL2026, IL3050 \ No newline at end of file diff --git a/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs b/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs index 2232d89b..7e1ee756 100644 --- a/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs +++ b/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs @@ -6,7 +6,6 @@ namespace DataFactory.MCP.Models.Gateway; /// /// Custom JSON converter for Gateway polymorphic deserialization /// -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public class GatewayJsonConverter : JsonConverter { public override Gateway Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -37,4 +36,3 @@ public override void Write(Utf8JsonWriter writer, Gateway value, JsonSerializerO JsonSerializer.Serialize(writer, value, value.GetType(), options); } } -#pragma warning restore IL2026, IL3050 diff --git a/DataFactory.MCP.Core/Services/BackgroundTasks/DataflowRefreshService.cs b/DataFactory.MCP.Core/Services/BackgroundTasks/DataflowRefreshService.cs index 0a7a35cf..472f5b8d 100644 --- a/DataFactory.MCP.Core/Services/BackgroundTasks/DataflowRefreshService.cs +++ b/DataFactory.MCP.Core/Services/BackgroundTasks/DataflowRefreshService.cs @@ -61,7 +61,6 @@ public async Task StartRefreshAsync( }; } -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task GetStatusAsync(DataflowRefreshContext context) { var httpClient = _httpClientFactory.CreateClient(HttpClientNames.FabricApi); @@ -91,5 +90,4 @@ public async Task GetStatusAsync(DataflowRefreshContext c FailureReason = jobInstance.FailureReason?.Message }; } -#pragma warning restore IL2026, IL3050 } diff --git a/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs b/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs index a1ddf797..5f2da228 100644 --- a/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs +++ b/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs @@ -69,10 +69,8 @@ public async Task StartAsync() } // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access -#pragma warning disable IL2026, IL3050 var jsonContent = System.Text.Json.JsonSerializer.Serialize(request, JsonSerializerOptionsProvider.FabricApi); -#pragma warning restore IL2026, IL3050 var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); _logger.LogInformation("Starting dataflow refresh: POST {Url}", url); @@ -157,10 +155,8 @@ public async Task CheckStatusAsync() response.EnsureSuccessStatusCode(); // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access -#pragma warning disable IL2026, IL3050 var jobInstance = await response.Content.ReadFromJsonAsync( JsonSerializerOptionsProvider.FabricApi); -#pragma warning restore IL2026, IL3050 if (jobInstance == null) { diff --git a/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs b/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs index e19bc92c..687dba02 100644 --- a/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs +++ b/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs @@ -64,7 +64,6 @@ public DecodedDataflowDefinition DecodeDefinition(DataflowDefinition rawDefiniti return decoded; } -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public DataflowDefinition AddConnectionsToDefinition( DataflowDefinition definition, IEnumerable<(Connection Connection, string ConnectionId, string? ClusterId)> connections, @@ -169,9 +168,7 @@ private Dictionary CreateUpdatedQueryMetadataWithConnections( metadataDict["connections"] = connectionsList; return metadataDict; } -#pragma warning restore IL2026, IL3050 -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public DataflowDefinition AddOrUpdateQueryInDefinition( DataflowDefinition definition, string queryName, @@ -218,7 +215,6 @@ public DataflowDefinition AddOrUpdateQueryInDefinition( return definition; } -#pragma warning restore IL2026, IL3050 /// /// Extracts the destination query name from a [DataDestinations] attribute. @@ -468,7 +464,6 @@ private Dictionary CreateUpdatedQueryMetadataWithQuery( return metadataDict; } -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public DataflowDefinition SyncMashupInDefinition( DataflowDefinition definition, string newMashupDocument, @@ -508,7 +503,6 @@ public DataflowDefinition SyncMashupInDefinition( return definition; } -#pragma warning restore IL2026, IL3050 private Dictionary SyncQueryMetadata( JsonElement currentMetadata, @@ -586,4 +580,4 @@ existingEntry is Dictionary existingDict && return metadataDict; } -} \ No newline at end of file +} diff --git a/DataFactory.MCP.Core/Services/McpUserNotificationService.cs b/DataFactory.MCP.Core/Services/McpUserNotificationService.cs index c6045af0..c4c67a99 100644 --- a/DataFactory.MCP.Core/Services/McpUserNotificationService.cs +++ b/DataFactory.MCP.Core/Services/McpUserNotificationService.cs @@ -49,14 +49,12 @@ public async Task NotifyAsync(string title, string message, NotificationLevel le timestamp = DateTime.UtcNow.ToString("o") }; -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access var notificationParams = new LoggingMessageNotificationParams { Level = mcpLevel, Logger = "UserNotification", Data = JsonSerializer.SerializeToElement(data) }; -#pragma warning restore IL2026, IL3050 await session.SendNotificationAsync( NotificationMethods.LoggingMessageNotification, diff --git a/DataFactory.MCP.Core/Services/ValidationService.cs b/DataFactory.MCP.Core/Services/ValidationService.cs index 127a5c37..0683a3fb 100644 --- a/DataFactory.MCP.Core/Services/ValidationService.cs +++ b/DataFactory.MCP.Core/Services/ValidationService.cs @@ -9,7 +9,6 @@ namespace DataFactory.MCP.Services; /// public class ValidationService : IValidationService { -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public void ValidateAndThrow(T obj, string parameterName) where T : class { if (obj == null) @@ -40,7 +39,6 @@ public IList Validate(T obj) where T : class return validationResults; } -#pragma warning restore IL2026 public void ValidateRequiredString(string value, string parameterName, int? maxLength = null) { @@ -67,4 +65,4 @@ public void ValidateGuid(string value, string parameterName) throw new ArgumentException($"{parameterName} must be a valid GUID", parameterName); } } -} \ No newline at end of file +} diff --git a/DataFactory.MCP.Core/Tools/ConnectionsTool.cs b/DataFactory.MCP.Core/Tools/ConnectionsTool.cs index bb01f87f..36929beb 100644 --- a/DataFactory.MCP.Core/Tools/ConnectionsTool.cs +++ b/DataFactory.MCP.Core/Tools/ConnectionsTool.cs @@ -151,7 +151,6 @@ public async Task GetConnectionAsync( } [McpServerTool, Description(@"Creates a new data source connection. Supports cloud, on-premises (gateway), and virtual network connectivity types.")] -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task CreateConnectionAsync( [Description("Display name for the connection")] string connectionName, [Description("Connection type identifier, e.g. 'SQL', 'Web', 'AzureBlobs', etc.")] string connectionType = "SQL", @@ -264,5 +263,4 @@ public async Task CreateConnectionAsync( return ex.ToOperationError("creating connection").ToMcpJson(); } } -#pragma warning restore IL2026, IL3050 } diff --git a/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs b/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs index b952fc42..80d5db64 100644 --- a/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs +++ b/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs @@ -324,7 +324,6 @@ public async Task UpdateCopyJobDefinitionAsync( } [McpServerTool, Description(@"Runs a Copy Job on demand. Returns a job instance ID that can be used to track the run status with GetCopyJobRunStatusAsync.")] -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task RunCopyJobAsync( [Description("The workspace ID containing the copy job (required)")] string workspaceId, [Description("The copy job ID to run (required)")] string copyJobId, @@ -388,7 +387,6 @@ public async Task RunCopyJobAsync( return ex.ToOperationError("running copy job").ToMcpJson(); } } -#pragma warning restore IL2026, IL3050 [McpServerTool, Description(@"Gets the status of a Copy Job run (job instance). Use the jobInstanceId returned from RunCopyJobAsync to check the run status. Possible statuses: NotStarted, InProgress, Completed, Failed, Cancelled, Deduped.")] public async Task GetCopyJobRunStatusAsync( @@ -439,7 +437,6 @@ public async Task GetCopyJobRunStatusAsync( } [McpServerTool, Description(@"Creates a schedule for a Copy Job. Supports Cron (interval-based), Daily, Weekly, and Monthly schedule types. An item can have up to 20 schedules.")] -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task CreateCopyJobScheduleAsync( [Description("The workspace ID containing the copy job (required)")] string workspaceId, [Description("The copy job ID to schedule (required)")] string copyJobId, @@ -507,7 +504,6 @@ public async Task CreateCopyJobScheduleAsync( return ex.ToOperationError("creating copy job schedule").ToMcpJson(); } } -#pragma warning restore IL2026, IL3050 [McpServerTool, Description(@"Lists all schedules for a Copy Job. Returns the schedule configurations, status, and owner information.")] public async Task ListCopyJobSchedulesAsync( diff --git a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs index d313f5c3..da470b40 100644 --- a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs +++ b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs @@ -262,7 +262,6 @@ public async Task UpdatePipelineDefinitionAsync( } [McpServerTool, Description(@"Runs a Pipeline on demand. Returns a job instance ID that can be used to track the run status with GetPipelineRunStatusAsync.")] -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task RunPipelineAsync( [Description("The workspace ID containing the pipeline (required)")] string workspaceId, [Description("The pipeline ID to run (required)")] string pipelineId, @@ -299,7 +298,6 @@ public async Task RunPipelineAsync( } return result.ToErrorResponse("running pipeline").ToMcpJson(); } -#pragma warning restore IL2026, IL3050 [McpServerTool, Description(@"Gets the status of a Pipeline run (job instance). Use the jobInstanceId returned from RunPipelineAsync to check the run status. Possible statuses: NotStarted, InProgress, Completed, Failed, Cancelled, Deduped.")] public async Task GetPipelineRunStatusAsync( @@ -350,7 +348,6 @@ public async Task GetPipelineRunStatusAsync( } [McpServerTool, Description(@"Creates a schedule for a Pipeline. Supports Cron (interval-based), Daily, Weekly, and Monthly schedule types. An item can have up to 20 schedules.")] -#pragma warning disable IL2026, IL3050 // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access public async Task CreatePipelineScheduleAsync( [Description("The workspace ID containing the pipeline (required)")] string workspaceId, [Description("The pipeline ID to schedule (required)")] string pipelineId, @@ -418,7 +415,6 @@ public async Task CreatePipelineScheduleAsync( return ex.ToOperationError("creating pipeline schedule").ToMcpJson(); } } -#pragma warning restore IL2026, IL3050 [McpServerTool, Description(@"Lists all schedules for a Pipeline. Returns the schedule configurations, status, and owner information.")] public async Task ListPipelineSchedulesAsync( From cd836bc6875365f4771a0c777022d71b064f1d86 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 10:00:32 -0700 Subject: [PATCH 15/27] Add DataflowQueryHandler and wire into DataflowQueryTool Extract query execution logic into DataflowQueryHandler following the shared handler pattern. DataflowQueryTool is now a thin MCP wrapper that delegates to the handler. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Extensions/ServiceCollectionExtensions.cs | 1 + .../Handlers/Dataflow/DataflowQueryHandler.cs | 79 +++++++++++++++++++ .../Tools/Dataflow/DataflowQueryTool.cs | 57 +++---------- 3 files changed, 89 insertions(+), 48 deletions(-) create mode 100644 DataFactory.MCP.Core/Handlers/Dataflow/DataflowQueryHandler.cs diff --git a/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs b/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs index 1aa07d9a..c415de73 100644 --- a/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs @@ -73,6 +73,7 @@ public static IServiceCollection AddDataFactoryMcpServices(this IServiceCollecti .AddSingleton() // Dataflow handlers .AddSingleton() + .AddSingleton() // Copy Job service .AddSingleton() // Session accessor for background notifications diff --git a/DataFactory.MCP.Core/Handlers/Dataflow/DataflowQueryHandler.cs b/DataFactory.MCP.Core/Handlers/Dataflow/DataflowQueryHandler.cs new file mode 100644 index 00000000..4147162b --- /dev/null +++ b/DataFactory.MCP.Core/Handlers/Dataflow/DataflowQueryHandler.cs @@ -0,0 +1,79 @@ +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Extensions; +using DataFactory.MCP.Models; +using DataFactory.MCP.Models.Dataflow.Query; + +namespace DataFactory.MCP.Handlers.Dataflow; + +public record ExecuteQueryResult( + bool Success, + object? Data, + QueryResultSummary? Summary); + +public class DataflowQueryHandler(IFabricDataflowService dataflowService) +{ + public async Task> ExecuteQueryAsync( + string workspaceId, + string dataflowId, + string queryName, + string customMashupDocument) + { + if (string.IsNullOrWhiteSpace(workspaceId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("workspaceId"), "validation"); + if (string.IsNullOrWhiteSpace(dataflowId)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("dataflowId"), "validation"); + if (string.IsNullOrWhiteSpace(queryName)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("queryName"), "validation"); + if (string.IsNullOrWhiteSpace(customMashupDocument)) + return ToolResult.Failure(Messages.InvalidParameterEmpty("customMashupDocument"), "validation"); + + try + { + // Auto-wrap the query if it's not already in section format + var wrappedQuery = customMashupDocument.WrapForDataflowQuery(queryName); + + var request = new ExecuteDataflowQueryRequest + { + QueryName = queryName, + CustomMashupDocument = wrappedQuery + }; + + var response = await dataflowService.ExecuteQueryAsync(workspaceId, dataflowId, request); + + if (!response.Success) + { + return ToolResult.Failure( + $"Query execution failed for '{queryName}' in dataflow {dataflowId}: {response.Error}", + "operation"); + } + + var data = response.CreateArrowDataReport(); + var result = new ExecuteQueryResult( + Success: true, + Data: data, + Summary: response.Summary); + + return ToolResult.Success(result); + } + catch (UnauthorizedAccessException ex) + { + return ToolResult.Failure( + string.Format(Messages.AuthenticationErrorTemplate, ex.Message), "auth"); + } + catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + return ToolResult.Failure( + "Authentication failed. Please check your credentials.", "auth"); + } + catch (HttpRequestException ex) + { + return ToolResult.Failure( + $"Failed to execute dataflow query: {ex.Message}", "http"); + } + catch (Exception ex) + { + return ToolResult.Failure( + $"Unexpected error executing dataflow query: {ex.Message}", "operation"); + } + } +} diff --git a/DataFactory.MCP.Core/Tools/Dataflow/DataflowQueryTool.cs b/DataFactory.MCP.Core/Tools/Dataflow/DataflowQueryTool.cs index fc080d76..fb176da5 100644 --- a/DataFactory.MCP.Core/Tools/Dataflow/DataflowQueryTool.cs +++ b/DataFactory.MCP.Core/Tools/Dataflow/DataflowQueryTool.cs @@ -1,8 +1,8 @@ using ModelContextProtocol.Server; using System.ComponentModel; -using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Extensions; -using DataFactory.MCP.Models.Dataflow.Query; +using DataFactory.MCP.Handlers; +using DataFactory.MCP.Handlers.Dataflow; namespace DataFactory.MCP.Tools.Dataflow; @@ -12,13 +12,11 @@ namespace DataFactory.MCP.Tools.Dataflow; [McpServerToolType] public class DataflowQueryTool { - private readonly IFabricDataflowService _dataflowService; - private readonly IValidationService _validationService; + private readonly DataflowQueryHandler _handler; - public DataflowQueryTool(IFabricDataflowService dataflowService, IValidationService validationService) + public DataflowQueryTool(DataflowQueryHandler handler) { - _dataflowService = dataflowService ?? throw new ArgumentNullException(nameof(dataflowService)); - _validationService = validationService ?? throw new ArgumentNullException(nameof(validationService)); + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); } [McpServerTool, Description(@"Executes a query against a dataflow and returns the complete results (all data) in Apache Arrow format. This allows you to run M (Power Query) language queries against data sources connected through the dataflow and get the full dataset. @@ -30,48 +28,11 @@ public async Task ExecuteQueryAsync( [Description("The name of the query to execute (required)")] string queryName, [Description("The M (Power Query) language query to execute. Can be either a raw M expression (which will be auto-wrapped) or a complete section document. Results will be returned as structured data - format the table.rows as a markdown table for user display.")] string customMashupDocument) { - try - { - // Validate required parameters using validation service - _validationService.ValidateRequiredString(workspaceId, nameof(workspaceId)); - _validationService.ValidateRequiredString(dataflowId, nameof(dataflowId)); - _validationService.ValidateRequiredString(queryName, nameof(queryName)); - _validationService.ValidateRequiredString(customMashupDocument, nameof(customMashupDocument)); + var result = await _handler.ExecuteQueryAsync(workspaceId, dataflowId, queryName, customMashupDocument); - // Auto-wrap the query if it's not already in section format - var wrappedQuery = customMashupDocument.WrapForDataflowQuery(queryName); + if (result.IsSuccess) + return result.Value!.Data!.ToMcpJson(); - // Execute the query - var request = new ExecuteDataflowQueryRequest - { - QueryName = queryName, - CustomMashupDocument = wrappedQuery - }; - - var response = await _dataflowService.ExecuteQueryAsync(workspaceId, dataflowId, request); - - // Return formatted response - var result = response.Success - ? response.CreateArrowDataReport() - : response.ToQueryExecutionError(workspaceId, dataflowId, queryName); - - return result.ToMcpJson(); - } - catch (ArgumentException ex) - { - return ex.ToValidationError().ToMcpJson(); - } - catch (UnauthorizedAccessException ex) - { - return ex.ToAuthenticationError().ToMcpJson(); - } - catch (HttpRequestException ex) - { - return ex.ToHttpError().ToMcpJson(); - } - catch (Exception ex) - { - return ex.ToOperationError("executing dataflow query").ToMcpJson(); - } + return result.ToErrorResponse("executing dataflow query").ToMcpJson(); } } From fb132289092d5d0a9f3681813225bc6f3ef55ea1 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 10:13:50 -0700 Subject: [PATCH 16/27] Revert comment-only change in DataflowRefreshJob.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs b/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs index 5f2da228..4584a546 100644 --- a/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs +++ b/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs @@ -68,7 +68,6 @@ public async Task StartAsync() }; } - // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access var jsonContent = System.Text.Json.JsonSerializer.Serialize(request, JsonSerializerOptionsProvider.FabricApi); var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); @@ -154,7 +153,6 @@ public async Task CheckStatusAsync() var response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); - // Members annotated with 'RequiresUnreferencedCodeAttribute'/'RequiresDynamicCodeAttribute' require dynamic access var jobInstance = await response.Content.ReadFromJsonAsync( JsonSerializerOptionsProvider.FabricApi); From bb6369ddde0430d1eb08897271a489a6a684d9e2 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 10:23:16 -0700 Subject: [PATCH 17/27] Move IL2091 suppression to project-level NoWarn, revert whitespace-only changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs | 4 +--- DataFactory.MCP.Core/DataFactory.MCP.Core.csproj | 2 +- DataFactory.MCP.Core/Extensions/JsonExtensions.cs | 2 +- .../Models/Connection/ConnectionJsonConverter.cs | 2 +- DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs | 2 +- DataFactory.MCP.Core/Services/ValidationService.cs | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs b/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs index 30b51836..cb35fa11 100644 --- a/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs +++ b/DataFactory.MCP.Core/Configuration/FeatureFlagRegistration.cs @@ -37,9 +37,7 @@ public static IMcpServerBuilder RegisterToolWithFeatureFlag( if (isEnabled) { logger.LogInformation("Registering {ToolName}...", toolName); -#pragma warning disable IL2091 // Generic type argument does not satisfy 'DynamicallyAccessedMemberTypes' constraint mcpBuilder.WithTools(); -#pragma warning restore IL2091 logger.LogInformation("{ToolName} registered successfully", toolName); } else @@ -49,4 +47,4 @@ public static IMcpServerBuilder RegisterToolWithFeatureFlag( return mcpBuilder; } -} +} \ No newline at end of file diff --git a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj index fe67dab7..4c457777 100644 --- a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj +++ b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj @@ -13,7 +13,7 @@ true true true - $(NoWarn);IL2026;IL3050 + $(NoWarn);IL2026;IL3050;IL2091 Microsoft-Fabric.png README.md diff --git a/DataFactory.MCP.Core/Extensions/JsonExtensions.cs b/DataFactory.MCP.Core/Extensions/JsonExtensions.cs index add60ab6..bf9a2ad0 100644 --- a/DataFactory.MCP.Core/Extensions/JsonExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/JsonExtensions.cs @@ -17,4 +17,4 @@ public static string ToMcpJson(this object obj) { return JsonSerializer.Serialize(obj, JsonSerializerOptionsProvider.McpResponse); } -} +} \ No newline at end of file diff --git a/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs b/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs index 363e8775..ca52b4f4 100644 --- a/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs +++ b/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs @@ -45,4 +45,4 @@ public override void Write(Utf8JsonWriter writer, Connection value, JsonSerializ jsonOptions.Converters.Remove(this); // Prevent infinite recursion JsonSerializer.Serialize(writer, value, value.GetType(), jsonOptions); } -} +} \ No newline at end of file diff --git a/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs b/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs index 687dba02..ee808f85 100644 --- a/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs +++ b/DataFactory.MCP.Core/Services/DataflowDefinitionProcessor.cs @@ -580,4 +580,4 @@ existingEntry is Dictionary existingDict && return metadataDict; } -} +} \ No newline at end of file diff --git a/DataFactory.MCP.Core/Services/ValidationService.cs b/DataFactory.MCP.Core/Services/ValidationService.cs index 0683a3fb..069352c8 100644 --- a/DataFactory.MCP.Core/Services/ValidationService.cs +++ b/DataFactory.MCP.Core/Services/ValidationService.cs @@ -65,4 +65,4 @@ public void ValidateGuid(string value, string parameterName) throw new ArgumentException($"{parameterName} must be a valid GUID", parameterName); } } -} +} \ No newline at end of file From eef43f776f052e1ac3cfdaee5278a41219a7a3d2 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 11:21:00 -0700 Subject: [PATCH 18/27] feat: migrate to source-generated JSON for ILLinker trim safety - Create DataFactoryJsonContext with 40+ registered types - Replace reflection-based JsonSerializer calls with source-gen context - Fix ConnectionJsonConverter and GatewayJsonConverter polymorphic dispatch - Fix FabricServiceBase.PostAsync/PatchAsync serialization - Fix HttpResponseMessageExtensions.ReadAsJsonAsync deserialization - Suppress IL2026 on ValidationService (DataAnnotations requires reflection) - Bump version to 0.20.0-beta Resolves all 16 IL2026 ILLinker trim warnings that would fail CI in Fabric.Mcp.Server (TreatWarningsAsErrors=true). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Abstractions/FabricServiceBase.cs | 14 +-- .../Configuration/DataFactoryJsonContext.cs | 96 +++++++++++++++++++ .../HttpResponseMessageExtensions.cs | 6 +- .../Extensions/JsonExtensions.cs | 5 +- .../Models/Connection/Connection.cs | 1 + .../Connection/ConnectionJsonConverter.cs | 39 +++++--- .../Models/Gateway/GatewayJsonConverter.cs | 27 ++++-- .../Jobs/DataflowRefreshJob.cs | 11 ++- .../DMTSv2/GatewayClusterDatasourceService.cs | 6 +- .../Services/ValidationService.cs | 3 + Directory.Build.props | 4 +- 11 files changed, 173 insertions(+), 39 deletions(-) create mode 100644 DataFactory.MCP.Core/Configuration/DataFactoryJsonContext.cs diff --git a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs index 27f91f7b..f8fb167d 100644 --- a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs +++ b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs @@ -61,7 +61,7 @@ protected void ValidateGuids(params (string value, string name)[] guids) .Build(); Logger.LogInformation("Posting to: {Url}", url); - var jsonContent = JsonSerializer.Serialize(request, JsonOptions); + var jsonContent = JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); Logger.LogInformation("Request body: {Body}", jsonContent); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); @@ -79,7 +79,7 @@ protected void ValidateGuids(params (string value, string name)[] guids) .Build(); Logger.LogInformation("Patching: {Url}", url); - var jsonContent = JsonSerializer.Serialize(request, JsonOptions); + var jsonContent = JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var httpRequest = new HttpRequestMessage(HttpMethod.Patch, url) { Content = content }; @@ -97,7 +97,7 @@ protected async Task PostAsBytesAsync(string endpoint, object request) .Build(); Logger.LogInformation("Posting to: {Url}", url); - var jsonContent = JsonSerializer.Serialize(request, JsonOptions); + var jsonContent = JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var response = await HttpClient.PostAsync(url, content); @@ -115,7 +115,7 @@ protected async Task PostAsBytesAsync(string endpoint, object request) .Build(); Logger.LogInformation("Posting to: {Url}", url); - var jsonContent = request != null ? JsonSerializer.Serialize(request, JsonOptions) : null; + var jsonContent = request != null ? JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default) : null; var content = jsonContent != null ? new StringContent(jsonContent, Encoding.UTF8, "application/json") : null; var response = await HttpClient.PostAsync(url, content); @@ -135,7 +135,7 @@ protected async Task PostNoContentAsync(string endpoint, object request) .Build(); Logger.LogInformation("Posting to: {Url}", url); - var jsonContent = JsonSerializer.Serialize(request, JsonOptions); + var jsonContent = JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var response = await HttpClient.PostAsync(url, content); @@ -145,9 +145,9 @@ protected async Task PostNoContentAsync(string endpoint, object request) return true; } - var (_, _, error) = await response.TryReadAsJsonAsync(JsonOptions); + var errorContent = await response.Content.ReadAsStringAsync(); Logger.LogError("API POST request failed. Status: {StatusCode}, Content: {Content}", - error?.StatusCode, error?.ResponseContent); + response.StatusCode, errorContent); return false; } } diff --git a/DataFactory.MCP.Core/Configuration/DataFactoryJsonContext.cs b/DataFactory.MCP.Core/Configuration/DataFactoryJsonContext.cs new file mode 100644 index 00000000..59523f9a --- /dev/null +++ b/DataFactory.MCP.Core/Configuration/DataFactoryJsonContext.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using DataFactory.MCP.Models.Capacity; +using DataFactory.MCP.Models.Connection; +using DataFactory.MCP.Models.CopyJob; +using DataFactory.MCP.Models.CopyJob.Definition; +using DataFactory.MCP.Models.Dataflow; +using DataFactory.MCP.Models.Dataflow.Definition; +using DataFactory.MCP.Models.Dataflow.Query; +using DataFactory.MCP.Models.Gateway; +using DataFactory.MCP.Models.Pipeline; +using DataFactory.MCP.Models.Pipeline.Definition; +using DataFactory.MCP.Models.Pipeline.Schedule; +using DataFactory.MCP.Models.Workspace; +using DataFactory.MCP.Services.DMTSv2; + +namespace DataFactory.MCP.Configuration; + +[JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +// Connection types (concrete subtypes only - base type has custom converter) +[JsonSerializable(typeof(ShareableCloudConnection))] +[JsonSerializable(typeof(PersonalCloudConnection))] +[JsonSerializable(typeof(OnPremisesGatewayConnection))] +[JsonSerializable(typeof(OnPremisesGatewayPersonalConnection))] +[JsonSerializable(typeof(VirtualNetworkGatewayConnection))] +[JsonSerializable(typeof(ListConnectionsResponse))] +[JsonSerializable(typeof(CreateConnectionRequest))] +[JsonSerializable(typeof(ListSupportedConnectionTypesResponse))] +// Gateway types (concrete subtypes only - base type has custom converter) +[JsonSerializable(typeof(OnPremisesGateway))] +[JsonSerializable(typeof(OnPremisesGatewayPersonal))] +[JsonSerializable(typeof(VirtualNetworkGateway))] +[JsonSerializable(typeof(ListGatewaysResponse))] +[JsonSerializable(typeof(CreateVirtualnetworkGatewayRequest))] +[JsonSerializable(typeof(CreateVirtualnetworkGatewayResponse))] +// Capacity types +[JsonSerializable(typeof(Capacity))] +[JsonSerializable(typeof(ListCapacitiesResponse))] +// Workspace types +[JsonSerializable(typeof(Workspace))] +[JsonSerializable(typeof(ListWorkspacesResponse))] +// CopyJob types +[JsonSerializable(typeof(CopyJob))] +[JsonSerializable(typeof(CreateCopyJobRequest))] +[JsonSerializable(typeof(CreateCopyJobResponse))] +[JsonSerializable(typeof(UpdateCopyJobRequest))] +[JsonSerializable(typeof(ListCopyJobsResponse))] +// CopyJob Definition types +[JsonSerializable(typeof(CopyJobDefinition))] +[JsonSerializable(typeof(CopyJobDefinitionPart))] +[JsonSerializable(typeof(UpdateCopyJobDefinitionRequest))] +[JsonSerializable(typeof(GetCopyJobDefinitionResponse))] +// Pipeline types +[JsonSerializable(typeof(Pipeline))] +[JsonSerializable(typeof(CreatePipelineRequest))] +[JsonSerializable(typeof(CreatePipelineResponse))] +[JsonSerializable(typeof(UpdatePipelineRequest))] +[JsonSerializable(typeof(ListPipelinesResponse))] +[JsonSerializable(typeof(Models.Pipeline.ItemJobInstance), TypeInfoPropertyName = "PipelineItemJobInstance")] +// Pipeline Schedule types +[JsonSerializable(typeof(CreateScheduleRequest))] +[JsonSerializable(typeof(ItemSchedule))] +[JsonSerializable(typeof(ListSchedulesResponse))] +// Pipeline Definition types +[JsonSerializable(typeof(PipelineDefinition))] +[JsonSerializable(typeof(PipelineDefinitionPart))] +[JsonSerializable(typeof(UpdatePipelineDefinitionRequest))] +[JsonSerializable(typeof(UpdatePipelineDefinitionResponse))] +[JsonSerializable(typeof(GetPipelineDefinitionResponse))] +// Dataflow types +[JsonSerializable(typeof(Dataflow))] +[JsonSerializable(typeof(CreateDataflowRequest))] +[JsonSerializable(typeof(CreateDataflowResponse))] +[JsonSerializable(typeof(ListDataflowsResponse))] +// Dataflow Query types +[JsonSerializable(typeof(ExecuteDataflowQueryRequest))] +[JsonSerializable(typeof(ExecuteDataflowQueryResponse))] +// Dataflow BackgroundTask types +[JsonSerializable(typeof(DataFactory.MCP.Models.Dataflow.BackgroundTask.RunOnDemandExecuteRequest))] +[JsonSerializable(typeof(DataFactory.MCP.Models.Dataflow.BackgroundTask.ItemJobInstance), TypeInfoPropertyName = "DataflowItemJobInstance")] +// Dataflow Definition types +[JsonSerializable(typeof(DataflowDefinition))] +[JsonSerializable(typeof(DataflowDefinitionPart))] +[JsonSerializable(typeof(GetDataflowDefinitionHttpResponse))] +[JsonSerializable(typeof(UpdateDataflowDefinitionRequest))] +[JsonSerializable(typeof(UpdateDataflowDefinitionResponse))] +// Gateway Cluster Datasource types (DMTSv2) +[JsonSerializable(typeof(GatewayClusterDatasourceService.GatewayClusterDatasourcesResponse))] +[JsonSerializable(typeof(GatewayClusterDatasourceService.CloudDatasourceInfo))] +internal sealed partial class DataFactoryJsonContext : JsonSerializerContext +{ +} diff --git a/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs b/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs index 256c78e0..efeb2b0c 100644 --- a/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/HttpResponseMessageExtensions.cs @@ -64,7 +64,7 @@ public static class HttpResponseMessageExtensions return null; } - return JsonSerializer.Deserialize(content, options ?? JsonSerializerOptionsProvider.FabricApi); + return (T?)JsonSerializer.Deserialize(content, typeof(T), DataFactoryJsonContext.Default); } /// @@ -95,7 +95,7 @@ public static async Task ReadAsJsonOrDefaultAsync( return defaultValue; } - return JsonSerializer.Deserialize(content, options ?? JsonSerializerOptionsProvider.FabricApi) + return (T?)JsonSerializer.Deserialize(content, typeof(T), DataFactoryJsonContext.Default) ?? defaultValue; } @@ -153,7 +153,7 @@ public static async Task EnsureSuccessOrThrowAsync( } var content = await response.Content.ReadAsStringAsync(cancellationToken); - var value = JsonSerializer.Deserialize(content, options ?? JsonSerializerOptionsProvider.FabricApi); + var value = (T?)JsonSerializer.Deserialize(content, typeof(T), DataFactoryJsonContext.Default); return (true, value, null); } diff --git a/DataFactory.MCP.Core/Extensions/JsonExtensions.cs b/DataFactory.MCP.Core/Extensions/JsonExtensions.cs index bf9a2ad0..c6e72b18 100644 --- a/DataFactory.MCP.Core/Extensions/JsonExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/JsonExtensions.cs @@ -1,4 +1,5 @@ using DataFactory.MCP.Configuration; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; namespace DataFactory.MCP.Extensions; @@ -9,10 +10,12 @@ namespace DataFactory.MCP.Extensions; public static class JsonExtensions { /// - /// Serializes an object to JSON using consistent MCP formatting + /// Serializes an object to JSON using consistent MCP formatting. + /// Uses reflection-based serialization to support anonymous types and dynamic MCP responses. /// /// The object to serialize /// The JSON string representation + [RequiresUnreferencedCode("MCP response serialization may use reflection for formatting")] public static string ToMcpJson(this object obj) { return JsonSerializer.Serialize(obj, JsonSerializerOptionsProvider.McpResponse); diff --git a/DataFactory.MCP.Core/Models/Connection/Connection.cs b/DataFactory.MCP.Core/Models/Connection/Connection.cs index 427e1eb9..33ee813f 100644 --- a/DataFactory.MCP.Core/Models/Connection/Connection.cs +++ b/DataFactory.MCP.Core/Models/Connection/Connection.cs @@ -5,6 +5,7 @@ namespace DataFactory.MCP.Models.Connection; /// /// Base connection class /// +[JsonConverter(typeof(ConnectionJsonConverter))] public abstract class Connection { [JsonPropertyName("id")] diff --git a/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs b/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs index ca52b4f4..ea4d4915 100644 --- a/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs +++ b/DataFactory.MCP.Core/Models/Connection/ConnectionJsonConverter.cs @@ -1,10 +1,12 @@ using System.Text.Json; using System.Text.Json.Serialization; +using DataFactory.MCP.Configuration; namespace DataFactory.MCP.Models.Connection; /// -/// JSON converter for handling polymorphic Connection types based on ConnectivityType +/// JSON converter for handling polymorphic Connection types based on ConnectivityType. +/// Uses source-generated JsonTypeInfo for trim-safe serialization. /// public class ConnectionJsonConverter : JsonConverter { @@ -25,24 +27,39 @@ public class ConnectionJsonConverter : JsonConverter } var json = root.GetRawText(); - var jsonOptions = new JsonSerializerOptions(options); - jsonOptions.Converters.Remove(this); // Prevent infinite recursion return connectivityType switch { - ConnectivityType.ShareableCloud => JsonSerializer.Deserialize(json, jsonOptions), - ConnectivityType.PersonalCloud => JsonSerializer.Deserialize(json, jsonOptions), - ConnectivityType.OnPremisesGateway => JsonSerializer.Deserialize(json, jsonOptions), - ConnectivityType.OnPremisesGatewayPersonal => JsonSerializer.Deserialize(json, jsonOptions), - ConnectivityType.VirtualNetworkGateway => JsonSerializer.Deserialize(json, jsonOptions), + ConnectivityType.ShareableCloud => JsonSerializer.Deserialize(json, DataFactoryJsonContext.Default.ShareableCloudConnection), + ConnectivityType.PersonalCloud => JsonSerializer.Deserialize(json, DataFactoryJsonContext.Default.PersonalCloudConnection), + ConnectivityType.OnPremisesGateway => JsonSerializer.Deserialize(json, DataFactoryJsonContext.Default.OnPremisesGatewayConnection), + ConnectivityType.OnPremisesGatewayPersonal => JsonSerializer.Deserialize(json, DataFactoryJsonContext.Default.OnPremisesGatewayPersonalConnection), + ConnectivityType.VirtualNetworkGateway => JsonSerializer.Deserialize(json, DataFactoryJsonContext.Default.VirtualNetworkGatewayConnection), _ => throw new JsonException($"Unsupported connectivity type: {connectivityType}") }; } public override void Write(Utf8JsonWriter writer, Connection value, JsonSerializerOptions options) { - var jsonOptions = new JsonSerializerOptions(options); - jsonOptions.Converters.Remove(this); // Prevent infinite recursion - JsonSerializer.Serialize(writer, value, value.GetType(), jsonOptions); + switch (value) + { + case ShareableCloudConnection c: + JsonSerializer.Serialize(writer, c, DataFactoryJsonContext.Default.ShareableCloudConnection); + break; + case PersonalCloudConnection c: + JsonSerializer.Serialize(writer, c, DataFactoryJsonContext.Default.PersonalCloudConnection); + break; + case OnPremisesGatewayConnection c: + JsonSerializer.Serialize(writer, c, DataFactoryJsonContext.Default.OnPremisesGatewayConnection); + break; + case OnPremisesGatewayPersonalConnection c: + JsonSerializer.Serialize(writer, c, DataFactoryJsonContext.Default.OnPremisesGatewayPersonalConnection); + break; + case VirtualNetworkGatewayConnection c: + JsonSerializer.Serialize(writer, c, DataFactoryJsonContext.Default.VirtualNetworkGatewayConnection); + break; + default: + throw new JsonException($"Unsupported connection type: {value.GetType()}"); + } } } \ No newline at end of file diff --git a/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs b/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs index 7e1ee756..9fc14add 100644 --- a/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs +++ b/DataFactory.MCP.Core/Models/Gateway/GatewayJsonConverter.cs @@ -1,10 +1,12 @@ using System.Text.Json; using System.Text.Json.Serialization; +using DataFactory.MCP.Configuration; namespace DataFactory.MCP.Models.Gateway; /// -/// Custom JSON converter for Gateway polymorphic deserialization +/// Custom JSON converter for Gateway polymorphic deserialization. +/// Uses source-generated JsonTypeInfo for trim-safe serialization. /// public class GatewayJsonConverter : JsonConverter { @@ -13,7 +15,6 @@ public override Gateway Read(ref Utf8JsonReader reader, Type typeToConvert, Json using JsonDocument doc = JsonDocument.ParseValue(ref reader); JsonElement root = doc.RootElement; - // Get the type property to determine which concrete type to deserialize to if (!root.TryGetProperty("type", out JsonElement typeElement)) { throw new JsonException("Gateway object must have a 'type' property"); @@ -21,18 +22,30 @@ public override Gateway Read(ref Utf8JsonReader reader, Type typeToConvert, Json string gatewayType = typeElement.GetString() ?? string.Empty; - // Deserialize to the appropriate concrete type based on the type field return gatewayType switch { - "OnPremises" => JsonSerializer.Deserialize(root.GetRawText(), options)!, - "OnPremisesPersonal" => JsonSerializer.Deserialize(root.GetRawText(), options)!, - "VirtualNetwork" => JsonSerializer.Deserialize(root.GetRawText(), options)!, + "OnPremises" => JsonSerializer.Deserialize(root.GetRawText(), DataFactoryJsonContext.Default.OnPremisesGateway)!, + "OnPremisesPersonal" => JsonSerializer.Deserialize(root.GetRawText(), DataFactoryJsonContext.Default.OnPremisesGatewayPersonal)!, + "VirtualNetwork" => JsonSerializer.Deserialize(root.GetRawText(), DataFactoryJsonContext.Default.VirtualNetworkGateway)!, _ => throw new JsonException($"Unknown gateway type: {gatewayType}") }; } public override void Write(Utf8JsonWriter writer, Gateway value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, value, value.GetType(), options); + switch (value) + { + case OnPremisesGateway g: + JsonSerializer.Serialize(writer, g, DataFactoryJsonContext.Default.OnPremisesGateway); + break; + case OnPremisesGatewayPersonal g: + JsonSerializer.Serialize(writer, g, DataFactoryJsonContext.Default.OnPremisesGatewayPersonal); + break; + case VirtualNetworkGateway g: + JsonSerializer.Serialize(writer, g, DataFactoryJsonContext.Default.VirtualNetworkGateway); + break; + default: + throw new JsonException($"Unsupported gateway type: {value.GetType()}"); + } } } diff --git a/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs b/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs index 4584a546..d08ccde7 100644 --- a/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs +++ b/DataFactory.MCP.Core/Services/BackgroundTasks/Jobs/DataflowRefreshJob.cs @@ -1,4 +1,4 @@ -using System.Net.Http.Json; +using System.Text.Json; using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Configuration; using DataFactory.MCP.Infrastructure.Http; @@ -68,8 +68,8 @@ public async Task StartAsync() }; } - var jsonContent = System.Text.Json.JsonSerializer.Serialize(request, - JsonSerializerOptionsProvider.FabricApi); + var jsonContent = JsonSerializer.Serialize(request, + typeof(RunOnDemandExecuteRequest), DataFactoryJsonContext.Default); var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); _logger.LogInformation("Starting dataflow refresh: POST {Url}", url); @@ -153,8 +153,9 @@ public async Task CheckStatusAsync() var response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); - var jobInstance = await response.Content.ReadFromJsonAsync( - JsonSerializerOptionsProvider.FabricApi); + var content = await response.Content.ReadAsStringAsync(); + var jobInstance = (ItemJobInstance?)JsonSerializer.Deserialize( + content, typeof(ItemJobInstance), DataFactoryJsonContext.Default); if (jobInstance == null) { diff --git a/DataFactory.MCP.Core/Services/DMTSv2/GatewayClusterDatasourceService.cs b/DataFactory.MCP.Core/Services/DMTSv2/GatewayClusterDatasourceService.cs index 52cf917c..9f83563b 100644 --- a/DataFactory.MCP.Core/Services/DMTSv2/GatewayClusterDatasourceService.cs +++ b/DataFactory.MCP.Core/Services/DMTSv2/GatewayClusterDatasourceService.cs @@ -79,7 +79,7 @@ private async Task> GetCloudDatasourcesAsync() _logger.LogDebug("Fetching cloud datasources from Power BI v2.0 API"); var response = await _httpClient.GetAsync(GatewayClusterDatasourcesUrl); - var result = await response.ReadAsJsonAsync(JsonSerializerOptionsProvider.FabricApi); + var result = await response.ReadAsJsonAsync(); var datasources = result?.Value ?? new List(); @@ -97,7 +97,7 @@ private async Task> GetCloudDatasourcesAsync() /// /// Minimal model for cloud datasource info from the v2 API /// - private sealed class CloudDatasourceInfo + internal sealed class CloudDatasourceInfo { [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; @@ -109,7 +109,7 @@ private sealed class CloudDatasourceInfo /// /// Response wrapper for the gatewayClusterDatasources API /// - private sealed class GatewayClusterDatasourcesResponse + internal sealed class GatewayClusterDatasourcesResponse { [JsonPropertyName("value")] public List Value { get; set; } = new(); diff --git a/DataFactory.MCP.Core/Services/ValidationService.cs b/DataFactory.MCP.Core/Services/ValidationService.cs index 069352c8..95e64f7c 100644 --- a/DataFactory.MCP.Core/Services/ValidationService.cs +++ b/DataFactory.MCP.Core/Services/ValidationService.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Models; @@ -9,6 +10,7 @@ namespace DataFactory.MCP.Services; /// public class ValidationService : IValidationService { + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Validation types are preserved by the application")] public void ValidateAndThrow(T obj, string parameterName) where T : class { if (obj == null) @@ -26,6 +28,7 @@ public void ValidateAndThrow(T obj, string parameterName) where T : class } } + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Validation types are preserved by the application")] public IList Validate(T obj) where T : class { if (obj == null) diff --git a/Directory.Build.props b/Directory.Build.props index 797e6bb7..7ae54283 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,8 +3,8 @@ 0 - 19 - 1 + 20 + 0 beta From 0dfcb66b1dab3d7823659e08e3116c9a2c85692a Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 11:32:09 -0700 Subject: [PATCH 19/27] fix: update npm dependencies to resolve security vulnerabilities - Update @modelcontextprotocol/ext-apps from ^1.0.1 to ^1.7.1 - Run npm audit fix to patch vite, rollup, postcss, picomatch - Resolves all 17 component governance security alerts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Resources/McpApps/package-lock.json | 463 ++++++------------ .../Resources/McpApps/package.json | 2 +- 2 files changed, 160 insertions(+), 305 deletions(-) diff --git a/DataFactory.MCP.Core/Resources/McpApps/package-lock.json b/DataFactory.MCP.Core/Resources/McpApps/package-lock.json index 3df32d99..a34e95e7 100644 --- a/DataFactory.MCP.Core/Resources/McpApps/package-lock.json +++ b/DataFactory.MCP.Core/Resources/McpApps/package-lock.json @@ -8,7 +8,7 @@ "name": "datafactory-mcp-ui-apps", "version": "1.0.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "^1.0.1", + "@modelcontextprotocol/ext-apps": "^1.7.1", "@modelcontextprotocol/sdk": "^1.26.0", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -747,9 +747,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -809,35 +809,21 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.0.1.tgz", - "integrity": "sha512-rAPzBbB5GNgYk216paQjGKUgbNXSy/yeR95c0ni6Y4uvhWI2AeF+ztEOqQFLBMQy/MPM+02pbVK1HaQmQjMwYQ==", - "hasInstallScript": true, + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.1.tgz", + "integrity": "sha512-J3WdG1A4JSSKnSWKyU+895dBVYBV2Utgtf7fUsUK45mlkETm53a/1DR6Pm3hUGKqLLQthZLmpxOg8VPzJi/lyg==", "license": "MIT", "workspaces": [ "examples/*" ], - "optionalDependencies": { - "@oven/bun-darwin-aarch64": "^1.2.21", - "@oven/bun-darwin-x64": "^1.2.21", - "@oven/bun-darwin-x64-baseline": "^1.2.21", - "@oven/bun-linux-aarch64": "^1.2.21", - "@oven/bun-linux-aarch64-musl": "^1.2.21", - "@oven/bun-linux-x64": "^1.2.21", - "@oven/bun-linux-x64-baseline": "^1.2.21", - "@oven/bun-linux-x64-musl": "^1.2.21", - "@oven/bun-linux-x64-musl-baseline": "^1.2.21", - "@oven/bun-windows-x64": "^1.2.21", - "@oven/bun-windows-x64-baseline": "^1.2.21", - "@rollup/rollup-darwin-arm64": "^4.53.3", - "@rollup/rollup-darwin-x64": "^4.53.3", - "@rollup/rollup-linux-arm64-gnu": "^4.53.3", - "@rollup/rollup-linux-x64-gnu": "^4.53.3", - "@rollup/rollup-win32-arm64-msvc": "^4.53.3", - "@rollup/rollup-win32-x64-msvc": "^4.53.3" + "dependencies": { + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">=20" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -852,9 +838,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", - "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -891,149 +877,6 @@ } } }, - "node_modules/@oven/bun-darwin-aarch64": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.3.9.tgz", - "integrity": "sha512-df7smckMWSUfaT5mzwN9Lfpd3ZGkOqo+vmQ8VV2a32gl14v6uZ/qeeo+1RlANXn8M0uzXPWWCkrKZIWSZUR0qw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oven/bun-darwin-x64": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.3.9.tgz", - "integrity": "sha512-YiLxfsPzQqaVvT2a+nxH9do0YfUjrlxF3tKP0b1DDgvfgCcVKGsrQH3Wa82qHgL4dnT8h2bqi94JxXESEuPmcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oven/bun-darwin-x64-baseline": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.3.9.tgz", - "integrity": "sha512-XbhsA2XAFzvFr0vPSV6SNqGxab4xHKdPmVTLqoSHAx9tffrSq/012BDptOskulwnD+YNsrJUx2D2Ve1xvfgGcg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oven/bun-linux-aarch64": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.3.9.tgz", - "integrity": "sha512-VaNQTu0Up4gnwZLQ6/Hmho6jAlLxTQ1PwxEth8EsXHf82FOXXPV5OCQ6KC9mmmocjKlmWFaIGebThrOy8DUo4g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-aarch64-musl": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.3.9.tgz", - "integrity": "sha512-t8uimCVBTw5f9K2QTZE5wN6UOrFETNrh/Xr7qtXT9nAOzaOnIFvYA+HcHbGfi31fRlCVfTxqm/EiCwJ1gEw9YQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-x64": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.3.9.tgz", - "integrity": "sha512-oQyAW3+ugulvXTZ+XYeUMmNPR94sJeMokfHQoKwPvVwhVkgRuMhcLGV2ZesHCADVu30Oz2MFXbgdC8x4/o9dRg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-x64-baseline": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.3.9.tgz", - "integrity": "sha512-nZ12g22cy7pEOBwAxz2tp0wVqekaCn9QRKuGTHqOdLlyAqR4SCdErDvDhUWd51bIyHTQoCmj72TegGTgG0WNPw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-x64-musl": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.3.9.tgz", - "integrity": "sha512-4ZjIUgCxEyKwcKXideB5sX0KJpnHTZtu778w73VNq2uNH2fNpMZv98+DBgJyQ9OfFoRhmKn1bmLmSefvnHzI9w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-x64-musl-baseline": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.3.9.tgz", - "integrity": "sha512-3FXQgtYFsT0YOmAdMcJn56pLM5kzSl6y942rJJIl5l2KummB9Ea3J/vMJMzQk7NCAGhleZGWU/pJSS/uXKGa7w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-windows-x64": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.3.9.tgz", - "integrity": "sha512-/d6vAmgKvkoYlsGPsRPlPmOK1slPis/F40UG02pYwypTH0wmY0smgzdFqR4YmryxFh17XrW1kITv+U99Oajk9Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@oven/bun-windows-x64-baseline": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.3.9.tgz", - "integrity": "sha512-a/+hSrrDpMD7THyXvE2KJy1skxzAD0cnW4K1WjuI/91VqsphjNzvf5t/ZgxEVL4wb6f+hKrSJ5J3aH47zPr61g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1042,9 +885,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", "cpu": [ "arm" ], @@ -1056,9 +899,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", "cpu": [ "arm64" ], @@ -1070,12 +913,13 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1083,12 +927,13 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1096,9 +941,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", "cpu": [ "arm64" ], @@ -1110,9 +955,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", "cpu": [ "x64" ], @@ -1124,9 +969,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", "cpu": [ "arm" ], @@ -1138,9 +983,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", "cpu": [ "arm" ], @@ -1152,12 +997,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1165,9 +1011,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", "cpu": [ "arm64" ], @@ -1179,9 +1025,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", "cpu": [ "loong64" ], @@ -1193,9 +1039,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", "cpu": [ "loong64" ], @@ -1207,9 +1053,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", "cpu": [ "ppc64" ], @@ -1221,9 +1067,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", "cpu": [ "ppc64" ], @@ -1235,9 +1081,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", "cpu": [ "riscv64" ], @@ -1249,9 +1095,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", "cpu": [ "riscv64" ], @@ -1263,9 +1109,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", "cpu": [ "s390x" ], @@ -1277,12 +1123,13 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1290,9 +1137,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", "cpu": [ "x64" ], @@ -1304,9 +1151,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", "cpu": [ "x64" ], @@ -1318,9 +1165,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", "cpu": [ "arm64" ], @@ -1332,12 +1179,13 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1345,9 +1193,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", "cpu": [ "ia32" ], @@ -1359,9 +1207,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", "cpu": [ "x64" ], @@ -1373,18 +1221,25 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1981,12 +1836,12 @@ } }, "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz", + "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==", "license": "MIT", "dependencies": { - "ip-address": "10.0.1" + "ip-address": "^10.2.0" }, "engines": { "node": ">= 16" @@ -2180,9 +2035,9 @@ } }, "node_modules/hono": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.0.tgz", - "integrity": "sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==", + "version": "4.12.18", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -2231,9 +2086,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" @@ -2516,9 +2371,9 @@ } }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -2533,9 +2388,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -2555,9 +2410,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { @@ -2680,9 +2535,9 @@ } }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", "dev": true, "license": "MIT", "dependencies": { @@ -2696,31 +2551,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" } }, @@ -2964,9 +2819,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -3076,9 +2931,9 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "dev": true, "license": "MIT", "dependencies": { @@ -3186,9 +3041,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { diff --git a/DataFactory.MCP.Core/Resources/McpApps/package.json b/DataFactory.MCP.Core/Resources/McpApps/package.json index 2fbce461..8288e87f 100644 --- a/DataFactory.MCP.Core/Resources/McpApps/package.json +++ b/DataFactory.MCP.Core/Resources/McpApps/package.json @@ -8,7 +8,7 @@ "dev": "vite build --watch" }, "dependencies": { - "@modelcontextprotocol/ext-apps": "^1.0.1", + "@modelcontextprotocol/ext-apps": "^1.7.1", "@modelcontextprotocol/sdk": "^1.26.0", "react": "^18.2.0", "react-dom": "^18.2.0" From 03b24cff85577be7018c0dccccdb56e9a7c39b9e Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 11:51:18 -0700 Subject: [PATCH 20/27] fix: pin vite to 7.3.2 for CI feed compatibility vite 7.3.3 is not yet cached in the DevOps Gateway-AdminPortal npm feed. Pin to 7.3.2 which fixes all security vulnerabilities and is available on the CI feed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- DataFactory.MCP.Core/Resources/McpApps/package-lock.json | 8 ++++---- DataFactory.MCP.Core/Resources/McpApps/package.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DataFactory.MCP.Core/Resources/McpApps/package-lock.json b/DataFactory.MCP.Core/Resources/McpApps/package-lock.json index a34e95e7..7fa9fa87 100644 --- a/DataFactory.MCP.Core/Resources/McpApps/package-lock.json +++ b/DataFactory.MCP.Core/Resources/McpApps/package-lock.json @@ -18,7 +18,7 @@ "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", "typescript": "^5.3.0", - "vite": "^7.3.1", + "vite": "^7.3.2", "vite-plugin-singlefile": "^2.0.0" } }, @@ -2931,9 +2931,9 @@ } }, "node_modules/vite": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", - "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/DataFactory.MCP.Core/Resources/McpApps/package.json b/DataFactory.MCP.Core/Resources/McpApps/package.json index 8288e87f..34c59f57 100644 --- a/DataFactory.MCP.Core/Resources/McpApps/package.json +++ b/DataFactory.MCP.Core/Resources/McpApps/package.json @@ -18,7 +18,7 @@ "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", "typescript": "^5.3.0", - "vite": "^7.3.1", + "vite": "^7.3.2", "vite-plugin-singlefile": "^2.0.0" } -} \ No newline at end of file +} From eb6b75ea415037669e68dee3bf5ba7074ee84c14 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 12:05:38 -0700 Subject: [PATCH 21/27] docs: add CI feed configuration skill for mcp integration Document DevOps NuGet feed caching behavior, how to trigger upstream cache after publishing, and npm feed troubleshooting for CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/builder.datafactory/SKILL.md | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/.github/skills/builder.datafactory/SKILL.md b/.github/skills/builder.datafactory/SKILL.md index 85426b70..c04671ba 100644 --- a/.github/skills/builder.datafactory/SKILL.md +++ b/.github/skills/builder.datafactory/SKILL.md @@ -45,6 +45,55 @@ To bump version, use `Update-ServerVersion.ps1` or edit `Directory.Build.props` - `Microsoft.DataFactory.MCP.Core` — Core library - `Microsoft.DataFactory.MCP.Http` — HTTP transport +## CI Feed Configuration (mcp repo integration) + +The `microsoft/mcp` repo uses a single DevOps NuGet feed with an upstream to nuget.org. +CI builds **only** pull from this feed — no direct nuget.org access. + +**Feed URL:** `https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json` +**Feed UI:** https://dev.azure.com/azure-sdk/public/_artifacts/feed/azure-sdk-for-net + +### How package caching works + +The DevOps feed caches packages **on-demand** from its nuget.org upstream: +1. A new version is published to nuget.org +2. It is **NOT** immediately available on the DevOps feed +3. A Collaborator must trigger a `dotnet restore` against the DevOps feed +4. The feed fetches and caches the package from nuget.org +5. Subsequent CI builds can then resolve it + +### After publishing a new version + +```bash +# 1. Verify the package exists on nuget.org +nuget list Microsoft.DataFactory.MCP.Core -Source "https://api.nuget.org/v3/index.json" -PreRelease -AllVersions + +# 2. Trigger the DevOps feed to cache it (run from mcp repo root) +dotnet restore tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj \ + --source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" + +# 3. Verify it's cached (search index may lag behind — restore success is the real proof) +nuget list Microsoft.DataFactory.MCP.Core -Source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" -PreRelease -AllVersions +``` + +> **Note:** The `nuget list` search index can lag behind actual availability. +> If `dotnet restore` succeeds, the package IS available for CI even if `nuget list` doesn't show it yet. + +### DevOps feed credential provider + +If you haven't authenticated to the DevOps feed locally, install the credential provider: +https://go.microsoft.com/fwlink/?linkid=2099625 + +Once installed, `dotnet restore` will trigger an auth challenge and let you query the feed as a Collaborator. + +### npm feed (McpApps UI) + +The `DataFactory.MCP.Core/Resources/McpApps/` project uses the `Gateway-AdminPortal` DevOps npm feed. +Same caching behavior applies — new npm versions may not be immediately available. + +If a package version isn't cached yet (404 on `npm ci`), pin to a version that exists on the feed +or wait for the upstream to sync. + ## Troubleshooting | Issue | Fix | @@ -53,3 +102,5 @@ To bump version, use `Update-ServerVersion.ps1` or edit `Directory.Build.props` | Framework not found | Install .NET 10 SDK | | Private feed auth | Check `nuget.private.config` credentials | | Build warnings | Nullable is enabled project-wide; fix nullable warnings | +| Package not on DevOps feed | Run `dotnet restore` locally to trigger upstream cache (see above) | +| npm 404 in CI | Pin to a version available on Gateway-AdminPortal feed | From 1f06d4a3c1174a6c3d44e0a4d3afd96f12712f3c Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 12:06:50 -0700 Subject: [PATCH 22/27] Revert "docs: add CI feed configuration skill for mcp integration" This reverts commit eb6b75ea415037669e68dee3bf5ba7074ee84c14. --- .github/skills/builder.datafactory/SKILL.md | 51 --------------------- 1 file changed, 51 deletions(-) diff --git a/.github/skills/builder.datafactory/SKILL.md b/.github/skills/builder.datafactory/SKILL.md index c04671ba..85426b70 100644 --- a/.github/skills/builder.datafactory/SKILL.md +++ b/.github/skills/builder.datafactory/SKILL.md @@ -45,55 +45,6 @@ To bump version, use `Update-ServerVersion.ps1` or edit `Directory.Build.props` - `Microsoft.DataFactory.MCP.Core` — Core library - `Microsoft.DataFactory.MCP.Http` — HTTP transport -## CI Feed Configuration (mcp repo integration) - -The `microsoft/mcp` repo uses a single DevOps NuGet feed with an upstream to nuget.org. -CI builds **only** pull from this feed — no direct nuget.org access. - -**Feed URL:** `https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json` -**Feed UI:** https://dev.azure.com/azure-sdk/public/_artifacts/feed/azure-sdk-for-net - -### How package caching works - -The DevOps feed caches packages **on-demand** from its nuget.org upstream: -1. A new version is published to nuget.org -2. It is **NOT** immediately available on the DevOps feed -3. A Collaborator must trigger a `dotnet restore` against the DevOps feed -4. The feed fetches and caches the package from nuget.org -5. Subsequent CI builds can then resolve it - -### After publishing a new version - -```bash -# 1. Verify the package exists on nuget.org -nuget list Microsoft.DataFactory.MCP.Core -Source "https://api.nuget.org/v3/index.json" -PreRelease -AllVersions - -# 2. Trigger the DevOps feed to cache it (run from mcp repo root) -dotnet restore tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj \ - --source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" - -# 3. Verify it's cached (search index may lag behind — restore success is the real proof) -nuget list Microsoft.DataFactory.MCP.Core -Source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" -PreRelease -AllVersions -``` - -> **Note:** The `nuget list` search index can lag behind actual availability. -> If `dotnet restore` succeeds, the package IS available for CI even if `nuget list` doesn't show it yet. - -### DevOps feed credential provider - -If you haven't authenticated to the DevOps feed locally, install the credential provider: -https://go.microsoft.com/fwlink/?linkid=2099625 - -Once installed, `dotnet restore` will trigger an auth challenge and let you query the feed as a Collaborator. - -### npm feed (McpApps UI) - -The `DataFactory.MCP.Core/Resources/McpApps/` project uses the `Gateway-AdminPortal` DevOps npm feed. -Same caching behavior applies — new npm versions may not be immediately available. - -If a package version isn't cached yet (404 on `npm ci`), pin to a version that exists on the feed -or wait for the upstream to sync. - ## Troubleshooting | Issue | Fix | @@ -102,5 +53,3 @@ or wait for the upstream to sync. | Framework not found | Install .NET 10 SDK | | Private feed auth | Check `nuget.private.config` credentials | | Build warnings | Nullable is enabled project-wide; fix nullable warnings | -| Package not on DevOps feed | Run `dotnet restore` locally to trigger upstream cache (see above) | -| npm 404 in CI | Pin to a version available on Gateway-AdminPortal feed | From b538ed12e9e617f2c1e809e853104a5547303337 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Thu, 7 May 2026 12:07:59 -0700 Subject: [PATCH 23/27] docs: add DevOps agent and Fabric.Mcp.Server integration skill Create devops.agent.md for CI/publishing workflows. Create devops.fabric-mcp-integration skill for DevOps feed caching, version management, and trim safety requirements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/agents/devops.agent.md | 14 ++ .github/copilot-instructions.md | 2 + .../devops.fabric-mcp-integration/SKILL.md | 127 ++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 .github/agents/devops.agent.md create mode 100644 .github/skills/devops.fabric-mcp-integration/SKILL.md diff --git a/.github/agents/devops.agent.md b/.github/agents/devops.agent.md new file mode 100644 index 00000000..b9840084 --- /dev/null +++ b/.github/agents/devops.agent.md @@ -0,0 +1,14 @@ +--- +description: "Use when: publishing packages, managing DevOps feed availability, troubleshooting CI failures, or working with Fabric.Mcp.Server integration." +name: "DevOps" +model: "Claude Opus 4.6 (copilot)" +tools: [execute, read, memory, todo] +user-invocable: true +argument-hint: "Describe the DevOps/CI task: publish package, check feed, fix CI failure" +--- + +You manage DevOps workflows, CI pipelines, and package publishing. You do NOT modify source code. + +Load the `devops.fabric-mcp-integration` skill for feed configuration, publishing workflow, and troubleshooting. + +Always run from the repo root. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4c944a17..8f95c8d5 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -30,3 +30,5 @@ When these topics come up, reference the corresponding file in `docs/datafactory | Fast Copy, Action.Sequence, Modern Evaluator | `advanced.md` | For connection management (creating/listing connections, supported types, UI form), reference `docs/connection-management.md`. + +For DevOps/CI integration with Fabric.Mcp.Server (feed caching, publishing, trim safety), reference the `devops.fabric-mcp-integration` skill. diff --git a/.github/skills/devops.fabric-mcp-integration/SKILL.md b/.github/skills/devops.fabric-mcp-integration/SKILL.md new file mode 100644 index 00000000..77bd1724 --- /dev/null +++ b/.github/skills/devops.fabric-mcp-integration/SKILL.md @@ -0,0 +1,127 @@ +--- +name: devops.fabric-mcp-integration +description: "DevOps and CI integration with microsoft/mcp (Fabric.Mcp.Server). Use when publishing packages, managing feed availability, or troubleshooting CI failures." +--- + +# DevOps Integration with Fabric.Mcp.Server + +## Overview + +The `microsoft/mcp` repo consumes `Microsoft.DataFactory.MCP.Core` as a NuGet package +in `Fabric.Mcp.Tools.DataFactory`. CI builds use a DevOps feed with upstream caching +from nuget.org — packages are **not** immediately available after publishing. + +## Feed Configuration + +The Fabric.Mcp.Server `NuGet.config` uses a single feed: + +```xml + + + + +``` + +- **Feed UI:** https://dev.azure.com/azure-sdk/public/_artifacts/feed/azure-sdk-for-net +- **No direct nuget.org access** — all packages must come through this feed +- The feed has an **upstream** to nuget.org that caches packages on-demand + +## How Upstream Caching Works + +1. A new version is published to **nuget.org** +2. It is **NOT** immediately available on the DevOps feed +3. A Collaborator runs `dotnet restore` against the DevOps feed +4. The feed fetches and caches the package from nuget.org +5. Subsequent Fabric.Mcp.Server CI builds can then resolve it + +> **Important:** The `nuget list` search index can lag behind actual availability. +> If `dotnet restore` succeeds, the package IS available for CI even if `nuget list` doesn't show it yet. + +## After Publishing a New Version + +### Step 1: Verify on nuget.org + +```bash +nuget list Microsoft.DataFactory.MCP.Core \ + -Source "https://api.nuget.org/v3/index.json" \ + -PreRelease -AllVersions +``` + +### Step 2: Trigger the DevOps feed to cache it + +Run from the `microsoft/mcp` repo root: + +```bash +dotnet restore tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj \ + --source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" +``` + +If restore succeeds, the package is cached and Fabric.Mcp.Server CI will find it. + +### Step 3: Verify (optional) + +```bash +nuget list Microsoft.DataFactory.MCP.Core \ + -Source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" \ + -PreRelease -AllVersions +``` + +> The search index may lag — restore success is the definitive proof. + +## Version Management + +### DataFactory.MCP side (this repo) + +Version is in `Directory.Build.props`: + +```xml +0 +20 +0 +beta +``` + +### Fabric.Mcp.Server side (consumer repo) + +Version is in `Directory.Packages.props` (Central Package Management): + +```xml + +``` + +The project reference is in: +`tools/Fabric.Mcp.Tools.DataFactory/src/Fabric.Mcp.Tools.DataFactory.csproj` + +## Credential Provider Setup + +To authenticate to the DevOps feed locally, install the credential provider: +https://go.microsoft.com/fwlink/?linkid=2099625 + +Once installed, `dotnet restore` triggers an auth challenge and grants Collaborator access. +The feed is public — anyone can pull packages that are already cached. +Collaborators can also trigger the feed to ingest new packages from nuget.org. + +## Trim Safety Requirements + +Fabric.Mcp.Server publishes with IL trimming enabled: + +```bash +dotnet publish servers/Fabric.Mcp.Server/src/Fabric.Mcp.Server.csproj \ + --runtime linux-x64 --self-contained \ + /p:PublishTrimmed=true /p:PublishSingleFile=true /p:TreatWarningsAsErrors=true +``` + +All code consumed from `Microsoft.DataFactory.MCP.Core` must be trim-safe: +- Use source-generated JSON (`DataFactoryJsonContext`) instead of reflection-based serialization +- Mark reflection-dependent APIs with `[RequiresUnreferencedCode]` +- Test with the publish command above before submitting version bumps + +## Troubleshooting + +| Issue | Cause | Fix | +|-------|-------|-----| +| `NU1102: Unable to find package` | Version not cached on DevOps feed | Run `dotnet restore` locally to trigger upstream cache | +| `nuget list` doesn't show new version | Search index lag | Check with `dotnet restore` instead — success = available | +| 401 Unauthorized on restore | Missing credential provider | Install cred provider (link above) | +| IL2026 trim warnings on publish | Reflection-based JSON in consumed code | Use `DataFactoryJsonContext` source-gen JSON | From c2ab71565017ac1d60bed782281d8c120e4c8051 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Tue, 12 May 2026 11:06:16 -0700 Subject: [PATCH 24/27] docs: add Handlers layer to architecture documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Handlers Layer to TOC, ASCII diagram, and component details - Explain why handlers exist (dual-framework: SDK tools + Fabric commands) - Document ToolResult, handler table, before/after examples - Add note to Data Flow section about Tool → Handler → Service path - Add handler-based tool pattern to Extension Points Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/ARCHITECTURE.md | 129 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 67690f31..4d7bffbb 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -10,6 +10,7 @@ This document provides a comprehensive overview of the Microsoft Data Factory MC - [Application Entry Point](#1-application-entry-point) - [MCP Tools Layer](#2-mcp-tools-layer) - [MCP App Resources Layer](#2a-mcp-app-resources-layer) + - [Handlers Layer](#2b-handlers-layer) - [Core Services Layer](#3-core-services-layer) - [Abstractions Layer](#4-abstractions-layer) - [Models Layer](#5-models-layer) @@ -65,6 +66,14 @@ The Microsoft Data Factory MCP Server is a .NET-based application that implement │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Handlers Layer (new) │ │ +│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ │ Pipeline │ │ Dataflow │ │DataflowQuery │ │ │ +│ │ │ Handler │ │ Handler │ │ Handler │ │ │ +│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Core Services Layer │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ │ │ │ │Authentication│ │FabricGateway │ │FabricConnect │ │FabricWork- │ │ │ @@ -323,6 +332,88 @@ public class CreateConnectionResourceHandler } ``` +### 2b. Handlers Layer + +The Handlers layer sits between Tools and Services, owning all business logic for domains that need to be shared across multiple MCP framework implementations. + +#### Why Handlers Exist + +The DataFactory.MCP.Core NuGet package is consumed by two different MCP server implementations: + +1. **Standalone DataFactory MCP Server** — uses `[McpServerTool]` SDK attributes +2. **Microsoft Fabric MCP Server** ([microsoft/mcp](https://github.com/microsoft/mcp)) — uses a custom `GlobalCommand` dispatch framework + +Without handlers, every tool's business logic (validation, service calls, error handling, result formatting) would need to be duplicated in both wrappers. Handlers extract this shared logic into a single, testable location. + +#### Architecture Pattern + +``` +Tool (SDK or Fabric Command) + └── Handler (business logic) + └── Service (HTTP API calls) + └── Model (DTOs) +``` + +- **Tools** become thin delegators (~3-8 lines per method) +- **Handlers** own validation, service orchestration, result shaping, and error handling +- **Services** remain unchanged (HTTP calls, auth, response parsing) + +#### ToolResult\ + +Handlers return `ToolResult`, a framework-agnostic result type: + +```csharp +public class ToolResult +{ + public bool IsSuccess { get; } + public T? Data { get; } + public string? Error { get; } + public string? ErrorMessage { get; } +} +``` + +Each framework wrapper maps `ToolResult` to its own error format: +- SDK tools use `ToolResultExtensions` to convert errors to MCP JSON responses +- Fabric commands map `IsSuccess`/`Error` to their `CommandResult` type + +#### Current Handlers + +| Handler | Service Dependency | Operations | +|---------|-------------------|------------| +| `PipelineHandler` | `IFabricPipelineService` | List, Create, Get, Run | +| `DataflowHandler` | `IFabricDataflowService` | List, Create | +| `DataflowQueryHandler` | `IFabricDataflowService`, `IArrowDataReaderService` | Execute M Query | + +#### Example: Before and After + +**Before (tool owns business logic):** +```csharp +[McpServerTool, Description("List all pipelines")] +public async Task ListPipelinesAsync(string workspaceId) +{ + try + { + _validationService.ValidateRequiredString(workspaceId, nameof(workspaceId)); + var pipelines = await _pipelineService.ListPipelinesAsync(workspaceId); + return new { pipelines, count = pipelines.Count }.ToMcpJson(); + } + catch (ArgumentException ex) { return ex.ToValidationError().ToMcpJson(); } + catch (UnauthorizedAccessException ex) { return ex.ToAuthenticationError().ToMcpJson(); } + catch (HttpRequestException ex) { return ex.ToHttpError().ToMcpJson(); } + catch (Exception ex) { return ex.ToOperationError("list pipelines").ToMcpJson(); } +} +``` + +**After (tool delegates to handler):** +```csharp +[McpServerTool, Description("List all pipelines")] +public async Task ListPipelinesAsync(string workspaceId) +{ + var result = await _handler.ListPipelinesAsync(workspaceId); + return result.ToMcpToolResponse(); +} +``` + ### 3. Core Services Layer **Location**: `Services/` @@ -755,6 +846,10 @@ Centralized JSON serialization options: 10. Tool → AI Assistant (JSON via ToMcpJson()) ``` +> **Note:** For Pipeline and Dataflow tools, the flow includes a Handler layer: +> `Tool → Handler → Service → HttpClient → API`. The handler owns all business +> logic and returns `ToolResult`, which the tool maps to an MCP response. + ### Dataflow Query Execution Flow ``` @@ -936,6 +1031,40 @@ mcpBuilder.RegisterToolWithFeatureFlag( configuration, args, "new-feature-flag", nameof(NewTool), logger); ``` +#### Alternative: Handler-Based Tools (Recommended for shared logic) + +For tools whose logic needs to be shared across multiple MCP frameworks, use the handler pattern: + +1. Create a handler in `Core/Handlers/`: +```csharp +public class NewHandler(INewService service) +{ + public async Task> DoOperationAsync(string parameter) + { + try + { + ArgumentException.ThrowIfNullOrWhiteSpace(parameter); + var result = await service.DoOperationAsync(parameter); + return ToolResult.Success(new NewResult(result)); + } + catch (Exception ex) + { + return ToolResult.Failure(ex.Message); + } + } +} +``` + +2. Create a thin tool wrapper: +```csharp +[McpServerTool, Description("Description of the tool")] +public async Task NewOperationAsync(string parameter) +{ + var result = await _handler.DoOperationAsync(parameter); + return result.ToMcpToolResponse(); +} +``` + ### Adding New Services 1. Define interface in `Abstractions/Interfaces/`: From 9363d66a71a76a7d178d9dc833c9241dc8ec893e Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Tue, 12 May 2026 11:20:34 -0700 Subject: [PATCH 25/27] fix: source-gen JSON fallback for anonymous types and JsonElement SerializeRequest() tries DataFactoryJsonContext first, falls back to reflection-based JsonOptions for types not registered in the source-gen context (anonymous types in RunCopyJobAsync, JsonElement in CreateScheduleRequest.Configuration). Fixes CI test failures in CopyJobToolIntegrationTests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Abstractions/FabricServiceBase.cs | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs index 45dd93a9..07af8321 100644 --- a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs +++ b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs @@ -20,6 +20,27 @@ public abstract class FabricServiceBase protected readonly IValidationService ValidationService; protected static JsonSerializerOptions JsonOptions => JsonSerializerOptionsProvider.FabricApi; + /// + /// Serializes a request object using source-generated context when available, + /// falling back to reflection-based serialization for types not registered + /// in DataFactoryJsonContext (e.g., anonymous types, JsonElement properties). + /// + private static string SerializeRequest(object request) + { + try + { + return JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); + } + catch (InvalidOperationException) + { + return JsonSerializer.Serialize(request, JsonOptions); + } + catch (NotSupportedException) + { + return JsonSerializer.Serialize(request, JsonOptions); + } + } + protected FabricServiceBase( IHttpClientFactory httpClientFactory, ILogger logger, @@ -61,7 +82,7 @@ protected void ValidateGuids(params (string value, string name)[] guids) .Build(); Logger.LogInformation("Posting to: {Url}", url); - var jsonContent = JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); + var jsonContent = SerializeRequest(request); Logger.LogDebug("Request body: {Body}", jsonContent); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); @@ -79,7 +100,7 @@ protected void ValidateGuids(params (string value, string name)[] guids) .Build(); Logger.LogInformation("Patching: {Url}", url); - var jsonContent = JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); + var jsonContent = SerializeRequest(request); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var httpRequest = new HttpRequestMessage(HttpMethod.Patch, url) { Content = content }; @@ -97,7 +118,7 @@ protected async Task PostAsBytesAsync(string endpoint, object request) .Build(); Logger.LogInformation("Posting to: {Url}", url); - var jsonContent = JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); + var jsonContent = SerializeRequest(request); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var response = await HttpClient.PostAsync(url, content); @@ -115,7 +136,7 @@ protected async Task PostAsBytesAsync(string endpoint, object request) .Build(); Logger.LogInformation("Posting to: {Url}", url); - var jsonContent = request != null ? JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default) : null; + var jsonContent = request != null ? SerializeRequest(request) : null; var content = jsonContent != null ? new StringContent(jsonContent, Encoding.UTF8, "application/json") : null; var response = await HttpClient.PostAsync(url, content); @@ -135,7 +156,7 @@ protected async Task PostNoContentAsync(string endpoint, object request) .Build(); Logger.LogInformation("Posting to: {Url}", url); - var jsonContent = JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); + var jsonContent = SerializeRequest(request); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var response = await HttpClient.PostAsync(url, content); From 05924fec3ece0148bb091fb9dc5391a939d37768 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Tue, 12 May 2026 11:33:39 -0700 Subject: [PATCH 26/27] fix: make source-gen JSON fully AOT-safe (no reflection fallback) Replace anonymous types with concrete DTOs (RunOnDemandRequest, EmptyRequest) and change object parameters to JsonElement for AOT source-gen compatibility. Register all types in DataFactoryJsonContext. Remove reflection-based SerializeRequest fallback from FabricServiceBase. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Abstractions/FabricServiceBase.cs | 18 +++------------- .../Interfaces/IFabricCopyJobService.cs | 3 ++- .../Interfaces/IFabricPipelineService.cs | 3 ++- .../Configuration/DataFactoryJsonContext.cs | 6 ++++++ .../Handlers/Pipeline/PipelineHandler.cs | 3 ++- .../Models/Common/EmptyRequest.cs | 10 +++++++++ .../Models/Common/RunOnDemandRequest.cs | 21 +++++++++++++++++++ .../Schedule/CreateScheduleRequest.cs | 3 ++- .../Services/FabricCopyJobService.cs | 10 ++++++--- .../Services/FabricDataflowService.cs | 3 ++- .../Services/FabricPipelineService.cs | 10 ++++++--- .../Tools/CopyJob/CopyJobTool.cs | 9 ++++---- .../Tools/Pipeline/PipelineTool.cs | 9 ++++---- 13 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 DataFactory.MCP.Core/Models/Common/EmptyRequest.cs create mode 100644 DataFactory.MCP.Core/Models/Common/RunOnDemandRequest.cs diff --git a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs index 07af8321..941fbd98 100644 --- a/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs +++ b/DataFactory.MCP.Core/Abstractions/FabricServiceBase.cs @@ -21,24 +21,12 @@ public abstract class FabricServiceBase protected static JsonSerializerOptions JsonOptions => JsonSerializerOptionsProvider.FabricApi; /// - /// Serializes a request object using source-generated context when available, - /// falling back to reflection-based serialization for types not registered - /// in DataFactoryJsonContext (e.g., anonymous types, JsonElement properties). + /// Serializes a request object using source-generated JSON context for AOT compatibility. + /// All request types must be registered in DataFactoryJsonContext. /// private static string SerializeRequest(object request) { - try - { - return JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); - } - catch (InvalidOperationException) - { - return JsonSerializer.Serialize(request, JsonOptions); - } - catch (NotSupportedException) - { - return JsonSerializer.Serialize(request, JsonOptions); - } + return JsonSerializer.Serialize(request, request.GetType(), DataFactoryJsonContext.Default); } protected FabricServiceBase( diff --git a/DataFactory.MCP.Core/Abstractions/Interfaces/IFabricCopyJobService.cs b/DataFactory.MCP.Core/Abstractions/Interfaces/IFabricCopyJobService.cs index fb1e8710..23d15002 100644 --- a/DataFactory.MCP.Core/Abstractions/Interfaces/IFabricCopyJobService.cs +++ b/DataFactory.MCP.Core/Abstractions/Interfaces/IFabricCopyJobService.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using DataFactory.MCP.Models.CopyJob; using DataFactory.MCP.Models.CopyJob.Definition; using DataFactory.MCP.Models.Pipeline; @@ -60,7 +61,7 @@ Task UpdateCopyJobDefinitionAsync( Task RunCopyJobAsync( string workspaceId, string copyJobId, - object? executionData = null); + JsonElement? executionData = null); /// /// Gets the status of a copy job instance diff --git a/DataFactory.MCP.Core/Abstractions/Interfaces/IFabricPipelineService.cs b/DataFactory.MCP.Core/Abstractions/Interfaces/IFabricPipelineService.cs index e27811c7..be388035 100644 --- a/DataFactory.MCP.Core/Abstractions/Interfaces/IFabricPipelineService.cs +++ b/DataFactory.MCP.Core/Abstractions/Interfaces/IFabricPipelineService.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using DataFactory.MCP.Models.Pipeline; using DataFactory.MCP.Models.Pipeline.Definition; using DataFactory.MCP.Models.Pipeline.Schedule; @@ -59,7 +60,7 @@ Task UpdatePipelineDefinitionAsync( Task RunPipelineAsync( string workspaceId, string pipelineId, - object? executionData = null); + JsonElement? executionData = null); /// /// Gets the status of a pipeline job instance diff --git a/DataFactory.MCP.Core/Configuration/DataFactoryJsonContext.cs b/DataFactory.MCP.Core/Configuration/DataFactoryJsonContext.cs index 59523f9a..b6492477 100644 --- a/DataFactory.MCP.Core/Configuration/DataFactoryJsonContext.cs +++ b/DataFactory.MCP.Core/Configuration/DataFactoryJsonContext.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Text.Json; using System.Text.Json.Serialization; using DataFactory.MCP.Models.Capacity; +using DataFactory.MCP.Models.Common; using DataFactory.MCP.Models.Connection; using DataFactory.MCP.Models.CopyJob; using DataFactory.MCP.Models.CopyJob.Definition; @@ -91,6 +93,10 @@ namespace DataFactory.MCP.Configuration; // Gateway Cluster Datasource types (DMTSv2) [JsonSerializable(typeof(GatewayClusterDatasourceService.GatewayClusterDatasourcesResponse))] [JsonSerializable(typeof(GatewayClusterDatasourceService.CloudDatasourceInfo))] +// Common types +[JsonSerializable(typeof(JsonElement))] +[JsonSerializable(typeof(EmptyRequest))] +[JsonSerializable(typeof(RunOnDemandRequest))] internal sealed partial class DataFactoryJsonContext : JsonSerializerContext { } diff --git a/DataFactory.MCP.Core/Handlers/Pipeline/PipelineHandler.cs b/DataFactory.MCP.Core/Handlers/Pipeline/PipelineHandler.cs index e8b1be8a..a2c5d09f 100644 --- a/DataFactory.MCP.Core/Handlers/Pipeline/PipelineHandler.cs +++ b/DataFactory.MCP.Core/Handlers/Pipeline/PipelineHandler.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Models; using DataFactory.MCP.Models.Pipeline; @@ -121,7 +122,7 @@ public async Task> GetAsync(string workspaceId, st } } - public async Task> RunAsync(string workspaceId, string pipelineId, object? executionData = null) + public async Task> RunAsync(string workspaceId, string pipelineId, JsonElement? executionData = null) { if (string.IsNullOrWhiteSpace(workspaceId)) return ToolResult.Failure(Messages.InvalidParameterEmpty("workspaceId"), "validation"); diff --git a/DataFactory.MCP.Core/Models/Common/EmptyRequest.cs b/DataFactory.MCP.Core/Models/Common/EmptyRequest.cs new file mode 100644 index 00000000..e33cb94a --- /dev/null +++ b/DataFactory.MCP.Core/Models/Common/EmptyRequest.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DataFactory.MCP.Models.Common; + +/// +/// Empty request body for API calls that require a POST with empty JSON payload. +/// Used instead of anonymous types to support source-gen JSON serialization. +/// +public class EmptyRequest { } diff --git a/DataFactory.MCP.Core/Models/Common/RunOnDemandRequest.cs b/DataFactory.MCP.Core/Models/Common/RunOnDemandRequest.cs new file mode 100644 index 00000000..43fa6c2d --- /dev/null +++ b/DataFactory.MCP.Core/Models/Common/RunOnDemandRequest.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DataFactory.MCP.Models.Common; + +/// +/// Request payload for running an item job on demand with optional execution data. +/// Used by both Pipeline and CopyJob run operations. +/// +public class RunOnDemandRequest +{ + /// + /// Optional execution data for the job run + /// + [JsonPropertyName("executionData")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public JsonElement? ExecutionData { get; set; } +} diff --git a/DataFactory.MCP.Core/Models/Pipeline/Schedule/CreateScheduleRequest.cs b/DataFactory.MCP.Core/Models/Pipeline/Schedule/CreateScheduleRequest.cs index 3c81a182..5d8bba3e 100644 --- a/DataFactory.MCP.Core/Models/Pipeline/Schedule/CreateScheduleRequest.cs +++ b/DataFactory.MCP.Core/Models/Pipeline/Schedule/CreateScheduleRequest.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using System.Text.Json.Serialization; namespace DataFactory.MCP.Models.Pipeline.Schedule; @@ -17,5 +18,5 @@ public class CreateScheduleRequest /// The schedule configuration (Cron, Daily, Weekly, or Monthly) /// [JsonPropertyName("configuration")] - public object Configuration { get; set; } = null!; + public JsonElement Configuration { get; set; } } diff --git a/DataFactory.MCP.Core/Services/FabricCopyJobService.cs b/DataFactory.MCP.Core/Services/FabricCopyJobService.cs index 26885281..f4861aa7 100644 --- a/DataFactory.MCP.Core/Services/FabricCopyJobService.cs +++ b/DataFactory.MCP.Core/Services/FabricCopyJobService.cs @@ -1,6 +1,8 @@ +using System.Text.Json; using DataFactory.MCP.Abstractions; using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Infrastructure.Http; +using DataFactory.MCP.Models.Common; using DataFactory.MCP.Models.CopyJob; using DataFactory.MCP.Models.CopyJob.Definition; using DataFactory.MCP.Models.Pipeline; @@ -123,7 +125,7 @@ public async Task GetCopyJobDefinitionAsync( Logger.LogInformation("Getting definition for copy job {CopyJobId} in workspace {WorkspaceId}", copyJobId, workspaceId); - var emptyRequest = new { }; + var emptyRequest = new EmptyRequest(); var response = await PostAsync(endpoint, emptyRequest) ?? throw new InvalidOperationException("Failed to get copy job definition response"); @@ -199,7 +201,7 @@ public async Task UpdateCopyJobDefinitionAsync( public async Task RunCopyJobAsync( string workspaceId, string copyJobId, - object? executionData = null) + JsonElement? executionData = null) { try { @@ -213,7 +215,9 @@ public async Task UpdateCopyJobDefinitionAsync( Logger.LogInformation("Running copy job {CopyJobId} on demand in workspace {WorkspaceId}", copyJobId, workspaceId); - var request = executionData != null ? new { executionData } : null; + var request = executionData != null + ? new RunOnDemandRequest { ExecutionData = executionData } + : null; var location = await PostAndGetLocationAsync(endpoint, request); Logger.LogInformation("Copy job {CopyJobId} run triggered successfully. Location: {Location}", diff --git a/DataFactory.MCP.Core/Services/FabricDataflowService.cs b/DataFactory.MCP.Core/Services/FabricDataflowService.cs index d29febd4..2479dbf9 100644 --- a/DataFactory.MCP.Core/Services/FabricDataflowService.cs +++ b/DataFactory.MCP.Core/Services/FabricDataflowService.cs @@ -2,6 +2,7 @@ using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Abstractions.Interfaces.DMTSv2; using DataFactory.MCP.Infrastructure.Http; +using DataFactory.MCP.Models.Common; using DataFactory.MCP.Models.Dataflow; using DataFactory.MCP.Models.Dataflow.Definition; using DataFactory.MCP.Models.Dataflow.Query; @@ -194,7 +195,7 @@ private async Task GetDataflowDefinitionRespo dataflowId, workspaceId); // Use empty object as required by API - var emptyRequest = new { }; + var emptyRequest = new EmptyRequest(); return await PostAsync(endpoint, emptyRequest) ?? throw new InvalidOperationException("Failed to get dataflow definition response"); } diff --git a/DataFactory.MCP.Core/Services/FabricPipelineService.cs b/DataFactory.MCP.Core/Services/FabricPipelineService.cs index 0993f7bc..8732de66 100644 --- a/DataFactory.MCP.Core/Services/FabricPipelineService.cs +++ b/DataFactory.MCP.Core/Services/FabricPipelineService.cs @@ -1,6 +1,8 @@ +using System.Text.Json; using DataFactory.MCP.Abstractions; using DataFactory.MCP.Abstractions.Interfaces; using DataFactory.MCP.Infrastructure.Http; +using DataFactory.MCP.Models.Common; using DataFactory.MCP.Models.Pipeline; using DataFactory.MCP.Models.Pipeline.Definition; using DataFactory.MCP.Models.Pipeline.Schedule; @@ -122,7 +124,7 @@ public async Task GetPipelineDefinitionAsync( Logger.LogInformation("Getting definition for pipeline {PipelineId} in workspace {WorkspaceId}", pipelineId, workspaceId); - var emptyRequest = new { }; + var emptyRequest = new EmptyRequest(); var response = await PostAsync(endpoint, emptyRequest) ?? throw new InvalidOperationException("Failed to get pipeline definition response"); @@ -198,7 +200,7 @@ public async Task UpdatePipelineDefinitionAsync( public async Task RunPipelineAsync( string workspaceId, string pipelineId, - object? executionData = null) + JsonElement? executionData = null) { try { @@ -212,7 +214,9 @@ public async Task UpdatePipelineDefinitionAsync( Logger.LogInformation("Running pipeline {PipelineId} on demand in workspace {WorkspaceId}", pipelineId, workspaceId); - var request = executionData != null ? new { executionData } : null; + var request = executionData != null + ? new RunOnDemandRequest { ExecutionData = executionData } + : null; var location = await PostAndGetLocationAsync(endpoint, request); Logger.LogInformation("Pipeline {PipelineId} run triggered successfully. Location: {Location}", diff --git a/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs b/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs index 80d5db64..e8c94e4f 100644 --- a/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs +++ b/DataFactory.MCP.Core/Tools/CopyJob/CopyJobTool.cs @@ -334,12 +334,12 @@ public async Task RunCopyJobAsync( _validationService.ValidateRequiredString(workspaceId, nameof(workspaceId)); _validationService.ValidateRequiredString(copyJobId, nameof(copyJobId)); - object? executionData = null; + JsonElement? executionData = null; if (!string.IsNullOrEmpty(executionDataJson)) { try { - executionData = JsonSerializer.Deserialize(executionDataJson); + executionData = JsonSerializer.Deserialize(executionDataJson); } catch (JsonException ex) { @@ -453,11 +453,10 @@ public async Task CreateCopyJobScheduleAsync( _validationService.ValidateRequiredString(copyJobId, nameof(copyJobId)); _validationService.ValidateRequiredString(configurationJson, nameof(configurationJson)); - object configuration; + JsonElement configuration; try { - configuration = JsonSerializer.Deserialize(configurationJson) - ?? throw new ArgumentException("Configuration JSON cannot be null"); + configuration = JsonSerializer.Deserialize(configurationJson); } catch (JsonException ex) { diff --git a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs index da470b40..dca5bc72 100644 --- a/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs +++ b/DataFactory.MCP.Core/Tools/Pipeline/PipelineTool.cs @@ -267,12 +267,12 @@ public async Task RunPipelineAsync( [Description("The pipeline ID to run (required)")] string pipelineId, [Description("Optional execution data as JSON string for parameterized pipeline runs (optional)")] string? executionDataJson = null) { - object? executionData = null; + JsonElement? executionData = null; if (!string.IsNullOrEmpty(executionDataJson)) { try { - executionData = JsonSerializer.Deserialize(executionDataJson); + executionData = JsonSerializer.Deserialize(executionDataJson); } catch (JsonException ex) { @@ -364,11 +364,10 @@ public async Task CreatePipelineScheduleAsync( _validationService.ValidateRequiredString(pipelineId, nameof(pipelineId)); _validationService.ValidateRequiredString(configurationJson, nameof(configurationJson)); - object configuration; + JsonElement configuration; try { - configuration = JsonSerializer.Deserialize(configurationJson) - ?? throw new ArgumentException("Configuration JSON cannot be null"); + configuration = JsonSerializer.Deserialize(configurationJson); } catch (JsonException ex) { From 8abe9834a1d2309e16cd888376ec97b9fb4b5038 Mon Sep 17 00:00:00 2001 From: Ebram Tawfik Date: Tue, 12 May 2026 13:04:34 -0700 Subject: [PATCH 27/27] feat: add TokenCredential auth bridge for host integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When DataFactory.MCP.Core is hosted inside a system that already provides authentication (e.g., Fabric MCP Server with DefaultAzureCredential), the new TokenCredentialAuthenticationService automatically delegates token acquisition to the host's TokenCredential. - Add TokenCredentialAuthenticationService implementing IAuthenticationService - Use TryAddSingleton with conditional: if TokenCredential is in DI, use bridge; otherwise fall back to existing standalone auth (device code/interactive/SP) - Add Azure.Core package reference - Bump version to 0.21.0-preview No breaking changes — standalone mode works exactly as before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DataFactory.MCP.Core.csproj | 1 + .../Extensions/ServiceCollectionExtensions.cs | 32 ++++++--- .../TokenCredentialAuthenticationService.cs | 71 +++++++++++++++++++ Directory.Build.props | 2 +- Directory.Packages.props | 1 + 5 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 DataFactory.MCP.Core/Services/Authentication/TokenCredentialAuthenticationService.cs diff --git a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj index 4c457777..dcec22d7 100644 --- a/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj +++ b/DataFactory.MCP.Core/DataFactory.MCP.Core.csproj @@ -31,6 +31,7 @@ + diff --git a/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs b/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs index c415de73..9745f4c7 100644 --- a/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs +++ b/DataFactory.MCP.Core/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ +using Azure.Core; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using DataFactory.MCP.Abstractions.Interfaces; @@ -49,15 +51,29 @@ public static IServiceCollection AddDataFactoryMcpServices(this IServiceCollecti }).AddHttpMessageHandler(); // Register core services + services.AddSingleton(); + + // Authentication system with providers (needed for standalone mode) + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + // If host already provides TokenCredential (e.g., Fabric MCP Server), + // use TokenCredentialAuthenticationService; otherwise use standalone AuthenticationService + services.TryAddSingleton(sp => + { + var credential = sp.GetService(); + if (credential != null) + { + var logger = sp.GetRequiredService>(); + return new TokenCredentialAuthenticationService(credential, logger); + } + return ActivatorUtilities.CreateInstance(sp); + }); + + // Other services services - .AddSingleton() - // Authentication system with providers - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - // Other services .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/DataFactory.MCP.Core/Services/Authentication/TokenCredentialAuthenticationService.cs b/DataFactory.MCP.Core/Services/Authentication/TokenCredentialAuthenticationService.cs new file mode 100644 index 00000000..5a43d7fa --- /dev/null +++ b/DataFactory.MCP.Core/Services/Authentication/TokenCredentialAuthenticationService.cs @@ -0,0 +1,71 @@ +using Azure.Core; +using DataFactory.MCP.Abstractions.Interfaces; +using DataFactory.MCP.Models; +using Microsoft.Extensions.Logging; + +namespace DataFactory.MCP.Services.Authentication; + +/// +/// An IAuthenticationService implementation that delegates token acquisition to an Azure.Core TokenCredential. +/// Used when DataFactory.MCP.Core is hosted inside a system that already provides authentication +/// (e.g., Fabric MCP Server with DefaultAzureCredential). +/// +public class TokenCredentialAuthenticationService : IAuthenticationService +{ + private readonly TokenCredential _credential; + private readonly ILogger _logger; + + public TokenCredentialAuthenticationService( + TokenCredential credential, + ILogger logger) + { + _credential = credential; + _logger = logger; + } + + public Task GetAccessTokenAsync() + { + return GetAccessTokenAsync(AzureAdConfiguration.PowerBIScopes); + } + + public async Task GetAccessTokenAsync(string[] scopes) + { + var context = new TokenRequestContext(scopes); + var token = await _credential.GetTokenAsync(context, CancellationToken.None).ConfigureAwait(false); + return token.Token; + } + + public string GetAuthenticationStatus() + { + return "Authenticated via host-provided credential (TokenCredential)"; + } + + public Task AuthenticateInteractiveAsync() + { + _logger.LogInformation("Interactive authentication is not required — using host-provided credential"); + return Task.FromResult("Already authenticated via host-provided credential. No login required."); + } + + public Task StartDeviceCodeAuthAsync() + { + _logger.LogInformation("Device code authentication is not required — using host-provided credential"); + return Task.FromResult("Already authenticated via host-provided credential. No device code required."); + } + + public Task CheckDeviceAuthStatusAsync() + { + return Task.FromResult("Already authenticated via host-provided credential."); + } + + public Task AuthenticateServicePrincipalAsync(string applicationId, string clientSecret, string? tenantId = null) + { + _logger.LogInformation("Service principal authentication is not required — using host-provided credential"); + return Task.FromResult("Already authenticated via host-provided credential. No service principal login required."); + } + + public Task SignOutAsync() + { + _logger.LogInformation("Sign out is not applicable — authentication is managed by the host"); + return Task.FromResult("Authentication is managed by the host. Sign out is not applicable."); + } +} diff --git a/Directory.Build.props b/Directory.Build.props index 7ae54283..fbccbd5d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ 0 - 20 + 21 0 beta diff --git a/Directory.Packages.props b/Directory.Packages.props index 0478783b..447bfd5d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,6 +3,7 @@ true +