Skip to content
Open
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
4 changes: 2 additions & 2 deletions servers/Azure.Mcp.Server/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,12 +376,12 @@ This error indicates that the access token doesn't have sufficient permissions t

### Service Principal Returns 403 for OneLake Operations in VS Code

When using Azure MCP Server inside VS Code with a Service Principal authenticated via Azure CLI (`az login --service-principal`), OneLake DFS operations (such as `directory_create`, `upload_file`) may return `403 Forbidden`, even though the Service Principal has the correct permissions (e.g., Workspace Admin in Microsoft Fabric).
When using Azure MCP Server inside VS Code with a Service Principal authenticated via Azure CLI (`az login --service-principal`), OneLake DFS operations (such as `directory_create`, `upload-file`) may return `403 Forbidden`, even though the Service Principal has the correct permissions (e.g., Workspace Admin in Microsoft Fabric).
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The troubleshooting examples mix old underscore-style command names (directory_create) with the new hyphenated naming (upload-file). Update the remaining OneLake examples to the new tool/command names (and include the correct onelake_ prefix if that’s what callers use) so readers can reliably copy/paste working commands.

Copilot uses AI. Check for mistakes.

#### Symptoms

- OneLake **blob** operations (e.g., `file_list` without a path) may succeed with `200 OK`
- OneLake **DFS** operations (e.g., `directory_create`, `upload_file`) fail with `403 Forbidden`
- OneLake **DFS** operations (e.g., `directory_create`, `upload-file`) fail with `403 Forbidden`
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The troubleshooting examples mix old underscore-style command names (directory_create) with the new hyphenated naming (upload-file). Update the remaining OneLake examples to the new tool/command names (and include the correct onelake_ prefix if that’s what callers use) so readers can reliably copy/paste working commands.

Copilot uses AI. Check for mistakes.
- The same Service Principal works correctly when used from Python scripts or other tools outside VS Code
- Fabric REST API operations (e.g., `item_create`) may also fail with `401 Unauthorized`

Expand Down
28 changes: 14 additions & 14 deletions servers/Fabric.Mcp.Server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,20 +239,20 @@ The Fabric MCP Server exposes tools organized into three categories:

| Tool Name | Description |
|-----------|-------------|
| `onelake_list_workspaces` | Lists available Microsoft Fabric workspaces. |
| `onelake_list_items` | Lists workspace items with high-level metadata. |
| `onelake_list_items_dfs` | Lists Fabric items via the DFS endpoint. |
| `onelake_list_files` | Lists files using the hierarchical file-list endpoint. |
| `onelake_download_file` | Downloads a OneLake file. |
| `onelake_upload_file` | Uploads a file to OneLake storage. |
| `onelake_delete_file` | Deletes a file from OneLake storage. |
| `onelake_create_directory` | Creates a directory via the DFS endpoint. |
| `onelake_delete_directory` | Deletes a directory (optionally recursive). |
| `onelake_get_table_config` | Retrieves table API configuration for a workspace item. |
| `onelake_list_table_namespaces` | Lists table namespaces (schemas) exposed through the table API. |
| `onelake_get_table_namespace` | Retrieves metadata for a specific namespace. |
| `onelake_list_tables` | Lists tables published within a namespace. |
| `onelake_get_table` | Retrieves the definition for a specific table. |
| `onelake_list-workspaces` | Lists available Microsoft Fabric workspaces. |
| `onelake_list-items` | Lists workspace items with high-level metadata. |
| `onelake_list-items-dfs` | Lists Fabric items via the DFS endpoint. |
| `onelake_list-files` | Lists files using the hierarchical file-list endpoint. |
| `onelake_download-file` | Downloads a OneLake file. |
| `onelake_upload-file` | Uploads a file to OneLake storage. |
| `onelake_delete-file` | Deletes a file from OneLake storage. |
| `onelake_create-directory` | Creates a directory via the DFS endpoint. |
| `onelake_delete-directory` | Deletes a directory (optionally recursive). |
| `onelake_get-table-config` | Retrieves table API configuration for a workspace item. |
| `onelake_list-table-namespaces` | Lists table namespaces (schemas) exposed through the table API. |
| `onelake_get-table-namespace` | Retrieves metadata for a specific namespace. |
| `onelake_list-tables` | Lists tables published within a namespace. |
| `onelake_get-table` | Retrieves the definition for a specific table. |

### Core Fabric Operations

Expand Down
48 changes: 48 additions & 0 deletions tools/Fabric.Mcp.Tools.OneLake/src/Commands/BaseItemCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Extensions;
using Fabric.Mcp.Tools.OneLake.Options;
using Microsoft.Mcp.Core.Extensions;
using Microsoft.Mcp.Core.Models.Option;

namespace Fabric.Mcp.Tools.OneLake.Commands;

public abstract class BaseItemCommand<
[DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions>
: BaseWorkspaceCommand<TOptions> where TOptions : BaseItemOptions, new()
{
protected BaseItemCommand()
{
}

protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
command.Options.Add(FabricOptionDefinitions.ItemId.AsOptional());
command.Options.Add(FabricOptionDefinitions.Item.AsOptional());
command.Validators.Add(result =>
{
var itemId = result.GetValueOrDefault<string>(FabricOptionDefinitions.ItemId.Name);
var item = result.GetValueOrDefault<string>(FabricOptionDefinitions.Item.Name);

if (string.IsNullOrWhiteSpace(item) && string.IsNullOrWhiteSpace(itemId))
{
result.AddError("Item identifier is required. Provide --item or --item-id.");
}
});
}

protected override TOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
var itemId = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.ItemId.Name);
var item = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.Item.Name);
options.ItemId = !string.IsNullOrWhiteSpace(itemId)
? itemId!
: item ?? string.Empty;
return options;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Extensions;
using Fabric.Mcp.Tools.OneLake.Options;
using Microsoft.Mcp.Core.Extensions;
using Microsoft.Mcp.Core.Models.Option;

namespace Fabric.Mcp.Tools.OneLake.Commands;

public abstract class BaseWorkspaceCommand<
[DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions>
: GlobalCommand<TOptions> where TOptions : BaseWorkspaceOptions, new()
{
protected BaseWorkspaceCommand()
{
}

protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
command.Options.Add(FabricOptionDefinitions.WorkspaceId.AsOptional());
command.Options.Add(FabricOptionDefinitions.Workspace.AsOptional());
command.Validators.Add(result =>
{
var workspaceId = result.GetValueOrDefault<string>(FabricOptionDefinitions.WorkspaceId.Name);
var workspace = result.GetValueOrDefault<string>(FabricOptionDefinitions.Workspace.Name);

if (string.IsNullOrWhiteSpace(workspaceId) && string.IsNullOrWhiteSpace(workspace))
{
result.AddError("Workspace identifier is required. Provide --workspace or --workspace-id.");
}
});
}

protected override TOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
var workspaceId = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.WorkspaceId.Name);
var workspaceName = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.Workspace.Name);
options.WorkspaceId = !string.IsNullOrWhiteSpace(workspaceId)
? workspaceId!
: workspaceName ?? string.Empty;
return options;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@
// Licensed under the MIT License.

using System.Net;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Extensions;
using Azure.Mcp.Core.Models;
using Azure.Mcp.Core.Options;
using Fabric.Mcp.Tools.OneLake.Models;
using Fabric.Mcp.Tools.OneLake.Options;
using Fabric.Mcp.Tools.OneLake.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Mcp.Core.Commands;
using Microsoft.Mcp.Core.Extensions;
using Microsoft.Mcp.Core.Models.Option;

namespace Fabric.Mcp.Tools.OneLake.Commands.File;

[HiddenCommand]
public sealed class BlobDeleteCommand(
ILogger<BlobDeleteCommand> logger,
IOneLakeService oneLakeService) : GlobalCommand<BlobDeleteOptions>()
IOneLakeService oneLakeService) : BaseItemCommand<BlobDeleteOptions>()
{
private readonly ILogger<BlobDeleteCommand> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
private readonly IOneLakeService _oneLakeService = oneLakeService ?? throw new ArgumentNullException(nameof(oneLakeService));
Expand All @@ -42,46 +39,12 @@ public sealed class BlobDeleteCommand(
protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
command.Options.Add(FabricOptionDefinitions.WorkspaceId.AsOptional());
command.Options.Add(FabricOptionDefinitions.Workspace.AsOptional());
command.Options.Add(FabricOptionDefinitions.ItemId.AsOptional());
command.Options.Add(FabricOptionDefinitions.Item.AsOptional());
command.Options.Add(FabricOptionDefinitions.FilePath);
command.Validators.Add(result =>
{
var workspaceId = result.GetValueOrDefault<string>(FabricOptionDefinitions.WorkspaceId.Name);
var workspace = result.GetValueOrDefault<string>(FabricOptionDefinitions.Workspace.Name);
var itemId = result.GetValueOrDefault<string>(FabricOptionDefinitions.ItemId.Name);
var item = result.GetValueOrDefault<string>(FabricOptionDefinitions.Item.Name);

if (string.IsNullOrWhiteSpace(workspaceId) && string.IsNullOrWhiteSpace(workspace))
{
result.AddError("Workspace identifier is required. Provide --workspace or --workspace-id.");
}

if (string.IsNullOrWhiteSpace(item) && string.IsNullOrWhiteSpace(itemId))
{
result.AddError("Item identifier is required. Provide --item or --item-id.");
}
});
}

protected override BlobDeleteOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);

var workspaceId = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.WorkspaceId.Name);
var workspaceName = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.Workspace.Name);
options.WorkspaceId = !string.IsNullOrWhiteSpace(workspaceId)
? workspaceId!
: workspaceName ?? string.Empty;

var itemId = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.ItemId.Name);
var itemName = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.Item.Name);
options.ItemId = !string.IsNullOrWhiteSpace(itemId)
? itemId!
: itemName ?? string.Empty;

options.FilePath = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.FilePath.Name) ?? string.Empty;
return options;
}
Expand All @@ -97,8 +60,8 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
try
{
var result = await _oneLakeService.DeleteBlobAsync(
options.WorkspaceId,
options.ItemId,
options.WorkspaceId!,
options.ItemId!,
options.FilePath,
cancellationToken);

Expand Down Expand Up @@ -128,9 +91,7 @@ public sealed record BlobDeleteCommandResult(BlobDeleteResult Result, string Mes
};
}

public sealed class BlobDeleteOptions : GlobalOptions
public sealed class BlobDeleteOptions : BaseItemOptions
{
public string WorkspaceId { get; set; } = string.Empty;
public string ItemId { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,36 @@

using System.Net;
using System.Text;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Extensions;
using Azure.Mcp.Core.Options;
using Fabric.Mcp.Tools.OneLake.Models;
using Fabric.Mcp.Tools.OneLake.Options;
using Fabric.Mcp.Tools.OneLake.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Mcp.Core.Areas.Server.Options;
using Microsoft.Mcp.Core.Commands;
using Microsoft.Mcp.Core.Extensions;
using Microsoft.Mcp.Core.Models.Option;

namespace Fabric.Mcp.Tools.OneLake.Commands.File;

public sealed class BlobGetCommand(
ILogger<BlobGetCommand> logger,
IOneLakeService oneLakeService) : GlobalCommand<BlobGetOptions>()
IOneLakeService oneLakeService) : BaseItemCommand<BlobGetOptions>()
{
private readonly ILogger<BlobGetCommand> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
private readonly IOneLakeService _oneLakeService = oneLakeService ?? throw new ArgumentNullException(nameof(oneLakeService));

private const long InlineContentLimitBytes = 1 * 1024 * 1024; // 1 MiB inline payload limit

public override string Id => "75d6cb4c-4e81-4e69-a4ec-eca53a7dacd9";
public override string Name => "download_file";
public override string Name => "download-file";
public override string Title => "Download OneLake File";
public override string Description => "Downloads a file from OneLake storage. Use this when the user needs to retrieve file content or metadata. Returns base64 content, metadata, and text when applicable.";

public override ToolMetadata Metadata => new()
{
Destructive = false,
Idempotent = true,
LocalRequired = false,
LocalRequired = true,
OpenWorld = false,
ReadOnly = true,
Secret = false
Expand All @@ -43,47 +41,13 @@ public sealed class BlobGetCommand(
protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
command.Options.Add(FabricOptionDefinitions.WorkspaceId.AsOptional());
command.Options.Add(FabricOptionDefinitions.Workspace.AsOptional());
command.Options.Add(FabricOptionDefinitions.ItemId.AsOptional());
command.Options.Add(FabricOptionDefinitions.Item.AsOptional());
command.Options.Add(FabricOptionDefinitions.FilePath);
command.Options.Add(FabricOptionDefinitions.DownloadFilePath.AsOptional());
command.Validators.Add(result =>
{
var workspaceId = result.GetValueOrDefault<string>(FabricOptionDefinitions.WorkspaceId.Name);
var workspace = result.GetValueOrDefault<string>(FabricOptionDefinitions.Workspace.Name);
var itemId = result.GetValueOrDefault<string>(FabricOptionDefinitions.ItemId.Name);
var item = result.GetValueOrDefault<string>(FabricOptionDefinitions.Item.Name);

if (string.IsNullOrWhiteSpace(workspaceId) && string.IsNullOrWhiteSpace(workspace))
{
result.AddError("Workspace identifier is required. Provide --workspace or --workspace-id.");
}

if (string.IsNullOrWhiteSpace(item) && string.IsNullOrWhiteSpace(itemId))
{
result.AddError("Item identifier is required. Provide --item or --item-id.");
}
});
}

protected override BlobGetOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);

var workspaceId = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.WorkspaceId.Name);
var workspaceName = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.Workspace.Name);
options.WorkspaceId = !string.IsNullOrWhiteSpace(workspaceId)
? workspaceId!
: workspaceName ?? string.Empty;

var itemId = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.ItemId.Name);
var itemName = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.Item.Name);
options.ItemId = !string.IsNullOrWhiteSpace(itemId)
? itemId!
: itemName ?? string.Empty;

options.FilePath = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.FilePath.Name) ?? string.Empty;
options.DownloadFilePath = parseResult.GetValueOrDefault<string>(FabricOptionDefinitions.DownloadFilePath.Name);
return options;
Expand All @@ -99,18 +63,9 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
var options = BindOptions(parseResult);
try
{
var serviceStartOptions = context.GetService<IOptions<ServiceStartOptions>>();
var transport = serviceStartOptions.Value.Transport ?? "stdio";
var isLocalTransport = string.Equals(transport, "stdio", StringComparison.OrdinalIgnoreCase);

string? downloadPath = null;
if (!string.IsNullOrWhiteSpace(options.DownloadFilePath))
{
if (!isLocalTransport)
{
throw new ArgumentException("The --download-file-path option is only supported when the server runs with stdio transport.", nameof(options.DownloadFilePath));
}

var candidatePath = options.DownloadFilePath!;
downloadPath = Path.IsPathRooted(candidatePath)
? candidatePath
Expand All @@ -136,8 +91,8 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
};

var result = await _oneLakeService.GetBlobAsync(
options.WorkspaceId,
options.ItemId,
options.WorkspaceId!,
options.ItemId!,
options.FilePath,
downloadOptions,
cancellationToken);
Expand Down Expand Up @@ -186,10 +141,8 @@ public sealed record BlobGetCommandResult(BlobGetResult Blob, string Message);
};
}

public sealed class BlobGetOptions : GlobalOptions
public sealed class BlobGetOptions : BaseItemOptions
{
public string WorkspaceId { get; set; } = string.Empty;
public string ItemId { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public string? DownloadFilePath { get; set; }
}
Loading
Loading