diff --git a/.mcp/server.json b/.mcp/server.json
index b3f444c..b13bdf0 100644
--- a/.mcp/server.json
+++ b/.mcp/server.json
@@ -6,7 +6,7 @@
{
"registry_name": "nuget",
"name": "Microsoft.DataFactory.MCP",
- "version": "0.2.0-beta",
+ "version": "#{VERSION}#",
"package_arguments": [],
"environment_variables": []
}
@@ -16,6 +16,6 @@
"source": "github"
},
"version_detail": {
- "version": "0.2.0-beta"
+ "version": "#{VERSION}#"
}
}
\ No newline at end of file
diff --git a/DataFactory.MCP.Tests/DataFactory.MCP.Tests.csproj b/DataFactory.MCP.Tests/DataFactory.MCP.Tests.csproj
index 422df9f..f9aecef 100644
--- a/DataFactory.MCP.Tests/DataFactory.MCP.Tests.csproj
+++ b/DataFactory.MCP.Tests/DataFactory.MCP.Tests.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/DataFactory.MCP.Tests/Infrastructure/McpTestFixture.cs b/DataFactory.MCP.Tests/Infrastructure/McpTestFixture.cs
index ed43643..57136e4 100644
--- a/DataFactory.MCP.Tests/Infrastructure/McpTestFixture.cs
+++ b/DataFactory.MCP.Tests/Infrastructure/McpTestFixture.cs
@@ -51,11 +51,13 @@ public McpTestFixture()
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
// Register tools
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
})
.Build();
diff --git a/DataFactory.MCP.Tests/Integration/WorkspacesToolIntegrationTests.cs b/DataFactory.MCP.Tests/Integration/WorkspacesToolIntegrationTests.cs
new file mode 100644
index 0000000..1a2f4ab
--- /dev/null
+++ b/DataFactory.MCP.Tests/Integration/WorkspacesToolIntegrationTests.cs
@@ -0,0 +1,201 @@
+using Xunit;
+using DataFactory.MCP.Tools;
+using DataFactory.MCP.Tests.Infrastructure;
+using DataFactory.MCP.Models;
+using System.Text.Json;
+
+namespace DataFactory.MCP.Tests.Integration;
+
+///
+/// Integration tests for WorkspacesTool that call the actual MCP tool methods
+/// without mocking to verify real behavior
+///
+public class WorkspacesToolIntegrationTests : FabricToolIntegrationTestBase
+{
+ private readonly WorkspacesTool _workspacesTool;
+
+ public WorkspacesToolIntegrationTests(McpTestFixture fixture) : base(fixture)
+ {
+ _workspacesTool = Fixture.GetService();
+ }
+
+ [Fact]
+ public async Task ListWorkspacesAsync_WithoutAuthentication_ShouldReturnAuthenticationError()
+ {
+ // Act
+ var result = await _workspacesTool.ListWorkspacesAsync();
+
+ // Assert
+ AssertAuthenticationError(result);
+ }
+
+ [Fact]
+ public async Task ListWorkspacesAsync_WithRoles_WithoutAuthentication_ShouldReturnAuthenticationError()
+ {
+ // Arrange
+ var testRoles = "Admin,Member";
+
+ // Act
+ var result = await _workspacesTool.ListWorkspacesAsync(testRoles);
+
+ // Assert
+ AssertAuthenticationError(result);
+ }
+
+ [Fact]
+ public async Task ListWorkspacesAsync_WithContinuationToken_WithoutAuthentication_ShouldReturnAuthenticationError()
+ {
+ // Arrange
+ var testToken = "test-continuation-token";
+
+ // Act
+ var result = await _workspacesTool.ListWorkspacesAsync(continuationToken: testToken);
+
+ // Assert
+ AssertAuthenticationError(result);
+ }
+
+ [Fact]
+ public async Task ListWorkspacesAsync_WithPreferWorkspaceSpecificEndpoints_WithoutAuthentication_ShouldReturnAuthenticationError()
+ {
+ // Act
+ var result = await _workspacesTool.ListWorkspacesAsync(preferWorkspaceSpecificEndpoints: true);
+
+ // Assert
+ AssertAuthenticationError(result);
+ }
+
+ [Fact]
+ public void WorkspacesTool_ShouldBeRegisteredInDI()
+ {
+ // Assert
+ Assert.NotNull(_workspacesTool);
+ Assert.IsType(_workspacesTool);
+ }
+
+ #region Authenticated Scenarios
+
+ ///
+ /// Test listing workspaces with authentication
+ ///
+ [SkippableFact]
+ public async Task ListWorkspacesAsync_WithAuthentication_ShouldReturnJsonResponseOrNoWorkspacesMessage()
+ {
+ // Arrange - Try to authenticate
+ var isAuthenticated = await TryAuthenticateAsync();
+
+ Skip.IfNot(isAuthenticated, "Skipping authenticated test - no valid credentials available");
+
+ // Act
+ var result = await _workspacesTool.ListWorkspacesAsync();
+
+ // Assert
+ AssertResult("workspaces", result);
+ }
+
+ [SkippableFact]
+ public async Task ListWorkspacesAsync_WithAuthentication_AndRoles_ShouldReturnJsonResponseOrNoWorkspacesMessage()
+ {
+ // Arrange - Try to authenticate
+ var isAuthenticated = await TryAuthenticateAsync();
+
+ Skip.IfNot(isAuthenticated, "Skipping authenticated test - no valid credentials available");
+
+ var testRoles = "Admin,Member,Contributor,Viewer";
+
+ // Act
+ var result = await _workspacesTool.ListWorkspacesAsync(testRoles);
+
+ // Assert
+ AssertResult("workspaces", result);
+
+ // Additional check for roles filtering
+ if (IsValidJson(result) && !result.Contains("No workspaces found"))
+ {
+ var jsonDoc = JsonDocument.Parse(result);
+ Assert.True(jsonDoc.RootElement.TryGetProperty("filteredByRoles", out var filteredByRoles));
+ Assert.True(filteredByRoles.GetBoolean());
+ Assert.True(jsonDoc.RootElement.TryGetProperty("roles", out var roles));
+ Assert.Equal(testRoles, roles.GetString());
+ }
+ }
+
+ [SkippableFact]
+ public async Task ListWorkspacesAsync_WithAuthentication_AndContinuationToken_ShouldHandleTokenParameter()
+ {
+ // Arrange - Try to authenticate
+ var isAuthenticated = await TryAuthenticateAsync();
+
+ Skip.IfNot(isAuthenticated, "Skipping authenticated test - no valid credentials available");
+
+ var testToken = "test-continuation-token-12345";
+
+ // Act
+ var result = await _workspacesTool.ListWorkspacesAsync(continuationToken: testToken);
+
+ // Assert
+ AssertResult("workspaces", result);
+ }
+
+ [SkippableFact]
+ public async Task ListWorkspacesAsync_WithAuthentication_AndPreferWorkspaceSpecificEndpoints_ShouldIncludeApiEndpoints()
+ {
+ // Arrange - Try to authenticate
+ var isAuthenticated = await TryAuthenticateAsync();
+
+ Skip.IfNot(isAuthenticated, "Skipping authenticated test - no valid credentials available");
+
+ // Act
+ var result = await _workspacesTool.ListWorkspacesAsync(preferWorkspaceSpecificEndpoints: true);
+
+ // Assert
+ AssertResult("workspaces", result);
+
+ // Additional check for API endpoints flag
+ if (IsValidJson(result) && !result.Contains("No workspaces found"))
+ {
+ var jsonDoc = JsonDocument.Parse(result);
+ Assert.True(jsonDoc.RootElement.TryGetProperty("includesApiEndpoints", out var includesApiEndpoints));
+ Assert.True(includesApiEndpoints.GetBoolean());
+ }
+ }
+
+ [SkippableFact]
+ public async Task ListWorkspacesAsync_WithAuthentication_AllParameters_ShouldHandleComplexRequest()
+ {
+ // Arrange - Try to authenticate
+ var isAuthenticated = await TryAuthenticateAsync();
+
+ Skip.IfNot(isAuthenticated, "Skipping authenticated test - no valid credentials available");
+
+ var testRoles = "Admin,Contributor";
+ var testToken = "test-token-complex";
+
+ // Act
+ var result = await _workspacesTool.ListWorkspacesAsync(
+ roles: testRoles,
+ continuationToken: testToken,
+ preferWorkspaceSpecificEndpoints: true);
+
+ // Assert
+ AssertResult("workspaces", result);
+
+ // Additional checks for all parameters
+ if (IsValidJson(result) && !result.Contains("No workspaces found"))
+ {
+ var jsonDoc = JsonDocument.Parse(result);
+
+ // Check roles filtering
+ Assert.True(jsonDoc.RootElement.TryGetProperty("filteredByRoles", out var filteredByRoles));
+ Assert.True(filteredByRoles.GetBoolean());
+ Assert.True(jsonDoc.RootElement.TryGetProperty("roles", out var roles));
+ Assert.Equal(testRoles, roles.GetString());
+
+ // Check API endpoints inclusion
+ Assert.True(jsonDoc.RootElement.TryGetProperty("includesApiEndpoints", out var includesApiEndpoints));
+ Assert.True(includesApiEndpoints.GetBoolean());
+ }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/DataFactory.MCP/Abstractions/FabricServiceBase.cs b/DataFactory.MCP/Abstractions/FabricServiceBase.cs
index 42d3d19..03f827b 100644
--- a/DataFactory.MCP/Abstractions/FabricServiceBase.cs
+++ b/DataFactory.MCP/Abstractions/FabricServiceBase.cs
@@ -10,7 +10,7 @@ namespace DataFactory.MCP.Abstractions;
///
/// Abstract base class for Microsoft Fabric API services providing common functionality
///
-public abstract class FabricServiceBase
+public abstract class FabricServiceBase : IDisposable
{
protected const string BaseUrl = "https://api.fabric.microsoft.com/v1";
protected readonly HttpClient HttpClient;
@@ -19,11 +19,10 @@ public abstract class FabricServiceBase
protected readonly JsonSerializerOptions JsonOptions;
protected FabricServiceBase(
- HttpClient httpClient,
ILogger logger,
IAuthenticationService authService)
{
- HttpClient = httpClient;
+ HttpClient = new HttpClient();
Logger = logger;
AuthService = authService;
@@ -88,4 +87,9 @@ protected async Task EnsureAuthenticationAsync()
throw new HttpRequestException($"API request failed: {response.StatusCode} - {errorContent}");
}
}
+
+ public void Dispose()
+ {
+ HttpClient?.Dispose();
+ }
}
diff --git a/DataFactory.MCP/Abstractions/Interfaces/IFabricWorkspaceService.cs b/DataFactory.MCP/Abstractions/Interfaces/IFabricWorkspaceService.cs
new file mode 100644
index 0000000..64a56c1
--- /dev/null
+++ b/DataFactory.MCP/Abstractions/Interfaces/IFabricWorkspaceService.cs
@@ -0,0 +1,21 @@
+using DataFactory.MCP.Models.Workspace;
+
+namespace DataFactory.MCP.Abstractions.Interfaces;
+
+///
+/// Service for interacting with Microsoft Fabric Workspaces API
+///
+public interface IFabricWorkspaceService
+{
+ ///
+ /// Lists all workspaces the user has permission for
+ ///
+ /// A list of roles. Separate values using a comma. If not provided, all workspaces are returned.
+ /// A token for retrieving the next page of results
+ /// A setting that controls whether to include the workspace-specific API endpoint per workspace
+ /// List of workspaces
+ Task ListWorkspacesAsync(
+ string? roles = null,
+ string? continuationToken = null,
+ bool? preferWorkspaceSpecificEndpoints = null);
+}
\ No newline at end of file
diff --git a/DataFactory.MCP/DataFactory.MCP.csproj b/DataFactory.MCP/DataFactory.MCP.csproj
index 957d60c..bd603e4 100644
--- a/DataFactory.MCP/DataFactory.MCP.csproj
+++ b/DataFactory.MCP/DataFactory.MCP.csproj
@@ -1,7 +1,7 @@
- net10.0
+ net9.0
Major
Exe
enable
@@ -30,9 +30,9 @@
-
-
-
+
+
+
diff --git a/DataFactory.MCP/Extensions/WorkspaceExtensions.cs b/DataFactory.MCP/Extensions/WorkspaceExtensions.cs
new file mode 100644
index 0000000..bfa0301
--- /dev/null
+++ b/DataFactory.MCP/Extensions/WorkspaceExtensions.cs
@@ -0,0 +1,31 @@
+using DataFactory.MCP.Models.Workspace;
+
+namespace DataFactory.MCP.Extensions;
+
+///
+/// Extension methods for Workspace model transformations.
+///
+public static class WorkspaceExtensions
+{
+ ///
+ /// Formats a Workspace object for MCP API responses.
+ /// Provides consistent output format and handles optional properties appropriately.
+ ///
+ /// The workspace object to format
+ /// Formatted object ready for JSON serialization
+ public static object ToFormattedInfo(this Workspace workspace)
+ {
+ var formattedInfo = new
+ {
+ Id = workspace.Id,
+ DisplayName = workspace.DisplayName,
+ Description = workspace.Description,
+ Type = workspace.Type.ToString(),
+ CapacityId = workspace.CapacityId,
+ DomainId = workspace.DomainId,
+ ApiEndpoint = workspace.ApiEndpoint
+ };
+
+ return formattedInfo;
+ }
+}
\ No newline at end of file
diff --git a/DataFactory.MCP/Models/AzureAdConfiguration.cs b/DataFactory.MCP/Models/AzureAdConfiguration.cs
index 8b11660..4bf80c9 100644
--- a/DataFactory.MCP/Models/AzureAdConfiguration.cs
+++ b/DataFactory.MCP/Models/AzureAdConfiguration.cs
@@ -29,5 +29,5 @@ public static class AzureAdConfiguration
///
/// Redirect URI for interactive authentication
///
- public const string RedirectUri = "http://localhost:8080";
+ public const string RedirectUri = "http://localhost:0";
}
diff --git a/DataFactory.MCP/Models/Messages.cs b/DataFactory.MCP/Models/Messages.cs
index 66a0469..c05b65c 100644
--- a/DataFactory.MCP/Models/Messages.cs
+++ b/DataFactory.MCP/Models/Messages.cs
@@ -124,6 +124,11 @@ public static class Messages
/// Error retrieving gateway message template
///
public const string ErrorRetrievingGatewayTemplate = "Error retrieving gateway: {0}";
+
+ ///
+ /// Error listing workspaces message template
+ ///
+ public const string ErrorListingWorkspacesTemplate = "Error listing workspaces: {0}";
#endregion
#region Validation Messages
@@ -168,6 +173,11 @@ public static class Messages
/// Template for gateway not found message
///
public const string GatewayNotFoundTemplate = "Gateway with ID '{0}' not found or you don't have permission to access it.";
+
+ ///
+ /// Message when no workspaces are found
+ ///
+ public const string NoWorkspacesFound = "No workspaces found. Make sure you have the required permissions (Workspace.Read.All or Workspace.ReadWrite.All).";
#endregion
#region Service Messages
diff --git a/DataFactory.MCP/Models/Workspace/Workspace.cs b/DataFactory.MCP/Models/Workspace/Workspace.cs
new file mode 100644
index 0000000..1f8700f
--- /dev/null
+++ b/DataFactory.MCP/Models/Workspace/Workspace.cs
@@ -0,0 +1,53 @@
+using System.Text.Json.Serialization;
+
+namespace DataFactory.MCP.Models.Workspace;
+
+///
+/// A workspace object representing a Microsoft Fabric workspace
+///
+public class Workspace
+{
+ ///
+ /// The workspace ID
+ ///
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ ///
+ /// The workspace display name
+ ///
+ [JsonPropertyName("displayName")]
+ public string DisplayName { get; set; } = string.Empty;
+
+ ///
+ /// The workspace description
+ ///
+ [JsonPropertyName("description")]
+ public string Description { get; set; } = string.Empty;
+
+ ///
+ /// The workspace type
+ ///
+ [JsonPropertyName("type")]
+ public WorkspaceType Type { get; set; }
+
+ ///
+ /// The ID of the capacity the workspace is assigned to
+ ///
+ [JsonPropertyName("capacityId")]
+ public string? CapacityId { get; set; }
+
+ ///
+ /// The ID of the domain the workspace is assigned to
+ ///
+ [JsonPropertyName("domainId")]
+ public string? DomainId { get; set; }
+
+ ///
+ /// HTTP URL that represents the API endpoint specific to the workspace.
+ /// This endpoint value is returned when the user enables preferWorkspaceSpecificEndpoints.
+ /// It allows for API access over private links.
+ ///
+ [JsonPropertyName("apiEndpoint")]
+ public string? ApiEndpoint { get; set; }
+}
\ No newline at end of file
diff --git a/DataFactory.MCP/Models/Workspace/WorkspaceResponses.cs b/DataFactory.MCP/Models/Workspace/WorkspaceResponses.cs
new file mode 100644
index 0000000..7909a48
--- /dev/null
+++ b/DataFactory.MCP/Models/Workspace/WorkspaceResponses.cs
@@ -0,0 +1,27 @@
+using System.Text.Json.Serialization;
+
+namespace DataFactory.MCP.Models.Workspace;
+
+///
+/// Response containing a list of workspaces with optional pagination
+///
+public class ListWorkspacesResponse
+{
+ ///
+ /// A list of workspaces
+ ///
+ [JsonPropertyName("value")]
+ public List Value { get; set; } = new();
+
+ ///
+ /// The token for the next result set batch. If there are no more records, it's removed from the response.
+ ///
+ [JsonPropertyName("continuationToken")]
+ public string? ContinuationToken { get; set; }
+
+ ///
+ /// The URI of the next result set batch. If there are no more records, it's removed from the response.
+ ///
+ [JsonPropertyName("continuationUri")]
+ public string? ContinuationUri { get; set; }
+}
\ No newline at end of file
diff --git a/DataFactory.MCP/Models/Workspace/WorkspaceType.cs b/DataFactory.MCP/Models/Workspace/WorkspaceType.cs
new file mode 100644
index 0000000..204bae8
--- /dev/null
+++ b/DataFactory.MCP/Models/Workspace/WorkspaceType.cs
@@ -0,0 +1,25 @@
+using System.Text.Json.Serialization;
+
+namespace DataFactory.MCP.Models.Workspace;
+
+///
+/// A workspace type. Additional workspace types may be added over time.
+///
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum WorkspaceType
+{
+ ///
+ /// My folder or My workspace used to manage user items.
+ ///
+ Personal,
+
+ ///
+ /// Workspace used to manage the Fabric items.
+ ///
+ Workspace,
+
+ ///
+ /// Admin monitoring workspace. Contains admin reports such as the audit report and the usage and adoption report.
+ ///
+ AdminWorkspace
+}
\ No newline at end of file
diff --git a/DataFactory.MCP/Program.cs b/DataFactory.MCP/Program.cs
index ad32b68..8c7ca65 100644
--- a/DataFactory.MCP/Program.cs
+++ b/DataFactory.MCP/Program.cs
@@ -1,7 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-using DataFactory.MCP.Models;
using DataFactory.MCP.Tools;
using DataFactory.MCP.Abstractions.Interfaces;
using DataFactory.MCP.Services;
@@ -11,24 +10,17 @@
// Configure all logs to go to stderr (stdout is used for the MCP protocol messages).
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);
-// Add authentication services directly
-builder.Services.AddSingleton();
-builder.Services.AddTransient();
-
-// Add HTTP client and Fabric Gateway services
-builder.Services.AddHttpClient();
-builder.Services.AddTransient();
-
-// Add HTTP client and Fabric Connection services
-builder.Services.AddHttpClient();
-builder.Services.AddTransient();
-
// Add the MCP services: the transport to use (stdio) and the tools to register.
builder.Services
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
.AddMcpServer()
.WithStdioServerTransport()
.WithTools()
.WithTools()
- .WithTools();
+ .WithTools()
+ .WithTools();
await builder.Build().RunAsync();
diff --git a/DataFactory.MCP/Services/AuthenticationService.cs b/DataFactory.MCP/Services/AuthenticationService.cs
index 074aacf..93a93ff 100644
--- a/DataFactory.MCP/Services/AuthenticationService.cs
+++ b/DataFactory.MCP/Services/AuthenticationService.cs
@@ -26,10 +26,10 @@ private void InitializeClientApplications()
{
// Initialize public client for interactive authentication
_publicClientApp = PublicClientApplicationBuilder
- .Create(AzureAdConfiguration.ClientId)
- .WithAuthority(AzureAdConfiguration.Authority)
- .WithRedirectUri(AzureAdConfiguration.RedirectUri)
- .Build();
+ .Create(AzureAdConfiguration.ClientId)
+ .WithAuthority(new Uri(AzureAdConfiguration.Authority))
+ .WithRedirectUri(AzureAdConfiguration.RedirectUri)
+ .Build();
_logger.LogInformation(Messages.AzureAdClientInitializedSuccessfully);
}
diff --git a/DataFactory.MCP/Services/FabricConnectionService.cs b/DataFactory.MCP/Services/FabricConnectionService.cs
index 77495ca..bee1061 100644
--- a/DataFactory.MCP/Services/FabricConnectionService.cs
+++ b/DataFactory.MCP/Services/FabricConnectionService.cs
@@ -12,10 +12,9 @@ namespace DataFactory.MCP.Services;
public class FabricConnectionService : FabricServiceBase, IFabricConnectionService
{
public FabricConnectionService(
- HttpClient httpClient,
ILogger logger,
IAuthenticationService authService)
- : base(httpClient, logger, authService)
+ : base(logger, authService)
{
// Add the custom connection converter to handle polymorphic deserialization
JsonOptions.Converters.Add(new ConnectionJsonConverter());
diff --git a/DataFactory.MCP/Services/FabricGatewayService.cs b/DataFactory.MCP/Services/FabricGatewayService.cs
index 607692d..c38b31d 100644
--- a/DataFactory.MCP/Services/FabricGatewayService.cs
+++ b/DataFactory.MCP/Services/FabricGatewayService.cs
@@ -12,10 +12,9 @@ namespace DataFactory.MCP.Services;
public class FabricGatewayService : FabricServiceBase, IFabricGatewayService
{
public FabricGatewayService(
- HttpClient httpClient,
ILogger logger,
IAuthenticationService authService)
- : base(httpClient, logger, authService)
+ : base(logger, authService)
{
}
diff --git a/DataFactory.MCP/Services/FabricWorkspaceService.cs b/DataFactory.MCP/Services/FabricWorkspaceService.cs
new file mode 100644
index 0000000..dd86fcb
--- /dev/null
+++ b/DataFactory.MCP/Services/FabricWorkspaceService.cs
@@ -0,0 +1,88 @@
+using DataFactory.MCP.Abstractions;
+using DataFactory.MCP.Abstractions.Interfaces;
+using DataFactory.MCP.Models.Workspace;
+using Microsoft.Extensions.Logging;
+using System.Text;
+
+namespace DataFactory.MCP.Services;
+
+///
+/// Service for interacting with Microsoft Fabric Workspaces API
+///
+public class FabricWorkspaceService : FabricServiceBase, IFabricWorkspaceService
+{
+ public FabricWorkspaceService(
+ ILogger logger,
+ IAuthenticationService authService)
+ : base(logger, authService)
+ {
+ }
+
+ public async Task ListWorkspacesAsync(
+ string? roles = null,
+ string? continuationToken = null,
+ bool? preferWorkspaceSpecificEndpoints = null)
+ {
+ try
+ {
+ await EnsureAuthenticationAsync();
+
+ var url = BuildWorkspacesUrl(roles, continuationToken, preferWorkspaceSpecificEndpoints);
+
+ Logger.LogInformation("Fetching workspaces from: {Url}", url);
+
+ var response = await HttpClient.GetAsync(url);
+
+ if (response.IsSuccessStatusCode)
+ {
+ var content = await response.Content.ReadAsStringAsync();
+ var workspacesResponse = System.Text.Json.JsonSerializer.Deserialize(content, JsonOptions);
+
+ Logger.LogInformation("Successfully retrieved {Count} workspaces", workspacesResponse?.Value?.Count ?? 0);
+ return workspacesResponse ?? new ListWorkspacesResponse();
+ }
+ else
+ {
+ var errorContent = await response.Content.ReadAsStringAsync();
+ Logger.LogError("API request failed. Status: {StatusCode}, Content: {Content}",
+ response.StatusCode, errorContent);
+
+ throw new HttpRequestException($"API request failed: {response.StatusCode} - {errorContent}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error fetching workspaces");
+ throw;
+ }
+ }
+
+ private static string BuildWorkspacesUrl(string? roles, string? continuationToken, bool? preferWorkspaceSpecificEndpoints)
+ {
+ var url = new StringBuilder($"{BaseUrl}/workspaces");
+ var queryParams = new List();
+
+ if (!string.IsNullOrEmpty(roles))
+ {
+ queryParams.Add($"roles={Uri.EscapeDataString(roles)}");
+ }
+
+ if (!string.IsNullOrEmpty(continuationToken))
+ {
+ queryParams.Add($"continuationToken={Uri.EscapeDataString(continuationToken)}");
+ }
+
+ if (preferWorkspaceSpecificEndpoints.HasValue)
+ {
+ queryParams.Add($"preferWorkspaceSpecificEndpoints={preferWorkspaceSpecificEndpoints.Value.ToString().ToLower()}");
+ }
+
+ if (queryParams.Any())
+ {
+ url.Append("?");
+ url.Append(string.Join("&", queryParams));
+ }
+
+ return url.ToString();
+ }
+}
\ No newline at end of file
diff --git a/DataFactory.MCP/Tools/WorkspacesTool.cs b/DataFactory.MCP/Tools/WorkspacesTool.cs
new file mode 100644
index 0000000..b2eb2f6
--- /dev/null
+++ b/DataFactory.MCP/Tools/WorkspacesTool.cs
@@ -0,0 +1,66 @@
+using ModelContextProtocol.Server;
+using System.ComponentModel;
+using DataFactory.MCP.Abstractions.Interfaces;
+using DataFactory.MCP.Extensions;
+using DataFactory.MCP.Models;
+using System.Text.Json;
+
+namespace DataFactory.MCP.Tools;
+
+[McpServerToolType]
+public class WorkspacesTool
+{
+ private readonly IFabricWorkspaceService _workspaceService;
+
+ public WorkspacesTool(IFabricWorkspaceService workspaceService)
+ {
+ _workspaceService = workspaceService;
+ }
+
+ [McpServerTool, Description(@"Lists all workspaces the user has permission for. Returns workspaces filtered by the specified roles if provided.")]
+ public async Task ListWorkspacesAsync(
+ [Description("A list of roles. Separate values using a comma (e.g., 'Admin,Member,Contributor,Viewer'). If not provided, all workspaces are returned.")] string? roles = null,
+ [Description("A token for retrieving the next page of results (optional)")] string? continuationToken = null,
+ [Description("Include workspace-specific API endpoints in the response (true/false, optional)")] bool? preferWorkspaceSpecificEndpoints = null)
+ {
+ try
+ {
+ var response = await _workspaceService.ListWorkspacesAsync(roles, continuationToken, preferWorkspaceSpecificEndpoints);
+
+ if (!response.Value.Any())
+ {
+ return Messages.NoWorkspacesFound;
+ }
+
+ var result = new
+ {
+ TotalCount = response.Value.Count,
+ ContinuationToken = response.ContinuationToken,
+ ContinuationUri = response.ContinuationUri,
+ HasMoreResults = !string.IsNullOrEmpty(response.ContinuationToken),
+ FilteredByRoles = !string.IsNullOrEmpty(roles),
+ Roles = roles,
+ IncludesApiEndpoints = preferWorkspaceSpecificEndpoints == true,
+ Workspaces = response.Value.Select(w => w.ToFormattedInfo())
+ };
+
+ return JsonSerializer.Serialize(result, new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ });
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ return string.Format(Messages.AuthenticationErrorTemplate, ex.Message);
+ }
+ catch (HttpRequestException ex)
+ {
+ return string.Format(Messages.ApiRequestFailedTemplate, ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return string.Format(Messages.ErrorListingWorkspacesTemplate, ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 7cb1d90..43dad79 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -3,7 +3,7 @@
0
- 3
+ 4
0
beta
diff --git a/README.md b/README.md
index 1794f21..f05ff6c 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ A Model Context Protocol (MCP) server for Microsoft Data Factory and Azure Fabri
- 🔐 **Azure AD Authentication**: Interactive and service principal authentication
- 🌐 **Gateway Management**: List and manage Azure Data Factory gateways
- 🔗 **Connection Management**: List and retrieve details for Azure Data Factory connections
+- **Workspace Management**: List and manage Microsoft Fabric workspaces
- 🏗️ **Microsoft Fabric Integration**: Support for on-premises, personal, and virtual network gateways
- 📦 **NuGet Distribution**: Available as a NuGet package for easy integration
- 🔧 **MCP Protocol**: Built using the official MCP C# SDK
@@ -16,6 +17,7 @@ A Model Context Protocol (MCP) server for Microsoft Data Factory and Azure Fabri
- **Authentication**: `authenticate_interactive`, `authenticate_service_principal`, `get_authentication_status`, `get_access_token`, `sign_out`
- **Gateway Management**: `list_gateways`, `get_gateway`
- **Connection Management**: `list_connections`, `get_connection`
+- **Workspace Management**: `list_workspaces`, `list_workspaces_summary`
## Quick Start
@@ -35,7 +37,7 @@ A Model Context Protocol (MCP) server for Microsoft Data Factory and Azure Fabri
"args": [
"Microsoft.DataFactory.MCP",
"--version",
- "0.2.0-beta",
+ "#{VERSION}#",
"--yes"
]
}
@@ -80,6 +82,7 @@ See the detailed guides for comprehensive usage instructions:
- **Authentication**: See [Authentication Guide](https://github.com/microsoft/DataFactory.MCP/blob/main/docs/authentication.md)
- **Gateway Management**: See [Gateway Management Guide](https://github.com/microsoft/DataFactory.MCP/blob/main/docs/gateway-management.md)
- **Connection Management**: See [Connection Management Guide](https://github.com/microsoft/DataFactory.MCP/blob/main/docs/connection-management.md)
+- **Workspace Management**: See [Workspace Management Guide](https://github.com/microsoft/DataFactory.MCP/blob/main/docs/workspace-management.md)
## Development
@@ -110,6 +113,7 @@ For complete documentation, see our **[Documentation Index](https://github.com/m
- **[Authentication Guide](https://github.com/microsoft/DataFactory.MCP/blob/main/docs/authentication.md)** - Complete authentication setup and usage
- **[Gateway Management Guide](https://github.com/microsoft/DataFactory.MCP/blob/main/docs/gateway-management.md)** - Gateway operations and examples
- **[Connection Management Guide](https://github.com/microsoft/DataFactory.MCP/blob/main/docs/connection-management.md)** - Connection operations and examples
+- **[Workspace Management Guide](https://github.com/microsoft/DataFactory.MCP/blob/main/docs/workspace-management.md)** - Workspace operations and examples
- **[Architecture Guide](https://github.com/microsoft/DataFactory.MCP/blob/main/docs/ARCHITECTURE.md)** - Technical architecture and design details
## Contributing
diff --git a/Update-ServerVersion.ps1 b/Update-ServerVersion.ps1
index 4ac408f..5f457ce 100644
--- a/Update-ServerVersion.ps1
+++ b/Update-ServerVersion.ps1
@@ -1,4 +1,6 @@
# Update server.json and README.md with current version from Directory.Build.props
+# This script replaces version placeholders (#{VERSION}#) and actual version numbers
+# with the current version from Directory.Build.props
[CmdletBinding()]
param(
[string]$Version,
@@ -63,11 +65,13 @@ function Update-ReadmeVersion {
$originalContent = $content
# Simple regex to find and replace version in README
- # Looking for version pattern after "--version",
- $pattern = '("--version",\s*[\r\n]+\s*)"[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?"'
+ # Looking for version pattern after "--version", supporting both actual versions and placeholders
+ $pattern1 = '("--version",\s*[\r\n]+\s*)"[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?"'
+ $pattern2 = '("--version",\s*[\r\n]+\s*)"#{VERSION}#"'
$replacement = "`$1`"$NewVersion`""
- $content = $content -replace $pattern, $replacement
+ $content = $content -replace $pattern1, $replacement
+ $content = $content -replace $pattern2, $replacement
if ($content -ne $originalContent) {
Set-Content $Path -Value $content -Encoding UTF8 -NoNewline
diff --git a/docs/index.md b/docs/index.md
index 401f542..3b84b34 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -7,6 +7,7 @@ Comprehensive documentation for the Microsoft Data Factory MCP Server.
- **[Authentication Guide](authentication.md)** - Setup and configuration
- **[Gateway Management Guide](gateway-management.md)** - Managing Azure Data Factory gateways
- **[Connection Management Guide](connection-management.md)** - Managing Azure Data Factory connections
+- **[Workspace Management Guide](workspace-management.md)** - Managing Microsoft Fabric workspaces
## Technical Documentation
diff --git a/docs/workspace-management.md b/docs/workspace-management.md
new file mode 100644
index 0000000..f99f97b
--- /dev/null
+++ b/docs/workspace-management.md
@@ -0,0 +1,116 @@
+# Workspace Management Guide
+
+This guide covers how to use the Microsoft Data Factory MCP Server for managing Microsoft Fabric workspaces.
+
+## Overview
+
+The workspace management tools allow you to:
+- List all accessible workspaces with filtering by role
+- Work with different workspace types (Personal, Shared, Admin)
+- Navigate paginated results for large workspace collections
+
+## Available Operations
+
+### List Workspaces
+
+Retrieve a list of all workspaces you have access to with full details.
+
+#### Usage
+```
+list_workspaces
+```
+
+#### With Role Filtering
+```
+list_workspaces(roles: "Admin,Member")
+```
+
+#### With Pagination
+```
+list_workspaces(continuationToken: "next-page-token")
+```
+
+#### With Workspace-Specific Endpoints
+```
+list_workspaces(preferWorkspaceSpecificEndpoints: true)
+```
+
+#### Response Format
+```json
+{
+ "totalCount": 10,
+ "continuationToken": "eyJza2lwIjoyMCwidGFrZSI6MjB9",
+ "hasMoreResults": true,
+ "workspaces": [
+ {
+ "id": "12345678-1234-1234-1234-123456789012",
+ "name": "Sales Analytics Workspace",
+ "description": "Workspace for sales team analytics and reporting",
+ "type": "PersonalGroup",
+ "state": "Active",
+ "isReadOnly": false,
+ "isOnDedicatedCapacity": true,
+ "capacityId": "87654321-4321-4321-4321-210987654321",
+ "defaultDatasetStorageFormat": "Small"
+ }
+ ]
+}
+```
+
+## Role-Based Filtering
+
+The workspace tools support filtering by user roles within workspaces:
+
+- **Admin**: Full administrative access to the workspace
+- **Member**: Can contribute content and manage workspace settings
+- **Contributor**: Can contribute content but cannot manage workspace settings
+- **Viewer**: Read-only access to workspace content
+
+### Multiple Role Filtering
+```
+# Filter by multiple roles (comma-separated)
+list_workspaces(roles: "Admin,Member,Contributor")
+
+# Filter by single role
+list_workspaces(roles: "Admin")
+```
+
+## Workspace Types
+
+The system supports different workspace types:
+
+- **PersonalGroup**: Personal workspace for individual users
+- **Group**: Shared workspace for team collaboration
+- **AdminWorkspace**: Administrative workspace with elevated privileges
+
+## Usage Examples
+
+### Basic Workspace Operations
+```
+# List all accessible workspaces
+> show me all my fabric workspaces
+
+# List workspaces where I'm an admin
+> list workspaces where I have admin role
+
+# List workspaces with pagination
+> list my workspaces with continuation token abc123
+```
+
+### Advanced Filtering
+```
+# List workspaces by specific roles
+> show me workspaces where I'm an admin or member
+
+# List only active workspaces
+> show me all active workspaces
+```
+
+### Pagination Scenarios
+```
+# Handle large workspace collections
+> list all my workspaces (this may return a continuation token for more results)
+
+# Continue listing with pagination token
+> continue listing workspaces with token eyJza2lwIjoyMCwidGFrZSI6MjB9
+```
\ No newline at end of file