Skip to content

Read default subscription from Azure CLI profile for all subscription-scoped commands#1974

Open
Copilot wants to merge 7 commits intomainfrom
copilot/fix-issue-1079
Open

Read default subscription from Azure CLI profile for all subscription-scoped commands#1974
Copilot wants to merge 7 commits intomainfrom
copilot/fix-issue-1079

Conversation

Copy link
Contributor

Copilot AI commented Mar 9, 2026

CommandHelper.GetSubscription() — used by every SubscriptionCommand<T>-derived command (EventHubs, AKS, Storage, Cosmos, etc.) — only fell back to AZURE_SUBSCRIPTION_ID env var when --subscription wasn't provided. It should read the default from ~/.azure/azureProfile.json (set via az account set), matching Azure CLI behavior.

Changes

  • New AzureCliProfileHelper in Microsoft.Mcp.Core — parses ~/.azure/azureProfile.json for the subscription with isDefault: true. Catches JsonException | IOException | UnauthorizedAccessException | SecurityException.
  • CommandHelper.GetDefaultSubscription() — new public method implementing the fallback chain: Azure CLI profile → AZURE_SUBSCRIPTION_ID env var
  • CommandHelper.GetSubscription() / HasSubscriptionAvailable() — now use GetDefaultSubscription() instead of only checking the env var
  • SubscriptionService.GetDefaultSubscriptionId() — delegates to CommandHelper.GetDefaultSubscription() to share the same resolution logic
  • SubscriptionListCommand — description updated to reference az account set; marks subscriptions with isDefault using the shared resolution

Fallback chain

--subscription option → ~/.azure/azureProfile.json → AZURE_SUBSCRIPTION_ID env var

All SubscriptionCommand<T> subclasses (60+ tool commands) inherit this behavior automatically through CommandHelper.

Invoking Livetests

Copilot submitted PRs are not trustworthy by default. Users with write access to the repo need to validate the contents of this PR before leaving a comment with the text /azp run mcp - pullrequest - live. This will trigger the necessary livetest workflows to complete required validation.


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI and others added 2 commits March 10, 2026 00:01
…cription to guide AI agent behavior

The subscription_list tool now:
- Returns a SubscriptionInfo model with isDefault field indicating the default subscription
  (based on AZURE_SUBSCRIPTION_ID environment variable)
- Sorts subscriptions so the default appears first in the list
- Updated description instructs AI agents to prefer the default subscription or ask the
  user when no default is set and multiple subscriptions exist

Fixes #1079

Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com>
Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix issue with mcp repository according to issue 1079 Add isDefault field to subscription_list response and guide AI subscription selection Mar 10, 2026
Copilot AI and others added 2 commits March 10, 2026 07:46
…cription detection

Read the default subscription from ~/.azure/azureProfile.json (set via 'az account set')
as the primary source. Fall back to AZURE_SUBSCRIPTION_ID environment variable only if
the profile is unavailable.

- Add GetDefaultSubscriptionId() to ISubscriptionService interface
- Implement ReadDefaultSubscriptionFromAzureProfile in SubscriptionService
- Update SubscriptionListCommand to use service method instead of env var directly
- Update tool description to reference 'az account set'
- Add 6 new profile-reading unit tests
- Update existing tests to use mocked GetDefaultSubscriptionId()

Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com>
Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com>
Copilot AI changed the title Add isDefault field to subscription_list response and guide AI subscription selection Read default subscription from Azure CLI profile instead of AZURE_SUBSCRIPTION_ID Mar 10, 2026
Copilot AI and others added 2 commits March 10, 2026 10:15
… into CommandHelper for all subscription-scoped commands

- Create AzureCliProfileHelper in Microsoft.Mcp.Core for reading default subscription from ~/.azure/azureProfile.json
- Update CommandHelper.GetSubscription() and HasSubscriptionAvailable() to use Azure CLI profile as primary fallback (before AZURE_SUBSCRIPTION_ID env var)
- Update SubscriptionService.GetDefaultSubscriptionId() to delegate to shared logic
- Update SubscriptionCommand comments to reflect new behavior
- Migrate SubscriptionServiceProfileTests to test AzureCliProfileHelper directly

Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com>
Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com>
Copilot AI changed the title Read default subscription from Azure CLI profile instead of AZURE_SUBSCRIPTION_ID Read default subscription from Azure CLI profile for all subscription-scoped commands Mar 10, 2026
@ArthurMa1978 ArthurMa1978 marked this pull request as ready for review March 11, 2026 03:28
@ArthurMa1978 ArthurMa1978 requested review from a team as code owners March 11, 2026 03:28
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates subscription resolution across subscription-scoped commands to match Azure CLI behavior by reading the default subscription from ~/.azure/azureProfile.json (set via az account set) before falling back to AZURE_SUBSCRIPTION_ID, and surfaces that default selection in subscription list.

Changes:

  • Added AzureCliProfileHelper and CommandHelper.GetDefaultSubscription() to implement the default-subscription fallback chain.
  • Updated subscription option validation/resolution to use the shared default-subscription logic.
  • Updated subscription list to return an isDefault indicator and prioritize the default subscription in the output.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
servers/Azure.Mcp.Server/docs/azmcp-commands.md Updates CLI docs for subscription list to mention isDefault.
core/Microsoft.Mcp.Core/src/Helpers/CommandHelper.cs Adds GetDefaultSubscription() and routes subscription resolution/validation through it.
core/Microsoft.Mcp.Core/src/Helpers/AzureCliProfileHelper.cs New helper to parse Azure CLI profile JSON for the default subscription.
core/Azure.Mcp.Core/src/Services/Azure/Subscription/ISubscriptionService.cs Adds GetDefaultSubscriptionId() to expose shared default-subscription resolution.
core/Azure.Mcp.Core/src/Services/Azure/Subscription/SubscriptionService.cs Implements GetDefaultSubscriptionId() via CommandHelper.
core/Azure.Mcp.Core/src/Commands/Subscription/SubscriptionCommand.cs Updates validation comments to include the new fallback sources.
core/Azure.Mcp.Core/src/Areas/Subscription/Models/SubscriptionInfo.cs Introduces a model that includes isDefault for subscription list output.
core/Azure.Mcp.Core/src/Areas/Subscription/Commands/SubscriptionListCommand.cs Maps subscriptions to SubscriptionInfo, marks/sorts the default subscription, and updates description.
core/Azure.Mcp.Core/src/Areas/Subscription/Commands/SubscriptionJsonContext.cs Adds source-gen serialization metadata for the new output model.
core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Subscription/SubscriptionListCommandTests.cs Extends unit tests to cover default subscription marking/sorting and isDefault output.
core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Subscription/AzureCliProfileHelperTests.cs Adds unit tests for parsing the default subscription from Azure CLI profile JSON.

Comment on lines 17 to +21
public static bool HasSubscriptionAvailable(CommandResult commandResult)
{
var hasOption = commandResult.HasOptionResult(OptionDefinitions.Common.Subscription.Name);
var hasEnv = !string.IsNullOrEmpty(EnvironmentHelpers.GetAzureSubscriptionId());
return hasOption || hasEnv;
var hasDefault = !string.IsNullOrEmpty(GetDefaultSubscription());
return hasOption || hasDefault;
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

HasSubscriptionAvailable always calls GetDefaultSubscription() even when --subscription is present, which can trigger an unnecessary read/parse of ~/.azure/azureProfile.json on every command validation. Consider short-circuiting (return true when the option exists) and only checking defaults when the option is absent (and optionally check the env var first to avoid disk IO).

Copilot uses AI. Check for mistakes.
Comment on lines 24 to 32
public static string? GetSubscription(ParseResult parseResult)
{
// Get subscription from command line option or fallback to environment variable
// Get subscription from command line option or fallback to default subscription
var subscriptionValue = parseResult.GetValueOrDefault<string>(OptionDefinitions.Common.Subscription.Name);

var envSubscription = EnvironmentHelpers.GetAzureSubscriptionId();
return (string.IsNullOrEmpty(subscriptionValue) || IsPlaceholder(subscriptionValue)) && !string.IsNullOrEmpty(envSubscription)
? envSubscription
var defaultSubscription = GetDefaultSubscription();
return (string.IsNullOrEmpty(subscriptionValue) || IsPlaceholder(subscriptionValue)) && !string.IsNullOrEmpty(defaultSubscription)
? defaultSubscription
: subscriptionValue;
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

GetSubscription reads the default subscription unconditionally, even when a non-placeholder --subscription value is provided. To avoid unnecessary file IO/parsing, consider only calling GetDefaultSubscription() when subscriptionValue is null/empty or matches the placeholder check, and otherwise return the provided value directly.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +71
internal static string GetAzureProfilePath()
{
var azureDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".azure");
return Path.Combine(azureDir, "azureProfile.json");
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

GetAzureProfilePath() uses Environment.SpecialFolder.UserProfile; if that resolves to an empty string in some hosting environments, Path.Combine("", ".azure") yields a relative path and may accidentally read a local ./.azure/azureProfile.json. Consider guarding for an empty/unknown user profile and returning null/empty (and having GetDefaultSubscriptionId treat that as unavailable) to avoid unintended file reads.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +28
"The isDefault field indicates the user's default subscription as configured via 'az account set' in the Azure CLI profile. " +
"When the user has not specified a subscription, prefer the subscription where isDefault is true. " +
"If no default is set and multiple subscriptions exist, ask the user which subscription to use.";
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The updated Description says isDefault reflects only the Azure CLI profile default set via az account set, but the implementation marks isDefault based on GetDefaultSubscriptionId() which can also come from AZURE_SUBSCRIPTION_ID. Consider updating the description to mention the env var fallback as well so callers interpret isDefault correctly.

Suggested change
"The isDefault field indicates the user's default subscription as configured via 'az account set' in the Azure CLI profile. " +
"When the user has not specified a subscription, prefer the subscription where isDefault is true. " +
"If no default is set and multiple subscriptions exist, ask the user which subscription to use.";
"The isDefault field indicates the user's default subscription as resolved from the Azure CLI profile (configured via 'az account set') or, if not set there, from the AZURE_SUBSCRIPTION_ID environment variable. " +
"When the user has not specified a subscription, prefer the subscription where isDefault is true. " +
"If no default can be determined from either source and multiple subscriptions exist, ask the user which subscription to use.";

Copilot uses AI. Check for mistakes.
Comment on lines +56 to 61
var defaultSubscriptionId = subscriptionService.GetDefaultSubscriptionId();
var subscriptionInfos = MapToSubscriptionInfos(subscriptions, defaultSubscriptionId);

context.Response.Results = ResponseResult.Create(
new SubscriptionListCommandResult(subscriptions),
new SubscriptionListCommandResult(subscriptionInfos),
SubscriptionJsonContext.Default.SubscriptionListCommandResult);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Switching the result payload from List<SubscriptionData> to List<SubscriptionInfo> changes the JSON contract for subscription list (it will no longer serialize any extra fields present on SubscriptionData). If existing clients might be consuming those additional fields, this is a breaking change; consider either wrapping SubscriptionData and adding an isDefault field, or explicitly documenting/changelogging the narrower output contract.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

3 participants