Skip to content
This repository was archived by the owner on Sep 4, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release History

## Unreleased

### Features Added
- Support for Azure Data Lake Storage Gen2 directory operations - List paths in Data Lake directories via the command: `azmcp storage datalake directory list-paths`.

## 0.4.1 (2025-07-17)

### Features Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some
- "Show me the tables in my Storage account"
- "Get details about my Storage container"
- "List paths in my Data Lake file system"
- "List paths in my Data Lake directory"
- "Show my key-value pairs in App Config"

### 🔧 Azure Resource Management
Expand Down
5 changes: 5 additions & 0 deletions docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,11 @@ azmcp storage blob container details --subscription <subscription> \
azmcp storage datalake file-system list-paths --subscription <subscription> \
--account-name <account-name> \
--file-system-name <file-system-name>

# List paths in a Data Lake directory
azmcp storage datalake directory list-paths --subscription <subscription> \
--account-name <account-name> \
--directory-name <filesystem/directory>
```

### Azure Subscription Management
Expand Down
2 changes: 2 additions & 0 deletions e2eTests/e2eTestPrompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
| azmcp-storage-table-list | Show me the tables in the storage account <account_name> |
| azmcp-storage-datalake-file-system-list-paths | List all paths in the Data Lake file system <file_system_name> in the storage account <account_name> |
| azmcp-storage-datalake-file-system-list-paths | Show me the paths in the Data Lake file system <file_system_name> in the storage account <account_name> |
| azmcp-storage-datalake-directory-list-paths | List all paths in the Data Lake directory <filesystem_name/directory_name> in the storage account <account_name> |
| azmcp-storage-datalake-directory-list-paths | Show me the paths in the Data Lake directory <filesystem_name/directory_name> in the storage account <account_name> |

## Azure Subscription Management

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;
using AzureMcp.Areas.Storage.Commands;
using AzureMcp.Areas.Storage.Options;
using AzureMcp.Areas.Storage.Options.DataLake;
using AzureMcp.Commands;

namespace AzureMcp.Areas.Storage.Commands.DataLake.Directory;

public abstract class BaseDirectoryCommand<
[DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions>
: BaseStorageCommand<TOptions> where TOptions : BaseDirectoryOptions, new()
{
protected readonly Option<string> _directoryOption = StorageOptionDefinitions.Directory;

protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
command.AddOption(_directoryOption);
}

protected override TOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.Directory = parseResult.GetValueForOption(_directoryOption);
return options;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using AzureMcp.Areas.Storage.Models;
using AzureMcp.Areas.Storage.Options.DataLake.Directory;
using AzureMcp.Areas.Storage.Services;
using AzureMcp.Commands.Storage;
using AzureMcp.Services.Telemetry;
using Microsoft.Extensions.Logging;

namespace AzureMcp.Areas.Storage.Commands.DataLake.Directory;

public sealed class DirectoryListPathsCommand(ILogger<DirectoryListPathsCommand> logger) : BaseDirectoryCommand<ListPathsOptions>
{
private const string CommandTitle = "List Data Lake Directory Paths";
private readonly ILogger<DirectoryListPathsCommand> _logger = logger;

public override string Name => "list-paths";

public override string Description =>
"""
List all paths in a Data Lake directory. This command retrieves and displays all paths (files and directories)
available in the specified directory within a Data Lake storage account. The directory path should include the
file system name (e.g., 'filesystem/directory'). Results include path names, types (file or directory),
and metadata, returned as a JSON array. Requires account-name and directory-name.
""";

public override string Title => CommandTitle;

[McpServerTool(Destructive = false, ReadOnly = true, Title = CommandTitle)]
public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult)
{
var options = BindOptions(parseResult);

try
{
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
{
return context.Response;
}

context.Activity?.WithSubscriptionTag(options);

var storageService = context.GetService<IStorageService>();
var paths = await storageService.ListDataLakeDirectoryPaths(
options.Account!,
options.Directory!,
options.Subscription!,
options.Tenant,
options.RetryPolicy);

context.Response.Results = ResponseResult.Create(
new DirectoryListPathsCommandResult(paths ?? []),
StorageJsonContext.Default.DirectoryListPathsCommandResult);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error listing Data Lake directory paths. Account: {Account}, Directory: {Directory}.",
options.Account, options.Directory);
HandleException(context, ex);
}

return context.Response;
}

internal record DirectoryListPathsCommandResult(List<DataLakePathInfo> Paths);
}
2 changes: 2 additions & 0 deletions src/Areas/Storage/Commands/StorageJsonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using AzureMcp.Areas.Storage.Commands.Account;
using AzureMcp.Areas.Storage.Commands.Blob;
using AzureMcp.Areas.Storage.Commands.Blob.Container;
using AzureMcp.Areas.Storage.Commands.DataLake.Directory;
using AzureMcp.Areas.Storage.Commands.DataLake.FileSystem;
using AzureMcp.Areas.Storage.Commands.Table;

Expand All @@ -16,6 +17,7 @@ namespace AzureMcp.Commands.Storage;
[JsonSerializable(typeof(ContainerListCommand.ContainerListCommandResult))]
[JsonSerializable(typeof(ContainerDetailsCommand.ContainerDetailsCommandResult))]
[JsonSerializable(typeof(FileSystemListPathsCommand.FileSystemListPathsCommandResult))]
[JsonSerializable(typeof(DirectoryListPathsCommand.DirectoryListPathsCommandResult))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal sealed partial class StorageJsonContext : JsonSerializerContext
{
Expand Down
12 changes: 12 additions & 0 deletions src/Areas/Storage/Options/DataLake/BaseDirectoryOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Serialization;

namespace AzureMcp.Areas.Storage.Options.DataLake;

public class BaseDirectoryOptions : BaseStorageOptions
{
[JsonPropertyName(StorageOptionDefinitions.DirectoryName)]
public string? Directory { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace AzureMcp.Areas.Storage.Options.DataLake.Directory;

public class ListPathsOptions : BaseDirectoryOptions;
9 changes: 9 additions & 0 deletions src/Areas/Storage/Options/StorageOptionDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public static class StorageOptionDefinitions
public const string ContainerName = "container-name";
public const string TableName = "table-name";
public const string FileSystemName = "file-system-name";
public const string DirectoryName = "directory-name";

public static readonly Option<string> Account = new(
$"--{AccountName}",
Expand Down Expand Up @@ -41,4 +42,12 @@ public static class StorageOptionDefinitions
{
IsRequired = true
};

public static readonly Option<string> Directory = new(
$"--{DirectoryName}",
"The name of the directory within the Data Lake file system to access."
)
{
IsRequired = true
};
}
6 changes: 6 additions & 0 deletions src/Areas/Storage/Services/IStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@ Task<List<DataLakePathInfo>> ListDataLakePaths(
string subscriptionId,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null);
Task<List<DataLakePathInfo>> ListDataLakeDirectoryPaths(
string accountName,
string directoryPath,
string subscriptionId,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null);
}
45 changes: 45 additions & 0 deletions src/Areas/Storage/Services/StorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,49 @@ public async Task<List<DataLakePathInfo>> ListDataLakePaths(

return paths;
}

public async Task<List<DataLakePathInfo>> ListDataLakeDirectoryPaths(
string accountName,
string directoryPath,
string subscriptionId,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null)
{
ValidateRequiredParameters(accountName, directoryPath, subscriptionId);

// Parse the directory path to extract file system and directory components
var pathParts = directoryPath.Split('/', 2, StringSplitOptions.RemoveEmptyEntries);
if (pathParts.Length < 1)
{
throw new ArgumentException("Directory path must include at least the file system name", nameof(directoryPath));
}

var fileSystemName = pathParts[0];
var directoryName = pathParts.Length > 1 ? pathParts[1] : "";

var dataLakeServiceClient = await CreateDataLakeServiceClient(accountName, tenant, retryPolicy);
var directoryClient = dataLakeServiceClient.GetDirectoryClient(fileSystemName, directoryName);
var paths = new List<DataLakePathInfo>();

try
{
await foreach (var pathItem in directoryClient.GetPathsAsync())
{
var pathInfo = new DataLakePathInfo(
pathItem.Name,
pathItem.IsDirectory == true ? "directory" : "file",
pathItem.ContentLength,
pathItem.LastModified,
pathItem.ETag.ToString());

paths.Add(pathInfo);
}
}
catch (Exception ex)
{
throw new Exception($"Error listing Data Lake directory paths: {ex.Message}", ex);
}

return paths;
}
}
8 changes: 8 additions & 0 deletions src/Areas/Storage/StorageSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using AzureMcp.Areas.Storage.Commands.Account;
using AzureMcp.Areas.Storage.Commands.Blob;
using AzureMcp.Areas.Storage.Commands.Blob.Container;
using AzureMcp.Areas.Storage.Commands.DataLake.Directory;
using AzureMcp.Areas.Storage.Commands.DataLake.FileSystem;
using AzureMcp.Areas.Storage.Commands.Table;
using AzureMcp.Areas.Storage.Services;
Expand Down Expand Up @@ -48,6 +49,10 @@ public void RegisterCommands(CommandGroup rootGroup, ILoggerFactory loggerFactor
var fileSystem = new CommandGroup("file-system", "Data Lake file system operations - Commands for managing file systems and paths in Azure Data Lake Storage Gen2.");
dataLake.AddSubGroup(fileSystem);

// Create directory subgroup under datalake
var directory = new CommandGroup("directory", "Data Lake directory operations - Commands for managing directories and paths within Azure Data Lake Storage Gen2 file systems.");
dataLake.AddSubGroup(directory);

// Register Storage commands
storageAccount.AddCommand("list", new AccountListCommand(
loggerFactory.CreateLogger<AccountListCommand>()));
Expand All @@ -63,5 +68,8 @@ public void RegisterCommands(CommandGroup rootGroup, ILoggerFactory loggerFactor

fileSystem.AddCommand("list-paths", new FileSystemListPathsCommand(
loggerFactory.CreateLogger<FileSystemListPathsCommand>()));

directory.AddCommand("list-paths", new DirectoryListPathsCommand(
loggerFactory.CreateLogger<DirectoryListPathsCommand>()));
}
}
17 changes: 17 additions & 0 deletions tests/Areas/Storage/LiveTests/StorageCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,5 +225,22 @@ public async Task Should_list_datalake_filesystem_paths()
var actual = result.AssertProperty("paths");
Assert.Equal(JsonValueKind.Array, actual.ValueKind);
}

[Fact]
[Trait("Category", "Live")]
public async Task Should_list_datalake_directory_paths()
{
var result = await CallToolAsync(
"azmcp_storage_datalake_directory_list-paths",
new()
{
{ "subscription", Settings.SubscriptionName },
{ "account-name", Settings.ResourceBaseName },
{ "directory-name", "testfilesystem/testdirectory" }
});

var actual = result.AssertProperty("paths");
Assert.Equal(JsonValueKind.Array, actual.ValueKind);
}
}
}
Loading