Skip to content

Commit b936294

Browse files
fix(billing-cost-management-mcp-server): correct the ri-performance coverage metrics example and default (#4834)
The `metrics` arg doc told callers to send '["HoursCoverage", "CostCoverage"]'. GetReservationCoverage rejects those names: "Selected metrics are not valid. Valid metrics are Hour, Unit, Cost. Provided metrics are HoursCoverage, CostCoverage". The doc also said omitting `metrics` returns all metrics, but Cost Explorer then returns only the hours section (CoverageHours). The doc now gives a working example ('["Hour", "Cost"]'), lists the valid values, says Unit is EC2 only ("Metric Unit is not supported for <service>" for the other reservable services), and says the default is Hour only. Tests: a contract test checks the metrics arg doc, and the two tests that sent CoverageHours/CoverageCost as request metrics now send Hour/Cost.
1 parent 8abaec6 commit b936294

2 files changed

Lines changed: 18 additions & 3 deletions

File tree

‎src/billing-cost-management-mcp-server/awslabs/billing_cost_management_mcp_server/tools/ri_performance_tools.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ async def ri_performance(
137137
start_date: Start date in YYYY-MM-DD format (inclusive). Defaults to 30 days ago if not provided.
138138
end_date: End date in YYYY-MM-DD format (exclusive). Defaults to today if not provided.
139139
granularity: Time granularity of the data (DAILY or MONTHLY). Defaults to DAILY. Ignored when group_by is provided (granularity and group_by are mutually exclusive).
140-
metrics: List of metrics to retrieve for coverage as a JSON string (e.g., '["HoursCoverage", "CostCoverage"]'). Defaults to all metrics.
140+
metrics: List of metrics to retrieve for coverage as a JSON string (e.g., '["Hour", "Cost"]'). Valid values are Hour, Unit and Cost; Unit is EC2 only. Defaults to Hour only.
141141
group_by: Optional grouping as a JSON string. For coverage: AZ, CACHE_ENGINE, DATABASE_ENGINE, DEPLOYMENT_OPTION, INSTANCE_TYPE, INSTANCE_TYPE_FAMILY, LINKED_ACCOUNT, PLATFORM, REGION, or TENANCY, depending on the service (see the tool description) (SERVICE is NOT valid — filter on it instead). For utilization: SUBSCRIPTION_ID only.
142142
filter: Optional filter as a JSON string (e.g., by service, region, or instance type). SERVICE defaults to EC2 when omitted; valid coverage SERVICE values are "Amazon Elastic Compute Cloud - Compute", "Amazon Relational Database Service", "Amazon ElastiCache", "Amazon MemoryDB", "Amazon Redshift", "Amazon OpenSearch Service", and "Amazon Elasticsearch Service" (use the full display name, not "EC2"/"RDS"). SubscriptionId values are numeric, not ARNs/UUIDs. A wide grouped/DAILY query may exceed the result size cap (ToolResultOversized) — scope it with a filter, a shorter window, or MONTHLY granularity.
143143
sort_by: Optional sorting configuration as a JSON string with key and direction (ASCENDING or DESCENDING).

‎src/billing-cost-management-mcp-server/tests/tools/test_ri_performance_tools.py‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ async def test_get_reservation_coverage_with_options(
482482
PAGINATION_COMPLETE,
483483
)
484484

485-
mock_metrics = ['CoverageHours', 'CoverageCost']
485+
mock_metrics = ['Hour', 'Cost']
486486
mock_group_by = [{'Type': 'DIMENSION', 'Key': 'INSTANCE_TYPE'}]
487487
mock_filter = {
488488
'Dimensions': {'Key': 'SERVICE', 'Values': ['Amazon Elastic Compute Cloud - Compute']}
@@ -766,7 +766,7 @@ async def test_ri_performance_utilization(self, mock_context):
766766
async def test_ri_performance_with_all_params(self, mock_context):
767767
"""Test ri_performance with all parameters."""
768768
# Setup
769-
metrics = '["CoverageHours", "CoverageCost"]'
769+
metrics = '["Hour", "Cost"]'
770770
group_by = '[{"Type": "DIMENSION", "Key": "INSTANCE_TYPE"}]'
771771
filter_expr = '{"Dimensions": {"Key": "SERVICE", "Values": ["Amazon Elastic Compute Cloud - Compute"]}}'
772772
sort_by = '{"Key": "CoverageHoursPercentage", "SortOrder": "DESCENDING"}'
@@ -1181,6 +1181,21 @@ async def test_description_states_subscription_id_format_and_error():
11811181
assert 'SERVICE filter takes a single value' not in description
11821182

11831183

1184+
@pytest.mark.asyncio
1185+
async def test_metrics_arg_lists_the_values_cost_explorer_accepts():
1186+
"""GetReservationCoverage takes Hour, Unit and Cost; other names are a ValidationException."""
1187+
tool = await ri_performance_server.get_tool('ri-performance')
1188+
assert tool is not None
1189+
metrics_doc = tool.parameters['properties']['metrics']['description']
1190+
assert '["Hour", "Cost"]' in metrics_doc
1191+
assert 'Valid values are Hour, Unit and Cost; Unit is EC2 only' in metrics_doc
1192+
# Without Metrics, Cost Explorer returns only CoverageHours.
1193+
assert 'Defaults to Hour only' in metrics_doc
1194+
assert 'Defaults to all metrics' not in metrics_doc
1195+
for wrong in ('HoursCoverage', 'CostCoverage', 'CoverageHours', 'CoverageCost'):
1196+
assert wrong not in metrics_doc
1197+
1198+
11841199
@pytest.mark.asyncio
11851200
async def test_ungrouped_utilization_ignores_max_items_and_next_token(mock_context):
11861201
"""GetReservationUtilization rejects any token without GroupBy, so none is sent."""

0 commit comments

Comments
 (0)