Repository navigation
fix(billing-cost-management-mcp-server): cost-explorer filter grammar + classify ParamValidationError - #4766
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4766 +/- ##
=======================================
Coverage 93.55% 93.55%
=======================================
Files 1068 1068
Lines 92344 92355 +11
Branches 15025 15027 +2
=======================================
+ Hits 86389 86400 +11
Misses 3562 3562
Partials 2393 2393 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Thanks for this. The Some other
Would you be open to covering those here too, since this PR already adds a branch before the Heads up: #4781 also touches 🤖 AI-assisted comment 🤖 |
…ationError as validation_error ParamValidationError is a client-side botocore failure (unknown kwargs, bad parameter shape, missing required keys, type mismatches) that subclasses BotoCoreError. The previous order in handle_aws_error matched BotoCoreError first, so every ParamValidationError was labeled aws_connection_error, telling callers 'AWS is unreachable' when the real problem was their own request shape. Match ParamValidationError before BotoCoreError and return it as validation_error, preserving boto_error_type=ParamValidationError for diagnostics. Plain BotoCoreError still classifies as aws_connection_error. Add a test asserting the new path is reached and the response carries error_type=validation_error with boto_error_type=ParamValidationError.
…r grammar and window limits Agent-side failures on the cost-explorer tool fall into three recurring patterns that the current tool description does not warn against: 1. Filter MatchOptions grammar. Agents guess shape from training memory or carry over Cost Category RULE grammar (CASE_INSENSITIVE, STARTS_WITH, ENDS_WITH, CONTAINS), none of which are valid in a Cost Explorer filter. Add a FILTER EXPRESSION GRAMMAR block listing the per-leaf MatchOptions allow-list (Dimensions: EQUALS, CASE_SENSITIVE; Tags and CostCategories: EQUALS, ABSENT, CASE_SENSITIVE), the explicit NOT-valid list, and the ABSENT-takes-no-Values rule, with correct examples. 2. Window limits. The 14-day cap on getCostAndUsageWithResources, the 38-month cap on standard CE operations, and the 7-month-history + future-start requirement for forecasts were absent or buried inline. Add a WINDOW LIMITS block and tighten the per-operation Notes. 3. getDimensionValues TAG. The DIMENSION REFERENCE listed TAG without qualification, so agents pass it as the dimension argument and hit ValidationException. TAG is valid only as a GroupBy Type and inside filter Tags leaves; clarify this in the DIMENSION REFERENCE and in the getDimensionValues Notes, and point agents to getTagsOrValues for tag discovery. Documentation only, no runtime change.
… against the API reference Corrects statements added in the previous commit so that every limit in the cost-explorer tool description matches the Cost Explorer API reference and user guide: - Forecast horizon is 3 months at DAILY and 18 months at MONTHLY (was 12). - Remove the claim that forecasts need 7 months of history. The user guide says only that no forecast is produced without enough history, commonly for accounts with less than one full billing cycle. - getCostForecast accepts only the five cost metrics; USAGE_QUANTITY and NORMALIZED_USAGE_AMOUNT belong to getUsageForecast. - getSavingsPlansUtilization: start must be within the last 13 months and end before the current date (was "up to 38 months"). - getCostAndUsage: current month plus the previous 13 months; older months only at MONTHLY with multi-year data enabled. Drop the history limits stated for getDimensionValues, getTagsOrValues and getCostCategories, which the reference does not give. - Scope the MatchOptions list to getCostAndUsage and getCostAndUsageWithResources, the operations whose reference documents it, and note the default (EQUALS, CASE_SENSITIVE). - Remove error names and error messages the reference does not document, and the unsupported reading of DataUnavailableException for Savings Plans utilization. Also move the ParamValidationError entry in CHANGELOG.md from Changed to Fixed. Documentation only.
… configuration errors NoCredentialsError, PartialCredentialsError, NoRegionError, ProfileNotFound and UnknownServiceError subclass BotoCoreError, so handle_aws_error reported them as aws_connection_error and retry_strategy resolved to retry_same_request. Retrying cannot fix them during the run. Keep error_type=aws_connection_error so the classification stays stable, and set retry_strategy=do_not_retry plus a hint explicitly on the response; ErrorSignalingMiddleware keeps a retry_strategy that is already set. The real connection failures (EndpointConnectionError, ConnectTimeoutError, ReadTimeoutError, ConnectionClosedError) are still retry_same_request. Also assert that ParamValidationError resolves to modify_request, and document both in the README error-response section and CHANGELOG. Requested in review by @finklen-amazon.
bcfdf0d to
a5bee5d
Compare
Fixes
Summary
Changes
Two fixes for the
cost-explorertool inbilling-cost-management-mcp-server.1. Classify
ParamValidationErrorasvalidation_error(utilities/aws_service_base.py)botocore.exceptions.ParamValidationErroris raised client-side, before any network call, when request parameters are malformed (unknown keys such asTags.MatchValue, missing required keys, type mismatches). It subclassesBotoCoreError, sohandle_aws_errormatched it in theBotoCoreErrorbranch and returnederror_type: aws_connection_errorwith the messageAWS service connection error: ParamValidationError. That tells the caller the service is unreachable when the request shape is the problem, so callers tend to retry the same malformed request.handle_aws_errornow matchesParamValidationErrorbeforeBotoCoreErrorand returnserror_type: validation_errorwith botocore's message, keepingboto_error_type: ParamValidationErrorfor diagnostics. OtherBotoCoreErrorsubclasses still returnaws_connection_error.2. Document filter grammar and time-period limits in the
cost-explorertool description (tools/cost_explorer_tools.py)Documentation only; no runtime change. The statements are based on the Cost Explorer API reference (the
ceservice model shipped with botocore) or the Cost Explorer user guide.FILTER EXPRESSION GRAMMARblock:{<Dimensions|Tags|CostCategories>: {Key, Values, MatchOptions}}and the one-root-key ruleMatchOptionsthatgetCostAndUsageandgetCostAndUsageWithResourcesaccept (Dimensions:EQUALS,CASE_SENSITIVE;TagsandCostCategories:EQUALS,ABSENT,CASE_SENSITIVE) and the default (EQUALS,CASE_SENSITIVE)MatchOptionvalues (CASE_INSENSITIVE,STARTS_WITH,ENDS_WITH,CONTAINS,GREATER_THAN_OR_EQUAL) apply to Cost Category rules and anomaly subscriptions, not to these filtersABSENTwith aKeyand noValuesmatches costs without that tag key, plus correct examplesWINDOW LIMITSblock with the documented time-period limits:getCostAndUsage: the current month and the previous 13 months; older months only at MONTHLY granularity with multi-year data enabledgetCostAndUsageWithResources: within the last 14 daysgetSavingsPlansUtilization: start within the last 13 months, end before the current dategetCostAndUsageWithResources,getDimensionValues,getCostForecast(its five accepted cost metrics), andgetSavingsPlansUtilization.TAGentry in the dimension reference now saysTAGis valid as aGroupBytype and through filterTagsleaves, but not as thedimensionargument ofgetDimensionValues(it is not in the API'sDimensionenum). Tag discovery goes throughgetTagsOrValueswithtag_key.Tests: added
test_handle_param_validation_error. The existingtest_handle_boto_core_erroris unchanged and still passes, so plainBotoCoreErrorhandling has not regressed.CHANGELOG.mdhas the classifier fix under Unreleased → Fixed and the description change under Unreleased → Changed.User experience
Before:
{"Tags": {"Key": "env", "MatchValue": "prod"}}returnserror_type: aws_connection_error,message: AWS service connection error: ParamValidationError. The caller cannot tell from this that its request was malformed.MatchOptionsthe filter accepts. Callers send values from the Cost Category rule grammar, such asCASE_INSENSITIVE, or sendABSENTtogether withValues, and Cost Explorer rejects the request withValidationException.TAGappears in the dimension reference without qualification, so callers pass it as thegetDimensionValuesdimension, which the API does not accept.After:
error_type: validation_error,boto_error_type: ParamValidationError, andmessage: Parameter validation failed: Unknown parameter in Filter.Tags: "MatchValue", must be one of: Key, Values, MatchOptions, so the caller can correct the request.MatchOptionsand each operation's time-period limits, so callers can build valid requests on the first attempt.Checklist
If your change doesn't seem to apply, please leave them unchecked.
Testing:
get_cost_and_usagewith the malformed filter against a local closed endpoint (no AWS call is made: botocore rejects the parameters first). Onmainit returnsaws_connection_error; on this branch it returnsvalidation_errorwith the message above.ruff checkandruff format --checkpass for the package;pyrightreports 0 errors on the changed source filescheck-license-header, which could not be installed in my environment (no license headers changed)Is this a breaking change? (Y/N)
N. The tool's arguments and success responses are unchanged. The only behavior change is that
ParamValidationErrornow returnserror_type: validation_errorinstead ofaws_connection_error.validation_erroris an existing error type, already returned for invalid JSON input.RFC issue number:
Checklist:
Acknowledgment
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.