Add CostManagement query tool#2603
Conversation
Adds a new Azure.Mcp.Tools.CostManagement toolset with the first command 'azmcp costmanagement query run' that retrieves actual Azure costs and usage from the Azure Cost Management Query/Usage API for a subscription or resource group. Features: - Predefined timeframes (MonthToDate, BillingMonthToDate, TheCurrentMonth, TheLastBillingMonth, WeekToDate) and Custom range with --from/--to - Optional Daily/Monthly granularity - Grouping by a single Azure dimension (ServiceName, ResourceGroupName, ResourceLocation, ResourceId, MeterCategory, MeterSubCategory, ChargeType, BillingPeriod) - Backed by Azure.ResourceManager.CostManagement 1.0.3 (GA) Includes: - 30 unit tests (10 command + 20 service helpers) - LiveTests project scaffolding (assets.json with empty Tag for maintainer to populate at merge) - Full CONTRIBUTING.md compliance: AOT-compatible, internal record, GetStatusCode override, JsonSerializerContext, sealed setup, etc. Closes the gap tracked in microsoft#1420 (FinOps recommenders) and supersedes the abandoned PR microsoft#447 (CostManagement Tool File Structure).
|
Thank you for your contribution @francesco1501! We will review the pull request and get back to you soon. |
There was a problem hiding this comment.
Pull request overview
This PR adds a new Azure.Mcp.Tools.CostManagement toolset to Azure MCP Server, introducing the azmcp costmanagement query run command to query actual Azure costs/usage via the Azure Cost Management Query/Usage API at subscription scope (optionally narrowed to a resource group).
Changes:
- Added a new Cost Management area (
costmanagement) withquery runcommand, options model, service implementation, and AOT-friendlySystem.Text.Jsonsource generation context. - Added unit tests plus recorded live tests scaffolding for the new command.
- Wired the tool into the server (area registration, consolidated tool mapping) and updated docs/metadata (README, command list, e2e prompts, changelog, CODEOWNERS, package version).
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.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/Azure.Mcp.Tools.CostManagement/tests/test-resources.bicep | Adds empty bicep template required by live test harness. |
| tools/Azure.Mcp.Tools.CostManagement/tests/test-resources-post.ps1 | Adds standard post-deploy script to generate live test settings. |
| tools/Azure.Mcp.Tools.CostManagement/tests/Azure.Mcp.Tools.CostManagement.UnitTests/Services/CostManagementServiceTests.cs | Unit tests for service helper parsing/formatting utilities. |
| tools/Azure.Mcp.Tools.CostManagement/tests/Azure.Mcp.Tools.CostManagement.UnitTests/Query/QueryRunCommandTests.cs | Unit tests for command wiring, validation, and error handling. |
| tools/Azure.Mcp.Tools.CostManagement/tests/Azure.Mcp.Tools.CostManagement.UnitTests/Azure.Mcp.Tools.CostManagement.UnitTests.csproj | Adds unit test project for CostManagement tool. |
| tools/Azure.Mcp.Tools.CostManagement/tests/Azure.Mcp.Tools.CostManagement.LiveTests/QueryRunCommandTests.cs | Adds recorded live tests covering core query scenarios. |
| tools/Azure.Mcp.Tools.CostManagement/tests/Azure.Mcp.Tools.CostManagement.LiveTests/Azure.Mcp.Tools.CostManagement.LiveTests.csproj | Adds live test project for CostManagement tool. |
| tools/Azure.Mcp.Tools.CostManagement/tests/Azure.Mcp.Tools.CostManagement.LiveTests/assets.json | Adds assets.json required for recorded test assets workflow. |
| tools/Azure.Mcp.Tools.CostManagement/src/Services/ICostManagementService.cs | Introduces service contract for cost query execution. |
| tools/Azure.Mcp.Tools.CostManagement/src/Services/CostManagementService.cs | Implements Cost Management query execution and response normalization. |
| tools/Azure.Mcp.Tools.CostManagement/src/Options/Query/QueryRunOptions.cs | Adds options model for query run. |
| tools/Azure.Mcp.Tools.CostManagement/src/Options/CostManagementOptionDefinitions.cs | Defines CLI options for timeframe/date range/granularity/grouping. |
| tools/Azure.Mcp.Tools.CostManagement/src/Options/BaseCostManagementOptions.cs | Introduces common options base for CostManagement commands. |
| tools/Azure.Mcp.Tools.CostManagement/src/Models/QueryTimeframe.cs | Adds supported timeframe enum (incl. Custom). |
| tools/Azure.Mcp.Tools.CostManagement/src/Models/QueryGranularity.cs | Adds granularity enum (None/Daily/Monthly). |
| tools/Azure.Mcp.Tools.CostManagement/src/Models/CostQueryRow.cs | Defines typed row model returned to clients. |
| tools/Azure.Mcp.Tools.CostManagement/src/Models/CostQueryResult.cs | Defines top-level result model returned to clients. |
| tools/Azure.Mcp.Tools.CostManagement/src/GlobalUsings.cs | Adds global using for System.CommandLine. |
| tools/Azure.Mcp.Tools.CostManagement/src/CostManagementSetup.cs | Registers DI + command groups for the new area. |
| tools/Azure.Mcp.Tools.CostManagement/src/Commands/Query/QueryRunCommand.cs | Implements costmanagement query run command and validation/error mapping. |
| tools/Azure.Mcp.Tools.CostManagement/src/Commands/CostManagementJsonContext.cs | Adds source-generated JSON serialization context for AOT. |
| tools/Azure.Mcp.Tools.CostManagement/src/Commands/BaseCostManagementCommand.cs | Adds shared command base for CostManagement (subscription + resource group option). |
| tools/Azure.Mcp.Tools.CostManagement/src/Azure.Mcp.Tools.CostManagement.csproj | Adds new tool project and dependencies including CostManagement SDK. |
| tools/Azure.Mcp.Tools.CostManagement/src/AssemblyInfo.cs | Enables internals visibility for unit/live tests. |
| servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json | Adds consolidated tool entry mapping to costmanagement_query_run. |
| servers/Azure.Mcp.Server/src/Program.cs | Registers CostManagementSetup in server area registration. |
| servers/Azure.Mcp.Server/README.md | Documents Cost Management prompts and adds it to services list. |
| servers/Azure.Mcp.Server/docs/e2eTestPrompts.md | Adds E2E prompts for costmanagement_query_run. |
| servers/Azure.Mcp.Server/docs/azmcp-commands.md | Adds CLI documentation block for the new command. |
| servers/Azure.Mcp.Server/changelog-entries/francesco1501-add-costmanagement-query.yml | Adds changelog entry announcing the new tool. |
| servers/Azure.Mcp.Server/Azure.Mcp.Server.slnx | Adds new tool + test projects to server solution. |
| Microsoft.Mcp.slnx | Adds new tool + test projects to repo solution. |
| Directory.Packages.props | Adds Azure.ResourceManager.CostManagement package version. |
| .vscode/cspell.json | Adds costmanagement to spelling dictionary. |
| .github/CODEOWNERS | Adds CODEOWNERS entry/label for CostManagement tool. |
| Assert.True(rows.GetArrayLength() > 0, | ||
| "Expected at least one cost row when grouping by ServiceName MTD against a subscription with known spend."); |
| new() | ||
| { | ||
| { "subscription", Settings.SubscriptionId }, | ||
| { "timeframe", "Custom" }, | ||
| { "from", "2026-04-01" }, | ||
| { "to", "2026-04-07" }, | ||
| { "granularity", "Daily" } | ||
| }); |
Azure.ResourceManager.CostManagement 1.0.3 transitively requires
Azure.Core >= 1.53.0. Bumping Azure.Core to 1.53 introduces a
CredentialUnavailableException type collision with Azure.Identity 1.17.1
in core/Azure.Mcp.Core/tests/.../CustomChainedCredentialTests.cs (CS0433).
Downgrade to 1.0.2 (which only requires Azure.Core >= 1.44.1) to keep
the existing Azure.Core 1.50 pin. Trade-off: 1.0.2 lacks two enum
values that we removed from our QueryTimeframe / QueryGranularity:
- QueryTimeframe.TheCurrentMonth (use MonthToDate instead)
- QueryGranularity.Monthly (use Daily for breakdown,
None for aggregated total)
Updated docs (azmcp-commands.md, consolidated-tools.json, changelog
entry) and option descriptions accordingly.
jongio
left a comment
There was a problem hiding this comment.
Clean implementation that follows the repo's toolset patterns well. The service layer handles response normalization thoughtfully, covering variant column names (Cost/PreTaxCost/CostUSD) across billing account types and date format parsing for both daily and monthly granularities. The Custom timeframe validation via command.Validators is the right approach, and the error handling maps SDK exceptions to actionable user messages.
A few items for maintainers:
CI: The mcp - pullrequest pipeline shows 25 errors, but the author reports clean local builds (0 warnings, 30/30 tests). Worth checking the Azure DevOps logs since this could be a pipeline config issue for fork/community PRs.
SDK version pinning: Pinning to Azure.ResourceManager.CostManagement 1.0.2 to avoid the Azure.Core version conflict is a reasonable trade-off. The removed enum values (TheCurrentMonth, Monthly) are correctly excluded from the option definitions and documented in the PR body.
Live tests: The bot's two comments about hardcoded dates and the rows > 0 assertion are worth addressing before merge.
| /tools/Azure.Mcp.Tools.CostManagement/ @francesco1501 @microsoft/azure-mcp | ||
|
|
||
| # ServiceLabel: %tools-CostManagement | ||
| # ServiceOwners: @francesco1501 |
There was a problem hiding this comment.
The ServiceOwners line lists only the external contributor. Other toolsets in this repo have at least one Microsoft team member as ServiceOwner/CODEOWNER. Consider adding a team member alongside so code ownership doesn't depend entirely on an external contributor's availability.
Closes #2602
References #1420, #447
Summary
Adds a new
Azure.Mcp.Tools.CostManagementtoolset with the first commandazmcp costmanagement query run, exposing the Azure Cost Management Query/Usage API to MCP clients. Read-only, subscription + optional resource-group scope. Backed by the GA SDKAzure.ResourceManager.CostManagement1.0.3.What's new
azmcp costmanagement query run— actual costs for a subscription (optionally narrowed to a resource group).--from/--to).TheLastMonthintentionally omitted — only supported at billing-account scope.--group-by(ServiceName, ResourceGroupName, ResourceLocation, ResourceId, MeterCategory, MeterSubCategory, ChargeType, BillingPeriod). Custom/tag dimensions also accepted.f7c4b3a8-9e62-4d18-bc41-2a5d8e6f1b09(verified unique repo-wide)Cost Management ReaderorReaderrole on the scope.Implementation notes
Azure.Mcp.Tools.Marketplace(newest reference) —BaseAzureService+SubscriptionCommand<TOptions>+ AOT-compatibleJsonSerializerContext. Tests extendCommandUnitTestsBase<TCommand, TService>.rows[][](array of arrays). The service normalizes columns (Cost/PreTaxCost/CostUSDare all handled — non-obvious EA/MCA/PAYG variance), parsesUsageDate(YYYYMMDD/YYYYMM ints) into ISO-8601, and returns typedCostQueryRowrecords — much friendlier for LLM consumption than the raw shape.Customtimeframe requires both--fromand--to;--frommust be ≤--to. Validated at command level viacommand.Validators.GetErrorMessagecovers 400/403/404/429 with actionable hints;GetStatusCodeoverride mapsRequestFailedException.Statusdirectly (verified: invalid--group-byreturns 400, not 500).ReadDecimal/ReadStringthrowInvalidOperationExceptionon parse failure (no silent under-reporting for billing data);JsonValueKind.Nullcleanly returns 0/null.Tests
ReadDecimal,ReadString,FormatUsageDate,ResolveColumnIndex,KnownDimensions) tested directly viainternal staticexposure.Should_query_subscription_costs_month_to_date,Should_query_subscription_costs_grouped_by_service,Should_query_custom_timeframe_with_daily_granularity). Recorded against an MCA subscription; recordings validated in Playback mode (3/3 passed in 5.8s with zero Azure calls). Recording files not included in this PR for organizational privacy (real cost figures and tenant identifiers); happy to either: add custom sanitizers + re-record, or let the maintainer record against an internal subscription at merge time.QueryResulthasinternal-only ctor in SDK 1.0.3 andArmCostManagementModelFactorydoes not expose a builder for it (verified against the SDK source); reflection-based test would be fragile across SDK bumps.Pre-merge checklist
costmanagement query rungit rebaseagainstupstream/mainHEAD (3c5fd84818d) — diff is purely additive: 10 wiring files +95/-0, 24 new files undertools/Azure.Mcp.Tools.CostManagement/dotnet build(src + server + LiveTests) — 0 warnings, 0 errorsdotnet test— 30/30 passeddotnet format --verify-no-changes— clean./eng/scripts/Update-AzCommandsMetadata.ps1— ran, only adds the new tool's entry./eng/scripts/Analyze-AOT-Compact.ps1— 0 trim/AOT warnings, 0 affected DLLs./eng/common/spelling/Invoke-Cspell.ps1— 0 issues across 24 files (addedcostmanagementto.vscode/cspell.json)servers/Azure.Mcp.Server/changelog-entries/francesco1501-add-costmanagement-query.ymlwith sectionFeatures Addedtools-CostManagementblock addedassets.json(Tag empty for maintainer push) +test-resources.bicep(empty per Marketplace convention)ToolDescriptionEvaluator≥0.4 + top-3 — not run locally (requires Azure OpenAItext-embedding-3-largedeployment unavailable to external contributors); relying on PR Validation CIAzure/azure-sdk-assets— requires maintainer write access;assets.jsonTagleft empty fortest-proxy pushat merge timeFiles changed
10 modified (wiring), 25 new (toolset + tests + changelog + bicep + assets):
Directory.Packages.props— addsAzure.ResourceManager.CostManagement 1.0.3Microsoft.Mcp.slnx,servers/Azure.Mcp.Server/Azure.Mcp.Server.slnx— register src + UnitTests + LiveTests projectsservers/Azure.Mcp.Server/src/Program.cs— registerCostManagementSetup()in alphabetical positionservers/Azure.Mcp.Server/src/Resources/consolidated-tools.json— addquery_azure_costsmappingservers/Azure.Mcp.Server/docs/azmcp-commands.md— sync viaUpdate-AzCommandsMetadata.ps1servers/Azure.Mcp.Server/docs/e2eTestPrompts.md— 10 test promptsservers/Azure.Mcp.Server/README.md— usage example block + entry in 43+ services list.github/CODEOWNERS—tools-CostManagementblock.vscode/cspell.json—costmanagementtools/Azure.Mcp.Tools.CostManagement/— full new toolset (src + UnitTests + LiveTests + bicep + post-deploy)Setup notes for whoever records the live tests
Proven working procedure (in case helpful):
.testsettings.jsonnext to the LiveTests project withTestMode: "Record",IsServicePrincipal: true, and anEnvironmentVariablesblock includingAZURE_TOKEN_CREDENTIALS=EnvironmentCredential— without this the harness prompts for browser login even with valid SP env varsAO View Chargesenabled — pure EA tenants without that flag returnAccountCostDisabledregardless of RBAC.assets/<hash>/.../SessionRecords/*.jsonfiles to commit, runtest-proxy pushto populate theassets.jsonTag