Repository: Azure/azure-mcp (2025-07-08T17:22:38Z)

Issue:
Support optional AZURE_SUBSCRIPTION_ID env var for default subscription
Currently, every MCP service call requires explicitly specifying the subscription ID in the query. For users working within a single subscription, this becomes repetitive. It would be helpful to allow an environment variable (e.g. `AZURE_SUBSCRIPTION_ID`) so that if it’s set, the subscription ID is auto-injected when not provided in the request.

**Proposal**

- Read `AZURE_SUBSCRIPTION_ID` from the environment (e.g., via `.env` or process env).
- When handling a tool call or service request, if the subscription ID parameter is missing and `AZURE_SUBSCRIPTION_ID` is set, use the env var value.
- If neither a subscription ID in the request nor the env var is available, fall back to the existing behavior (error indicating subscription ID is required).

**Benefits**
- Reduces boilerplate for users who always operate against the same subscription.
- Simplifies samples and quick tests, since users can define their subscription once.
- Keeps backward compatibility: explicit subscription IDs still work, and omission only succeeds if the env var is defined.


Discussion:
We could also inspect azd or az to find the already assigned default sub
Great suggestion @eosho - thank you for filing this github issue.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b42bfc3eb..4f6a1716b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ The Azure MCP Server updates automatically by default whenever a new release com
 
 ### Features Added
 
+- Read `AZURE_SUBSCRIPTION_ID` from the environment if the subscription is not provided. [[#533](https://github.com/Azure/azure-mcp/pull/533)]
+
 ### Breaking Changes
 
 ### Bugs Fixed
diff --git a/core/src/AzureMcp.Core/Commands/CommandExtensions.cs b/core/src/AzureMcp.Core/Commands/CommandExtensions.cs
index 0f8721cc0..f468ecf2d 100644
--- a/core/src/AzureMcp.Core/Commands/CommandExtensions.cs
+++ b/core/src/AzureMcp.Core/Commands/CommandExtensions.cs
@@ -46,7 +46,7 @@ public static ParseResult ParseFromDictionary(this Command command, IReadOnlyDic
                 _ => value.GetRawText()
             };
 
-            if (!string.IsNullOrEmpty(strValue))
+            if (strValue != null)
             {
                 args.Add(strValue);
             }
diff --git a/core/src/AzureMcp.Core/Commands/Subscription/SubscriptionCommand.cs b/core/src/AzureMcp.Core/Commands/Subscription/SubscriptionCommand.cs
index 6915dcceb..7cbdb6be8 100644
--- a/core/src/AzureMcp.Core/Commands/Subscription/SubscriptionCommand.cs
+++ b/core/src/AzureMcp.Core/Commands/Subscription/SubscriptionCommand.cs
@@ -18,12 +18,37 @@ protected override void RegisterOptions(Command command)
     {
         base.RegisterOptions(command);
         command.AddOption(_subscriptionOption);
+
+        command.AddValidator(result =>
+        {
+            var subscriptionValue = result.GetValueForOption(_subscriptionOption);
+            var envSubscription = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID");
+
+            // Check if both subscription option and environment variable are missing or invalid
+            var hasValidSubscription = !string.IsNullOrEmpty(subscriptionValue);
+
+            var hasValidEnvVar = !string.IsNullOrEmpty(envSubscription);
+
+            if (!hasValidSubscription && !hasValidEnvVar)
+            {
+                result.ErrorMessage = "Missing Required options: --subscription";
+            }
+        });
     }
 
     protected override TOptions BindOptions(ParseResult parseResult)
     {
         var options = base.BindOptions(parseResult);
-        options.Subscription = parseResult.GetValueForOption(_subscriptionOption);
+
+        // Get subscription from command line option or fallback to environment variable
+        var subscriptionValue = parseResult.GetValueForOption(_subscriptionOption);
+        options.Subscription = (string.IsNullOrEmpty(subscriptionValue)
+            || subscriptionValue.Contains("subscription")
+            || subscriptionValue.Contains("default"))
+            && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID"))
+            ? Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID")
+            : subscriptionValue;
+
         return options;
     }
 }
diff --git a/core/src/AzureMcp.Core/Models/Option/OptionDefinitions.cs b/core/src/AzureMcp.Core/Models/Option/OptionDefinitions.cs
index 009ff0c7c..c907133fb 100644
--- a/core/src/AzureMcp.Core/Models/Option/OptionDefinitions.cs
+++ b/core/src/AzureMcp.Core/Models/Option/OptionDefinitions.cs
@@ -26,10 +26,11 @@ public static class Common
 
         public static readonly Option<string> Subscription = new(
             $"--{SubscriptionName}",
-            "The Azure subscription ID or name. This can be either the GUID identifier or the display name of the Azure subscription to use."
+    "Specifies the Azure subscription to use. Accepts either a subscription ID (GUID) or display name. " +
+    "If not specified, the AZURE_SUBSCRIPTION_ID environment variable will be used instead."
         )
         {
-            IsRequired = true
+            IsRequired = false
         };
 
         public static readonly Option<AuthMethod> AuthMethod = new(
diff --git a/docs/azmcp-commands.md b/docs/azmcp-commands.md
index 71f58b055..9d2ba10d0 100644
--- a/docs/azmcp-commands.md
+++ b/docs/azmcp-commands.md
@@ -9,7 +9,7 @@ The following options are available for all commands:
 
 | Option | Required | Default | Description |
 |-----------|----------|---------|-------------|
-| `--subscription` | Yes | - | Azure subscription ID for target resources |
+| `--subscription` | No | Environment variable `AZURE_SUBSCRIPTION_ID` | Azure subscription ID for target resources |
 | `--tenant-id` | No | - | Azure tenant ID for authentication |
 | `--auth-method` | No | 'credential' | Authentication method ('credential', 'key', 'connectionString') |
 | `--retry-max-retries` | No | 3 | Maximum retry attempts for failed operations |


Test patch:
diff --git a/areas/monitor/tests/AzureMcp.Monitor.UnitTests/Metrics/MetricsQueryCommandTests.cs b/areas/monitor/tests/AzureMcp.Monitor.UnitTests/Metrics/MetricsQueryCommandTests.cs
index b4c2873a7..4244682e1 100644
--- a/areas/monitor/tests/AzureMcp.Monitor.UnitTests/Metrics/MetricsQueryCommandTests.cs
+++ b/areas/monitor/tests/AzureMcp.Monitor.UnitTests/Metrics/MetricsQueryCommandTests.cs
@@ -98,7 +98,6 @@ public void RegisterOptions_AddsAllExpectedOptions()
         var options = command.Options.Select(o => o.Name).ToList();
 
         // Base options from BaseMetricsCommand
-        Assert.Contains("subscription", options);
         Assert.Contains("resource-group", options);
         Assert.Contains("resource-type", options);
         Assert.Contains("resource-name", options);
@@ -115,7 +114,6 @@ public void RegisterOptions_AddsAllExpectedOptions()
 
         // Verify required options are marked as required
         var requiredOptions = command.Options.Where(o => o.IsRequired).Select(o => o.Name).ToList();
-        Assert.Contains("subscription", requiredOptions);
         Assert.Contains("resource-name", requiredOptions);
         Assert.Contains("metric-names", requiredOptions);
     }
diff --git a/areas/workbooks/tests/AzureMcp.Workbooks.UnitTests/ListWorkbooksCommandTests.cs b/areas/workbooks/tests/AzureMcp.Workbooks.UnitTests/ListWorkbooksCommandTests.cs
index 213d7dbb2..f4049f6fa 100644
--- a/areas/workbooks/tests/AzureMcp.Workbooks.UnitTests/ListWorkbooksCommandTests.cs
+++ b/areas/workbooks/tests/AzureMcp.Workbooks.UnitTests/ListWorkbooksCommandTests.cs
@@ -331,27 +331,6 @@ await _service.Received(1).ListWorkbooks(
             Arg.Any<string?>());
     }
 
-    [Theory]
-    [InlineData("")]
-    [InlineData("   ")]
-    public async Task ExecuteAsync_WithInvalidSubscription_ReturnsValidationError(string invalidSubscription)
-    {
-        // Arrange
-        var args = _command.GetCommand().Parse([
-            "--subscription", invalidSubscription,
-            "--resource-group", "valid-rg"
-        ]);
-
-        var context = new CommandContext(_serviceProvider);
-
-        // Act
-        var response = await _command.ExecuteAsync(context, args);
-
-        // Assert
-        Assert.Equal(400, response.Status);
-        Assert.Contains("subscription", response.Message, StringComparison.OrdinalIgnoreCase);
-    }
-
     [Theory]
     [InlineData("")]
     [InlineData("   ")]
diff --git a/core/tests/AzureMcp.Core.UnitTests/Areas/Group/UnitTests/GroupListCommandTests.cs b/core/tests/AzureMcp.Core.UnitTests/Areas/Group/UnitTests/GroupListCommandTests.cs
index 62a4953df..cd7741fe2 100644
--- a/core/tests/AzureMcp.Core.UnitTests/Areas/Group/UnitTests/GroupListCommandTests.cs
+++ b/core/tests/AzureMcp.Core.UnitTests/Areas/Group/UnitTests/GroupListCommandTests.cs
@@ -48,7 +48,7 @@ public GroupListCommandTests()
     public async Task ExecuteAsync_WithValidSubscription_ReturnsResourceGroups()
     {
         // Arrange
-        var subscriptionId = "test-subscription-id";
+        var subscriptionId = "test-subs-id";
         var expectedGroups = new List<ResourceGroupInfo>
         {
             ResourceGroupTestHelpers.CreateResourceGroupInfo("rg1", subscriptionId, "East US"),
@@ -78,11 +78,11 @@ public async Task ExecuteAsync_WithValidSubscription_ReturnsResourceGroups()
         var second = groupsArray[1];
 
         Assert.Equal("rg1", first.GetProperty("name").GetString());
-        Assert.Equal("/subscriptions/test-subscription-id/resourceGroups/rg1", first.GetProperty("id").GetString());
+        Assert.Equal("/subscriptions/test-subs-id/resourceGroups/rg1", first.GetProperty("id").GetString());
         Assert.Equal("East US", first.GetProperty("location").GetString());
 
         Assert.Equal("rg2", second.GetProperty("name").GetString());
-        Assert.Equal("/subscriptions/test-subscription-id/resourceGroups/rg2", second.GetProperty("id").GetString());
+        Assert.Equal("/subscriptions/test-subs-id/resourceGroups/rg2", second.GetProperty("id").GetString());
         Assert.Equal("West US", second.GetProperty("location").GetString());
 
         await _resourceGroupService.Received(1).GetResourceGroups(
@@ -95,7 +95,7 @@ await _resourceGroupService.Received(1).GetResourceGroups(
     public async Task ExecuteAsync_WithTenant_PassesTenantToService()
     {
         // Arrange
-        var subscriptionId = "test-subscription-id";
+        var subscriptionId = "test-subs-id";
         var tenantId = "test-tenant-id";
         var expectedGroups = new List<ResourceGroupInfo>
         {
@@ -127,7 +127,7 @@ await _resourceGroupService.Received(1).GetResourceGroups(
     public async Task ExecuteAsync_EmptyResourceGroupList_ReturnsNullResults()
     {
         // Arrange
-        var subscriptionId = "test-subscription-id";
+        var subscriptionId = "test-subs-id";
         _resourceGroupService
             .GetResourceGroups(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<RetryPolicyOptions>())
             .Returns([]);
@@ -147,7 +147,7 @@ public async Task ExecuteAsync_EmptyResourceGroupList_ReturnsNullResults()
     public async Task ExecuteAsync_ServiceThrowsException_ReturnsErrorInResponse()
     {
         // Arrange
-        var subscriptionId = "test-subscription-id";
+        var subscriptionId = "test-subs-id";
         var expectedError = "Test error message";
         _resourceGroupService
             .GetResourceGroups(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<RetryPolicyOptions>())
@@ -163,21 +163,4 @@ public async Task ExecuteAsync_ServiceThrowsException_ReturnsErrorInResponse()
         Assert.Equal(500, result.Status);
         Assert.Contains(expectedError, result.Message);
     }
-
-    [Fact]
-    public async Task ExecuteAsync_MissingSubscription_FailsValidation()
-    {
-        // Arrange
-        var args = _parser.Parse(""); // No subscription provided
-
-        // Act
-        var result = await _command.ExecuteAsync(_context, args);
-
-        // Assert
-        Assert.NotNull(result);
-        Assert.Equal(400, result.Status);
-        Assert.Contains("required", result.Message.ToLower());
-        // Verify service was not called when validation fails
-        await _resourceGroupService.DidNotReceive().GetResourceGroups(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<RetryPolicyOptions>());
-    }
 }
diff --git a/core/tests/AzureMcp.Core.UnitTests/Areas/Subscription/SubscriptionCommandTests.cs b/core/tests/AzureMcp.Core.UnitTests/Areas/Subscription/SubscriptionCommandTests.cs
new file mode 100644
index 000000000..d384e4cb3
--- /dev/null
+++ b/core/tests/AzureMcp.Core.UnitTests/Areas/Subscription/SubscriptionCommandTests.cs
@@ -0,0 +1,124 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.CommandLine.Parsing;
+using AzureMcp.Core.Models.Command;
+using AzureMcp.Core.Options;
+using AzureMcp.Storage.Commands.Account;
+using AzureMcp.Storage.Services;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using NSubstitute;
+using Xunit;
+
+namespace AzureMcp.Tests.Commands.Subscription;
+
+[Trait("Area", "Core")]
+public class SubscriptionCommandTests
+{
+    private readonly IServiceProvider _serviceProvider;
+    private readonly IStorageService _storageService;
+    private readonly ILogger<AccountListCommand> _logger;
+    private readonly AccountListCommand _command;
+    private readonly CommandContext _context;
+    private readonly Parser _parser;
+
+    public SubscriptionCommandTests()
+    {
+        _storageService = Substitute.For<IStorageService>();
+        _logger = Substitute.For<ILogger<AccountListCommand>>();
+
+        var collection = new ServiceCollection().AddSingleton(_storageService);
+
+        _serviceProvider = collection.BuildServiceProvider();
+        _command = new(_logger);
+        _context = new(_serviceProvider);
+        _parser = new(_command.GetCommand());
+    }
+
+    [Fact]
+    public void Validate_WithEnvironmentVariableOnly_PassesValidation()
+    {
+        // Arrange
+        var originalValue = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID");
+        Environment.SetEnvironmentVariable("AZURE_SUBSCRIPTION_ID", "env-subs");
+
+        try
+        {
+            var parseResult = _parser.Parse([]);
+
+            // Act & Assert
+            Assert.Empty(parseResult.Errors);
+        }
+        finally
+        {
+            // Cleanup
+            Environment.SetEnvironmentVariable("AZURE_SUBSCRIPTION_ID", originalValue);
+        }
+    }
+
+    [Fact]
+    public async Task ExecuteAsync_WithEnvironmentVariableOnly_CallsServiceWithCorrectSubscription()
+    {
+        // Arrange
+        var originalValue = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID");
+        Environment.SetEnvironmentVariable("AZURE_SUBSCRIPTION_ID", "env-subs");
+
+        try
+        {
+            var expectedAccounts = new List<string> { "account1", "account2" };
+
+            _storageService.GetStorageAccounts(Arg.Is("env-subs"), Arg.Any<string>(), Arg.Any<RetryPolicyOptions>())
+                .Returns(expectedAccounts);
+
+            var parseResult = _parser.Parse([]);
+
+            // Act
+            var response = await _command.ExecuteAsync(_context, parseResult);
+
+            // Assert
+            Assert.NotNull(response);
+
+            // Verify the service was called with the environment variable subscription
+            _ = _storageService.Received(1).GetStorageAccounts("env-subs", Arg.Any<string>(), Arg.Any<RetryPolicyOptions>());
+        }
+        finally
+        {
+            // Cleanup
+            Environment.SetEnvironmentVariable("AZURE_SUBSCRIPTION_ID", originalValue);
+        }
+    }
+
+    [Fact]
+    public async Task ExecuteAsync_WithBothOptionAndEnvironmentVariable_PrefersOption()
+    {
+        // Arrange
+        var originalValue = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID");
+        Environment.SetEnvironmentVariable("AZURE_SUBSCRIPTION_ID", "env-subs");
+
+        try
+        {
+            var expectedAccounts = new List<string> { "account1", "account2" };
+
+            _storageService.GetStorageAccounts(Arg.Is("option-subs"), Arg.Any<string>(), Arg.Any<RetryPolicyOptions>())
+                .Returns(expectedAccounts);
+
+            var parseResult = _parser.Parse(["--subscription", "option-subs"]);
+
+            // Act
+            var response = await _command.ExecuteAsync(_context, parseResult);
+
+            // Assert
+            Assert.NotNull(response);
+
+            // Verify the service was called with the option subscription, not the environment variable
+            _ = _storageService.Received(1).GetStorageAccounts("option-subs", Arg.Any<string>(), Arg.Any<RetryPolicyOptions>());
+            _ = _storageService.DidNotReceive().GetStorageAccounts("env-subs", Arg.Any<string>(), Arg.Any<RetryPolicyOptions>());
+        }
+        finally
+        {
+            // Cleanup
+            Environment.SetEnvironmentVariable("AZURE_SUBSCRIPTION_ID", originalValue);
+        }
+    }
+}