Skip to content

Commit 7d68eca

Browse files
fix(billing-cost-management-mcp-server): document NoRegion-style placeholder group keys (#4876)
Cost Explorer reports costs with no value for a grouped dimension under a placeholder key such as NoRegion or NoTenancy, both in getCostAndUsage groups and in getCostAndUsageComparisons selectors. Filters match the stored empty value instead, so a filter on the placeholder silently returns $0, and getDimensionValues lists the value as "". The cost-explorer (REPORTING $0) and cost-comparison descriptions now say the placeholder is not a value and that the filter value for those costs is the empty string. The cost-explorer filter grammar now says that [""] is a valid, non-empty Values list. Co-authored-by: Som Chatterjee <5313130+somsubhro@users.noreply.github.com>
1 parent 1601a42 commit 7d68eca

3 files changed

Lines changed: 48 additions & 1 deletion

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ def _starts_before_default_history(today: date, *starts: str) -> bool:
242242
- RECORD_TYPE: Record type (e.g., Usage, Credit, Tax)
243243
- LEGAL_ENTITY_NAME: AWS seller of record
244244
245+
Costs with no value for a grouped dimension come back under a placeholder such as NoRegion or
246+
NoTenancy, including in each comparison's cost_and_usage_selector. The placeholder is not a
247+
value: a filter on it matches nothing. To filter on those costs, pass the empty string:
248+
`{"Dimensions": {"Key": "REGION", "Values": [""]}}`.
249+
245250
Note:
246251
- Time periods must start and end on the first day of a month, with a duration of exactly one month
247252
- The getCostComparisonDrivers operation automatically includes SERVICE and USAGE_TYPE dimensions

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ def _with_example_dates(description: str) -> str:
9595
with usage in the period, so absence is not proof the value is invalid. If you also need the
9696
spend, re-run grouped by that dimension to get the value and its cost in one call.
9797
2. If you GROUPED and the line genuinely shows $0, report "$0.00", not "no data found".
98+
3. A grouped result shows costs with no value for the dimension under a placeholder key such
99+
as NoRegion or NoTenancy. The placeholder is not a value: a filter on it matches nothing
100+
and returns $0, and getDimensionValues lists the empty value as "". To filter on those
101+
costs, pass the empty string: `{"Dimensions": {"Key": "REGION", "Values": [""]}}`. For
102+
costs without a tag or cost category key, use `ABSENT` instead (see FILTER EXPRESSION
103+
GRAMMAR).
98104
99105
PAGINATION (max_items, next_token: getCostAndUsage, getCostAndUsageWithResources, getDimensionValues, getTagsOrValues, getCostCategories only; the other operations take neither):
100106
- max_items is the total number of items returned (getCostAndUsage: one item per group per period, or per period without group_by). Omit it to fetch every page (no limit). Pass it, or narrow the time range, filter or group_by, for queries that can be large (e.g. daily data grouped by RESOURCE_ID, USAGE_TYPE or LINKED_ACCOUNT).
@@ -145,7 +151,9 @@ def _with_example_dates(description: str) -> str:
145151
to these filters.
146152
147153
Values per leaf type:
148-
- Dimensions: `Values` is required and must not be empty.
154+
- Dimensions: `Values` is required and must not be empty. `[""]` (one empty string) is not
155+
empty: in getCostAndUsage it matches costs with no value for the dimension (see
156+
REPORTING $0).
149157
- Tags / CostCategories: `Values` is required, except with `ABSENT`. `ABSENT` matches costs
150158
where the tag or cost category key is not present, so there is no value to compare against:
151159
omit `Values` entirely (an empty `Values` list is rejected too).

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,40 @@ def test_dimension_examples_use_exact_ce_values(
17571757
# A wrong value only counts as a whole list item, so 'Aurora MySQL' is not 'Aurora'.
17581758
assert not re.search(rf'[ (]{re.escape(value)}(?=[,);.]|$)', entry), value
17591759

1760+
@pytest.mark.parametrize(
1761+
'module_name, tool_name, server_attr',
1762+
[
1763+
('cost_explorer_tools', 'cost-explorer', 'cost_explorer_server'),
1764+
('cost_comparison_tools', 'cost-comparison', 'cost_comparison_server'),
1765+
],
1766+
)
1767+
def test_empty_value_placeholder_is_filtered_as_empty_string(
1768+
self, module_name, tool_name, server_attr
1769+
):
1770+
"""Grouped results label an empty value NoRegion etc.; a filter must use "" instead.
1771+
1772+
Cost Explorer reports a cost with no value for a grouped dimension under a
1773+
placeholder key (NoRegion, NoTenancy, ...), but its filters match the stored empty
1774+
value, so a filter on the placeholder silently returns $0.
1775+
"""
1776+
module = importlib.reload(
1777+
importlib.import_module(
1778+
f'awslabs.billing_cost_management_mcp_server.tools.{module_name}'
1779+
)
1780+
)
1781+
description = _registered_tool(getattr(module, server_attr), tool_name).description or ''
1782+
flat = ' '.join(description.split())
1783+
assert 'NoRegion' in flat
1784+
assert 'NoTenancy' in flat
1785+
assert 'a filter on it matches nothing' in flat
1786+
examples = [json.loads(m) for m in re.findall(r'`(\{"Dimensions": [^`]*\})`', description)]
1787+
assert {'Dimensions': {'Key': 'REGION', 'Values': ['']}} in examples
1788+
1789+
def test_filter_grammar_allows_one_empty_string_value(self, tool):
1790+
"""The grammar's "Values must not be empty" rule does not forbid the [""] filter."""
1791+
grammar = tool.description.split('Values per leaf type:')[1].split('\n- Tags')[0]
1792+
assert '`[""]` (one empty string) is not' in grammar
1793+
17601794
def test_description_prefers_monthly_for_whole_month_rankings(self, tool):
17611795
"""Whole-month totals and rankings are steered to MONTHLY; the code default stays DAILY."""
17621796
assert 'DAILY granularity for periods <3 months' not in tool.description

0 commit comments

Comments
 (0)