Skip to content

Add metadata-backed filtering and enrichment for List Advisor recommendations tool - #3307

Open
Koushik (koushik137) wants to merge 13 commits into
microsoft:mainfrom
koushik137:users/koushik137/advisor-list-metadata-join
Open

Add metadata-backed filtering and enrichment for List Advisor recommendations tool#3307
Koushik (koushik137) wants to merge 13 commits into
microsoft:mainfrom
koushik137:users/koushik137/advisor-list-metadata-join

Conversation

@koushik137

@koushik137 Koushik (koushik137) commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

This PR adds metadata-backed filtering and enrichment to the List Advisor recommendations tool, enabling precise filtering by recommendation type ID and service-retirement details while enriching recommendation data with comprehensive metadata.

Core Capabilities Added:

1. Four New Filtering Options

  • --recommendation-type-id: Filter by recommendation type GUID (canonical format, case-insensitive exact match)
  • --sub-category: Filter by recommendation subcategory (e.g., ServiceUpgradeAndRetirement, ZoneResiliency, Reservations, etc.)
  • --tracking-ids: Filter by one or more Service Health tracking IDs (space-separated, match-any logic for multiple IDs)
  • --retirement-date: Filter by service-retirement date using comparison operators (e.g., ge:2026-03-31, lt:2026-12-31)

2. Intelligent Metadata Enrichment Pipeline

  • Recommendations automatically enriched with type-level metadata when metadata-backed filters are applied
  • Metadata properties (category, impact, descriptions, subcategory, retirement details) override instance properties for consistency
  • Security category recommendations skip enrichment (direct queries only)
  • Smart subcategory inference: when tracking IDs or retirement dates are specified without explicit subcategory, defaults to ServiceUpgradeAndRetirement

3. Paginated Metadata Resolution

  • Paging support (1000 items/page) through all matching metadata records to avoid truncation failures
  • Automatic continuation token handling with cycle detection
  • Deduplication of results by recommendation type ID
  • Prevents incomplete results when 1000+ records match metadata filters

4. Comprehensive Validation Framework

  • New validators: ServiceRetirementFilterValidator and RecommendationFilterValidator
  • Enforces filter compatibility:
  • Tracking IDs and retirement dates only work with ServiceUpgradeAndRetirement subcategory
  • Security category cannot use metadata-only filters (subcategory, tracking IDs, retirement date)
  • Recommendation Type ID must be valid GUID in canonical format
  • Multiple tracking IDs require space-separated syntax

GitHub issue number?

[Link to the GitHub issue this PR addresses]

Pre-merge Checklist

  • Required for All PRs
    • Read contribution guidelines
    • PR title clearly describes the change
    • Commit history is clean with descriptive messages (cleanup guide)
    • Added comprehensive tests for new/modified functionality
    • Created a changelog entry if the change falls among the following: new feature, bug fix, UI/UX update, breaking change, or updated dependencies. Follow the changelog entry guide
  • For MCP tool changes:
    • One tool per PR: This PR adds or modifies only one MCP tool for faster review cycles
    • Updated servers/Azure.Mcp.Server/README.md and/or servers/Fabric.Mcp.Server/README.md documentation
    • Validate README.md changes running the script ./eng/scripts/Process-PackageReadMe.ps1. See Package README
    • For new or modified tool descriptions, ran ToolDescriptionEvaluator and obtained a score of 0.4 or more and a top 3 ranking for all related test prompts
    • For tools with new names, including new tools or renamed tools, update consolidated-tools.json
    • For renamed tools, follow the Tool Rename Checklist and tag the PR with the breaking-change label
    • For new tools associated with Azure services or publicly available tools/APIs/products, add URL to documentation in the PR description
  • Extra steps for Azure MCP Server tool changes:
    • Updated command list in servers/Azure.Mcp.Server/docs/azmcp-commands.md
    • Ran ./eng/scripts/Update-AzCommandsMetadata.ps1 to update tool metadata in azmcp-commands.md (required for CI)
    • Updated test prompts in servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
    • 👉 For Community (non-Microsoft team member) PRs:
      • Security review: Reviewed code for security vulnerabilities, malicious code, or suspicious activities before running tests (crypto mining, spam, data exfiltration, etc.)
      • Manual tests run: added comment /azp run mcp - pullrequest - live to run Live Test Pipeline

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@koushik137 Koushik (koushik137) changed the title Users/koushik137/advisor list metadata join Implement metadata-backed filtering for Advisor List recommendations with type ID and retirement date support Aug 20, 2026
@koushik137 Koushik (koushik137) changed the title Implement metadata-backed filtering for Advisor List recommendations with type ID and retirement date support Implement metadata-backed filtering for List Advisor recommendations tool with type ID and retirement date support Aug 20, 2026
@koushik137
Koushik (koushik137) marked this pull request as ready for review August 20, 2026 14:08
@koushik137
Koushik (koushik137) requested a review from a team as a code owner August 20, 2026 14:08
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@koushik137 Koushik (koushik137) changed the title Implement metadata-backed filtering for List Advisor recommendations tool with type ID and retirement date support Add metadata-backed filtering and enrichment for List Advisor recommendations tool Aug 20, 2026
Comment thread tools/Azure.Mcp.Tools.Advisor/src/Models/Recommendation.cs Outdated
Copilot AI lite review requested due to automatic review settings August 21, 2026 06:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds metadata-backed filtering and type-level enrichment to the Azure Advisor recommendation list tool, enabling filtering by recommendation type GUID and service-retirement metadata (tracking IDs + retirement date) while paging through Advisor metadata to avoid Resource Graph truncation.

Changes:

  • Added new CLI filters (--recommendation-type-id, --sub-category, multi-value --tracking-ids, --retirement-date) plus shared validation for service-retirement filter rules.
  • Implemented metadata resolution + paging (1000/page with continuation handling) and joined metadata into recommendation results (with Security-category bypass rules).
  • Updated response models, tests, docs (azmcp-commands.md, e2eTestPrompts.md), and added a changelog entry.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Services/AdvisorServiceMetadataJoinTests.cs New tests covering metadata paging, lookup, and join behavior.
tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Services/AdvisorServiceFilterBuilderTests.cs Extends KQL filter-building tests for new filters/type-ID intersection.
tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Services/AdvisorServiceConversionTests.cs Updates conversion/serialization expectations for expanded recommendation shape.
tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Services/AdvisorMetadataServiceTests.cs Updates metadata query tests for multi-tracking-id behavior and new projection/order.
tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Recommendation/RecommendationListCommandTests.cs Adds/updates command tests for new filters and validation errors.
tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Metadata/RecommendationMetadataListCommandTests.cs Updates metadata-list command tests for tracking ID list handling.
tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Metadata/MetadataGetCommandTests.cs Renames a test to reflect metadata language normalization wording.
tools/Azure.Mcp.Tools.Advisor/src/Validation/ServiceRetirementFilterValidator.cs New shared validator for retirement-date parsing and subcategory compatibility rules.
tools/Azure.Mcp.Tools.Advisor/src/Validation/RecommendationFilterValidator.cs New validator for recommendation list filters (allowed values, GUID format, etc.).
tools/Azure.Mcp.Tools.Advisor/src/Services/Models/RecommendationProperties.cs Expands ARG wire model to include additional recommendation fields.
tools/Azure.Mcp.Tools.Advisor/src/Services/Models/RecommendationDescription.cs Adds Solution to wire short description model.
tools/Azure.Mcp.Tools.Advisor/src/Services/Models/RecommendationData.cs Expands wire model shape (e.g., resource group) and clarifies docs.
tools/Azure.Mcp.Tools.Advisor/src/Services/AdvisorService.cs Implements metadata match resolution + paging, enrichment join, and extended filtering logic.
tools/Azure.Mcp.Tools.Advisor/src/Options/Recommendation/RecommendationListOptions.cs Adds new CLI options and updated help text for metadata-backed filters.
tools/Azure.Mcp.Tools.Advisor/src/Models/RecommendationShortDescription.cs New public response model for short description.
tools/Azure.Mcp.Tools.Advisor/src/Models/RecommendationResourceMetadata.cs New public response model for impacted resource metadata.
tools/Azure.Mcp.Tools.Advisor/src/Models/RecommendationProperties.cs New public response model capturing richer recommendation properties.
tools/Azure.Mcp.Tools.Advisor/src/Models/RecommendationMetadataFilters.cs Updates metadata filters to support multi-value tracking IDs.
tools/Azure.Mcp.Tools.Advisor/src/Models/RecommendationFilters.cs Adds new filtering fields (type ID, subcategory, tracking IDs, retirement date).
tools/Azure.Mcp.Tools.Advisor/src/Models/Recommendation.cs Changes recommendation response to an ARM-style {id,name,type,properties} shape.
tools/Azure.Mcp.Tools.Advisor/src/Commands/Recommendation/RecommendationListCommand.cs Wires new options/validation and forwards parsed filters into the service.
tools/Azure.Mcp.Tools.Advisor/src/Commands/Metadata/RecommendationMetadataListCommand.cs Reuses shared retirement filter parsing/validation.
tools/Azure.Mcp.Tools.Advisor/src/Commands/AdvisorJsonContext.cs Registers new response/wire-model serialization types for source-gen (AOT).
servers/Azure.Mcp.Server/docs/e2eTestPrompts.md Adds/updates prompts covering the new filter scenarios.
servers/Azure.Mcp.Server/docs/azmcp-commands.md Updates CLI docs to include new options and service-retirement guidance.
servers/Azure.Mcp.Server/changelog-entries/advisor-recommendation-metadata-filters.yml Adds a changelog entry describing the new filters + paging behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tools/Azure.Mcp.Tools.Advisor/src/Services/AdvisorService.cs
Comment thread tools/Azure.Mcp.Tools.Advisor/src/Models/Recommendation.cs
{
Category = metadata.Category,
Impact = metadata.Impact,
ShortDescription = new RecommendationShortDescription(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This builds the RecommendationShortDescription with metadata.DisplayName in both fields, so when DisplayName is set (the normal case) problem == solution and the instance's actual solution text is discarded. Two things worth reconsidering:

  1. Overwriting solution with the type-level display name loses per-instance solution guidance. Consider per-field fallback (e.g. recommendation.Properties.ShortDescription?.Problem ?? metadata.DisplayName for problem, and keep the instance solution) or surface DisplayName in its own field.
  2. If DisplayName and the instance description are both null, this still allocates a record that serializes to shortDescription: {} under DefaultIgnoreCondition = WhenWritingNull. ConvertToRecommendationMetadataModel only enforces recommendationTypeId (not DisplayName), so the "metadata always has displayName" assumption isn't guaranteed in code — cheap to guard by returning null when both are empty.

filters?.RetirementDate is not null ||
!string.IsNullOrWhiteSpace(filters?.RetirementDateOperator);

internal static bool HasMetadataFilters(RecommendationFilters? filters) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Because enrichment overwrites the instance category/impact with metadata values, a lone --impact or --category (non-Security) is treated as a metadata filter here. That pre-resolves the whole metadata catalog and then emits recommendationTypeId in~ ('id1' … 'idN') on the instance query. For broad filters that type-ID set can be large (paged at 1000), producing a very large IN clause and an extra catalog round-trip. It's a defensible consistency trade-off (filter on the same field you display), but worth a comment here and possibly a guardrail on how many type IDs get pushed into the query.

{
var additionalFilter = BuildAdditionalFilter(filters);
Dictionary<string, RecommendationMetadata>? metadataByTypeId =
await ResolveMetadataFilterMatchesAsync(filters, cancellationToken);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both metadata paths — this filter-resolution call and the enrichment fetch below (GetRecommendationMetadataByTypeIdsAsync) — go through GetTenantResourceAsync, which returns tenants[0] and ignores the --tenant option, whereas the recommendations query itself honors tenant. Advisor metadata is a language-keyed global catalog so this is usually harmless, but it's inconsistent and could hit an unintended or less-privileged tenant for the ARG metadata call. Consider threading tenant through the metadata paths too.

result.ResultTruncated == ResultTruncated.True);
}

private static List<RecommendationMetadata> ParseMetadata(BinaryData data)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: this parse block duplicates the array-parse + validation already in ExecuteMetadataQueryAsync. Worth collapsing the two into a single shared helper.

recommendation.Properties.ExtendedProperties,
metadata.ServiceRetirement),
metadata.SubCategory),
ResourceMetadata = recommendation.Properties.ResourceMetadata,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: ResourceMetadata = recommendation.Properties.ResourceMetadata inside the with expression is a no-op (it already holds that value) and can be dropped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants