Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b2903b0
Add the option to start Azure MCP server with a list of specific tools
fanyang-mono Oct 2, 2025
800115f
Fix format
fanyang-mono Oct 3, 2025
d81a602
Merge remote-tracking branch 'origin/main' into tool_mode
fanyang-mono Oct 3, 2025
1f7e9a7
Fix tests with new syntax
fanyang-mono Oct 3, 2025
c034ec6
Update CHANGELOG
fanyang-mono Oct 3, 2025
97d08c7
Make namespace mode and registry based tool loader honor --tool swith…
fanyang-mono Oct 6, 2025
fefa503
Update all docs
fanyang-mono Oct 6, 2025
1236b5b
Merge remote-tracking branch 'origin/main' into tool_mode
fanyang-mono Oct 6, 2025
74a92db
Update core/Azure.Mcp.Core/src/Areas/Server/Options/ServiceOptionDefi…
fanyang-mono Oct 7, 2025
f651051
Update docs/azmcp-commands.md
fanyang-mono Oct 7, 2025
4efa111
Fix format
fanyang-mono Oct 7, 2025
46d2cab
Merge remote-tracking branch 'origin/main' into tool_mode
fanyang-mono Oct 7, 2025
4004c2b
Fix format
fanyang-mono Oct 7, 2025
2bde267
Address review feedbacks
fanyang-mono Oct 7, 2025
670a227
When --tool is used, It automatically switches to `all` mode.
fanyang-mono Oct 7, 2025
d623d6f
Update doc to provide clear explanation on --tool behavior
fanyang-mono Oct 7, 2025
0ba9856
Check for substring match instead of exact string match
fanyang-mono Oct 8, 2025
61a6729
Merge remote-tracking branch 'origin/main' into tool_mode
fanyang-mono Oct 8, 2025
8d0a2b7
Fix format
fanyang-mono Oct 8, 2025
f552dd9
Update area name
fanyang-mono Oct 8, 2025
2a2af8d
Disallow --namespace and --tool options being used together
fanyang-mono Oct 8, 2025
2d37dc5
Merge remote-tracking branch 'origin/main' into tool_mode
fanyang-mono Oct 8, 2025
c0373c3
Clean up
fanyang-mono Oct 8, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public static IServiceCollection AddAzureMcpServer(this IServiceCollection servi
Namespace = serviceStartOptions.Namespace,
ReadOnly = serviceStartOptions.ReadOnly ?? false,
InsecureDisableElicitation = serviceStartOptions.InsecureDisableElicitation,
Tool = serviceStartOptions.Tool,
};

if (serviceStartOptions.Mode == ModeTypes.NamespaceProxy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ protected override void RegisterOptions(Command command)
command.Options.Add(ServiceOptionDefinitions.Transport);
command.Options.Add(ServiceOptionDefinitions.Namespace);
command.Options.Add(ServiceOptionDefinitions.Mode);
command.Options.Add(ServiceOptionDefinitions.Tool);
command.Options.Add(ServiceOptionDefinitions.ReadOnly);
command.Options.Add(ServiceOptionDefinitions.Debug);
command.Options.Add(ServiceOptionDefinitions.EnableInsecureTransports);
Expand All @@ -82,6 +83,7 @@ protected override ServiceStartOptions BindOptions(ParseResult parseResult)
Transport = parseResult.GetValueOrDefault<string>(ServiceOptionDefinitions.Transport.Name) ?? TransportTypes.StdIo,
Namespace = parseResult.GetValueOrDefault<string[]?>(ServiceOptionDefinitions.Namespace.Name),
Mode = parseResult.GetValueOrDefault<string?>(ServiceOptionDefinitions.Mode.Name),
Tool = parseResult.GetValueOrDefault<string[]?>(ServiceOptionDefinitions.Tool.Name),
ReadOnly = parseResult.GetValueOrDefault<bool?>(ServiceOptionDefinitions.ReadOnly.Name),
Debug = parseResult.GetValueOrDefault<bool>(ServiceOptionDefinitions.Debug.Name),
EnableInsecureTransports = parseResult.GetValueOrDefault<bool>(ServiceOptionDefinitions.EnableInsecureTransports.Name),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,15 @@ private static bool IsRawMcpToolInputOption(Option option)
/// <returns>A result containing the list of available tools.</returns>
public ValueTask<ListToolsResult> ListToolsHandler(RequestContext<ListToolsRequestParams> request, CancellationToken cancellationToken)
{
var tools = CommandFactory.GetVisibleCommands(_toolCommands)
var visibleCommands = CommandFactory.GetVisibleCommands(_toolCommands);

// Filter by specific tools if provided
if (_options.Value.Tool != null && _options.Value.Tool.Length > 0)
{
visibleCommands = visibleCommands.Where(kvp => _options.Value.Tool.Contains(kvp.Key, StringComparer.OrdinalIgnoreCase));
}

var tools = visibleCommands
.Select(kvp => GetTool(kvp.Key, kvp.Value))
.Where(tool => !_options.Value.ReadOnly || (tool.Annotations?.ReadOnlyHint == true))
.ToList();
Expand Down Expand Up @@ -96,6 +104,25 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
}

var toolName = request.Params.Name;

// Check if tool filtering is enabled and validate the requested tool
if (_options.Value.Tool != null && _options.Value.Tool.Length > 0)
{
if (!_options.Value.Tool.Contains(toolName, StringComparer.OrdinalIgnoreCase))
{
var content = new TextContentBlock
{
Text = $"Tool '{toolName}' is not available. This server is configured to only expose the tools: {string.Join(", ", _options.Value.Tool.Select(t => $"'{t}'"))}",
};

return new CallToolResult
{
Content = [content],
IsError = true,
};
}
}

var command = _toolCommands.GetValueOrDefault(toolName);
if (command == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ namespace Azure.Mcp.Core.Areas.Server.Commands.ToolLoading;
/// <param name="Namespace">The namespaces to filter commands by. If null or empty, all commands will be included.</param>
/// <param name="ReadOnly">Whether the tool loader should operate in read-only mode. When true, only tools marked as read-only will be exposed.</param>
/// <param name="InsecureDisableElicitation">Whether elicitation is disabled (insecure mode). When true, elicitation will always be treated as accepted.</param>
public sealed record ToolLoaderOptions(string[]? Namespace = null, bool ReadOnly = false, bool InsecureDisableElicitation = false);
/// <param name="Tool">The specific tool names to filter by. When specified, only these tools will be exposed.</param>
public sealed record ToolLoaderOptions(string[]? Namespace = null, bool ReadOnly = false, bool InsecureDisableElicitation = false, string[]? Tool = null);
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public static class ServiceOptionDefinitions
public const string TransportName = "transport";
public const string NamespaceName = "namespace";
public const string ModeName = "mode";
public const string ToolName = "tool";
public const string ReadOnlyName = "read-only";
public const string DebugName = "debug";
public const string EnableInsecureTransportsName = "enable-insecure-transports";
Expand Down Expand Up @@ -41,6 +42,17 @@ public static class ServiceOptionDefinitions
DefaultValueFactory = _ => (string?)ModeTypes.NamespaceProxy
};

public static readonly Option<string[]?> Tool = new Option<string[]?>(
$"--{ToolName}"
)
{
Description = "Filter to expose only specific tools by name (e.g., 'azmcp storage account list'). Can be specified multiple times.",
Required = false,
Arity = ArgumentArity.OneOrMore,
AllowMultipleArgumentsPerToken = true,
DefaultValueFactory = _ => null
};

public static readonly Option<bool?> ReadOnly = new(
$"--{ReadOnlyName}")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ public class ServiceStartOptions
[JsonPropertyName("mode")]
public string? Mode { get; set; } = ModeTypes.NamespaceProxy;

/// <summary>
/// Gets or sets the specific tool names to expose.
/// When specified, only these tools will be available.
/// </summary>
[JsonPropertyName("tool")]
public string[]? Tool { get; set; } = null;

/// <summary>
/// Gets or sets whether the server should operate in read-only mode.
/// When true, only tools marked as read-only will be available.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,46 @@

using System.Diagnostics;
using Azure.Mcp.Core.Areas;
using Azure.Mcp.Core.Areas.Group;
using Azure.Mcp.Core.Areas.Server;
using Azure.Mcp.Core.Areas.Subscription;
using Azure.Mcp.Core.Areas.Tools;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Services.Telemetry;
using Azure.Mcp.Tools.Acr;
using Azure.Mcp.Tools.Aks;
using Azure.Mcp.Tools.AppConfig;
using Azure.Mcp.Tools.AppLens;
using Azure.Mcp.Tools.Authorization;
using Azure.Mcp.Tools.AzureBestPractices;
using Azure.Mcp.Tools.AzureIsv;
using Azure.Mcp.Tools.AzureManagedLustre;
using Azure.Mcp.Tools.AzureTerraformBestPractices;
using Azure.Mcp.Tools.BicepSchema;
using Azure.Mcp.Tools.CloudArchitect;
using Azure.Mcp.Tools.Cosmos;
using Azure.Mcp.Tools.Deploy;
using Azure.Mcp.Tools.EventGrid;
using Azure.Mcp.Tools.Extension;
using Azure.Mcp.Tools.Foundry;
using Azure.Mcp.Tools.FunctionApp;
using Azure.Mcp.Tools.Grafana;
using Azure.Mcp.Tools.KeyVault;
using Azure.Mcp.Tools.Kusto;
using Azure.Mcp.Tools.LoadTesting;
using Azure.Mcp.Tools.Marketplace;
using Azure.Mcp.Tools.Monitor;
using Azure.Mcp.Tools.MySql;
using Azure.Mcp.Tools.Postgres;
using Azure.Mcp.Tools.Quota;
using Azure.Mcp.Tools.Redis;
using Azure.Mcp.Tools.ResourceHealth;
using Azure.Mcp.Tools.Search;
using Azure.Mcp.Tools.ServiceBus;
using Azure.Mcp.Tools.Sql;
using Azure.Mcp.Tools.Storage;
using Azure.Mcp.Tools.VirtualDesktop;
using Azure.Mcp.Tools.Workbooks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
Expand All @@ -21,11 +54,45 @@ internal class CommandFactoryHelpers
public static CommandFactory CreateCommandFactory(IServiceProvider? serviceProvider = default)
{
IAreaSetup[] areaSetups = [
// Core areas
new SubscriptionSetup(),
new GroupSetup(),

// Tool areas
new AcrSetup(),
new AksSetup(),
new AppConfigSetup(),
new AppLensSetup(),
new AuthorizationSetup(),
new AzureBestPracticesSetup(),
new AzureIsvSetup(),
new AzureManagedLustreSetup(),
new AzureTerraformBestPracticesSetup(),
new BicepSchemaSetup(),
new CloudArchitectSetup(),
new CosmosSetup(),
new DeploySetup(),
new EventGridSetup(),
new ExtensionSetup(),
new FoundrySetup(),
new FunctionAppSetup(),
new GrafanaSetup(),
new KeyVaultSetup(),
new KustoSetup(),
new LoadTestingSetup(),
new MarketplaceSetup(),
new MonitorSetup(),
new MySqlSetup(),
new PostgresSetup(),
new QuotaSetup(),
new RedisSetup(),
new ResourceHealthSetup(),
new SearchSetup(),
new ServiceBusSetup(),
new SqlSetup(),
new StorageSetup(),
new DeploySetup(),
new AppConfigSetup()
new VirtualDesktopSetup(),
new WorkbooksSetup(),
];

var services = serviceProvider ?? CreateDefaultServiceProvider();
Expand All @@ -44,11 +111,45 @@ public static IServiceProvider CreateDefaultServiceProvider()
public static IServiceCollection SetupCommonServices()
{
IAreaSetup[] areaSetups = [
// Core areas
new SubscriptionSetup(),
new GroupSetup(),

// Tool areas
new AcrSetup(),
new AksSetup(),
new AppConfigSetup(),
new AppLensSetup(),
new AuthorizationSetup(),
new AzureBestPracticesSetup(),
new AzureIsvSetup(),
new AzureManagedLustreSetup(),
new AzureTerraformBestPracticesSetup(),
new BicepSchemaSetup(),
new CloudArchitectSetup(),
new CosmosSetup(),
new DeploySetup(),
new EventGridSetup(),
new ExtensionSetup(),
new FoundrySetup(),
new FunctionAppSetup(),
new GrafanaSetup(),
new KeyVaultSetup(),
new KustoSetup(),
new LoadTestingSetup(),
new MarketplaceSetup(),
new MonitorSetup(),
new MySqlSetup(),
new PostgresSetup(),
new QuotaSetup(),
new RedisSetup(),
new ResourceHealthSetup(),
new SearchSetup(),
new ServiceBusSetup(),
new SqlSetup(),
new StorageSetup(),
new DeploySetup(),
new AppConfigSetup()
new VirtualDesktopSetup(),
new WorkbooksSetup(),
];

var builder = new ServiceCollection()
Expand Down
Loading
Loading