Skip to content

Commit eae52c9

Browse files
fix(billing-cost-management-mcp-server): list the exact cost-explorer RECORD_TYPE values (#4874)
* fix(billing-cost-management-mcp-server): list the exact cost-explorer RECORD_TYPE values The cost-explorer DIMENSION REFERENCE described RECORD_TYPE as "Charge types (e.g., RI fees, usage costs)". Cost Explorer matches RECORD_TYPE values exactly and case-sensitively, so a filter on a guessed value such as "RI fees" returns no rows and reads as $0. The entry now lists the values Cost Explorer returns and matches: Usage, DiscountedUsage, SavingsPlanCoveredUsage, SavingsPlanNegation, SavingsPlanRecurringFee, SavingsPlanUpfrontFee, Recurring, Upfront, Support, Other, Tax, Credit, Refund, and the seven discount types by full name (for example Bundled Discount). It says that the CUR and Cost Categories line-item types RIFee and Fee match nothing in a Cost Explorer filter and names Recurring, Upfront and Other to use instead. It points to getDimensionValues for the account's own list. Adds a description contract test for the entry. * fix(billing-cost-management-mcp-server): use real Cost Explorer values in dimension examples Five cost-explorer DIMENSION REFERENCE entries and the cost-comparison PLATFORM entry gave example values that Cost Explorer does not return: Aurora, SingleAZ/MultiAZ, Compute Optimized/Memory Optimized, Linux and On-Demand. Dimension filters are exact, so a model that filters on one of them gets no rows and reads the result as $0. The examples are now values Cost Explorer returns: Aurora MySQL, Aurora PostgreSQL, MySQL and PostgreSQL for DATABASE_ENGINE; Single-AZ and Multi-AZ for DEPLOYMENT_OPTION; m5, t3 and c6g for INSTANCE_TYPE_FAMILY, noting that the case varies by service (r5 and R5 are separate values); Linux/UNIX and Windows (Amazon VPC) for OPERATING_SYSTEM and PLATFORM (cost-explorer PLATFORM had no examples); On Demand Instances, Standard Reserved Instances and Savings Plans for PURCHASE_TYPE. DATABASE_ENGINE and DEPLOYMENT_OPTION no longer say RDS only, since other services report them too. Adds a parametrized description contract test for the seven entries. * fix(billing-cost-management-mcp-server): correct the RECORD_TYPE note on the CUR Fee type The RECORD_TYPE entry said that the CUR line-item types RIFee and Fee match nothing in a Cost Explorer filter. That holds for RIFee, which Cost Explorer always reports as Recurring. It does not hold for every Fee row: Cost Explorer reports most of them as Upfront, Other or Support, but a fee that is none of those keeps the Fee type. A Fee filter therefore misses most fees rather than matching nothing. The note now says that, and the test and CHANGELOG follow it. * chore(billing-cost-management-mcp-server): drop the CHANGELOG entries for the dimension fixes These are non-breaking description fixes. Leaving CHANGELOG.md untouched avoids rebase conflicts with other changes that edit the Unreleased section. * fix(billing-cost-management-mcp-server): say unmapped CUR types pass through in RECORD_TYPE The RECORD_TYPE entry called its list "Exact" values, which reads as a complete list, and said only that Fee "can remain". When building the record type, Cost Explorer maps discounts, support charges, subscription fees and RIFee, keeps a fixed set of types unchanged, and passes any other CUR line-item type through as-is, for example a Fee with no subscription that is not a support charge. The entry now says the values are case-sensitive without calling the list exact, and states the pass-through. The test pins both.
1 parent b9b0c94 commit eae52c9

3 files changed

Lines changed: 143 additions & 8 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def _starts_before_default_history(today: date, *starts: str) -> bool:
237237
- REGION: AWS Region
238238
- USAGE_TYPE: Type of usage (e.g., BoxUsage:t2.micro)
239239
- INSTANCE_TYPE: EC2 instance type (e.g., t2.micro, m5.large)
240-
- PLATFORM: Operating system (e.g., Windows, Linux)
240+
- PLATFORM: Operating system (e.g., Linux/UNIX, Windows (Amazon VPC))
241241
- TENANCY: Instance tenancy. Exact, case-sensitive values: Shared, Dedicated, Host
242242
- RECORD_TYPE: Record type (e.g., Usage, Credit, Tax)
243243
- LEGAL_ENTITY_NAME: AWS seller of record

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,18 +219,20 @@ def _with_example_dates(description: str) -> str:
219219
- AZ: The Availability Zone (e.g., us-east-1a)
220220
- BILLING_ENTITY: Who bills the charge - "AWS" for AWS services, "AWS Marketplace" for
221221
third-party Marketplace purchases. This is the key for Marketplace spend, NOT LEGAL_ENTITY_NAME
222-
- DATABASE_ENGINE: The Amazon RDS database (e.g., Aurora, MySQL)
223-
- DEPLOYMENT_OPTION: RDS deployment scope (SingleAZ, MultiAZ)
222+
- DATABASE_ENGINE: The database engine (e.g., Aurora MySQL, Aurora PostgreSQL, MySQL, PostgreSQL)
223+
- DEPLOYMENT_OPTION: Deployment option (e.g., Single-AZ, Multi-AZ)
224224
- INSTANCE_TYPE: The EC2 instance type (e.g., m4.xlarge)
225-
- INSTANCE_TYPE_FAMILY: Family of instances (e.g., Compute Optimized, Memory Optimized)
225+
- INSTANCE_TYPE_FAMILY: Instance family (e.g., m5, t3, c6g); the case can vary by service (e.g., r5
226+
and R5 are separate values)
226227
- INVOICING_ENTITY: The AWS entity that issued the invoice (e.g., Amazon Web Services, Inc.)
227228
- LEGAL_ENTITY_NAME: The AWS seller of record (e.g., Amazon Web Services, Inc.); it does not
228229
separate Marketplace from AWS charges
229230
- LINKED_ACCOUNT: AWS member accounts
230-
- OPERATING_SYSTEM: OS type (e.g., Windows, Linux)
231+
- OPERATING_SYSTEM: OS type (e.g., Linux/UNIX, Windows (Amazon VPC))
231232
- OPERATION: The API operation behind a charge (e.g., RunInstances, PutObject; for Amazon Bedrock, model-invocation operations). The dimension is OPERATION — there is no API_OPERATION dimension.
232-
- PLATFORM: EC2 operating system
233-
- PURCHASE_TYPE: Reservation type (e.g., On-Demand, Reserved)
233+
- PLATFORM: EC2 operating system (e.g., Linux/UNIX, Windows (Amazon VPC))
234+
- PURCHASE_TYPE: Purchase option (e.g., On Demand Instances, Standard Reserved Instances, Savings
235+
Plans)
234236
- REGION: AWS Region
235237
- RESOURCE_ID: Resource identifier. Only for getCostAndUsageWithResources (last 14 days)
236238
- SERVICE: AWS service (e.g., Amazon DynamoDB)
@@ -239,7 +241,18 @@ def _with_example_dates(description: str) -> str:
239241
Host (Dedicated Host)
240242
- USAGE_TYPE: Usage type (e.g., DataTransfer-In-Bytes)
241243
- USAGE_TYPE_GROUP: Group of related usage types (e.g., EC2: Running Hours)
242-
- RECORD_TYPE: Charge types (e.g., RI fees, usage costs)
244+
- RECORD_TYPE: Charge type. Values are case-sensitive: Usage, DiscountedUsage (RI-covered
245+
usage), SavingsPlanCoveredUsage, SavingsPlanNegation, SavingsPlanRecurringFee,
246+
SavingsPlanUpfrontFee, Recurring (RI recurring fee), Upfront (RI upfront fee), Support, Other
247+
(out-of-cycle and non-RI subscription charges), Tax, Credit, Refund, and these discounts by full
248+
name: Bundled Discount, Distributor Discount, Enterprise Discount Program Discount, Marketplace
249+
Discount, Private Rate Card Discount, RI Volume Discount, Solution Provider Program Discount.
250+
These differ from the CUR and Cost Categories line-item types: Cost Explorer reports RIFee as
251+
Recurring, so a RIFee filter matches nothing, and reports most CUR Fee rows as Upfront, Other or
252+
Support, so a Fee filter misses them (use Upfront for RI upfront fees, Other for other
253+
subscription fees). Any other CUR line-item type, such as a Fee with no subscription that is not
254+
a support charge, passes through unchanged. Call getDimensionValues with RECORD_TYPE for the
255+
values present in the account.
243256
244257
BILLING VIEW SUPPORT:
245258
Most Cost Explorer operations support an optional billing_view_arn parameter that allows you to scope

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,6 +1635,128 @@ def test_tenancy_reference_uses_exact_ce_values(self, module_name, tool_name, se
16351635
for wrong in ('shared', 'dedicated', 'default', 'SHARED', 'DEDICATED', 'HOST'):
16361636
assert not re.search(rf'\b{wrong}\b', tenancy)
16371637

1638+
def test_record_type_reference_uses_exact_ce_values(self, tool):
1639+
"""RECORD_TYPE lists the values Cost Explorer matches, not CUR codes or prose."""
1640+
match = re.search(r'^- RECORD_TYPE:.*(?:\n .*)*', tool.description, re.MULTILINE)
1641+
assert match is not None
1642+
# Re-join wrapped lines so multi-word values split across a line break still match.
1643+
record_type = ' '.join(match.group(0).split())
1644+
assert 'case-sensitive' in record_type
1645+
for value in (
1646+
'Usage',
1647+
'DiscountedUsage',
1648+
'SavingsPlanCoveredUsage',
1649+
'SavingsPlanNegation',
1650+
'SavingsPlanRecurringFee',
1651+
'SavingsPlanUpfrontFee',
1652+
'Recurring',
1653+
'Upfront',
1654+
'Support',
1655+
'Other',
1656+
'Tax',
1657+
'Credit',
1658+
'Refund',
1659+
'Bundled Discount',
1660+
'Enterprise Discount Program Discount',
1661+
'Private Rate Card Discount',
1662+
'RI Volume Discount',
1663+
'Solution Provider Program Discount',
1664+
'Distributor Discount',
1665+
'Marketplace Discount',
1666+
):
1667+
assert re.search(rf'\b{value}\b', record_type), value
1668+
# The old prose guesses are gone, and the CUR codes are named only as non-values.
1669+
assert 'e.g., RI fees, usage costs' not in record_type
1670+
assert 'reports RIFee as Recurring, so a RIFee filter matches nothing' in record_type
1671+
assert 'most CUR Fee rows as Upfront, Other or Support' in record_type
1672+
assert 'use Upfront for RI upfront fees, Other for other subscription fees' in record_type
1673+
assert 'RIFee and Fee match nothing' not in record_type
1674+
# The list is not exhaustive: any unmapped CUR line-item type passes through unchanged.
1675+
assert 'Exact' not in record_type
1676+
assert 'Any other CUR line-item type' in record_type
1677+
assert 'passes through unchanged' in record_type
1678+
assert 'getDimensionValues with RECORD_TYPE' in record_type
1679+
1680+
@pytest.mark.parametrize(
1681+
'module_name, tool_name, server_attr, key, values, wrong',
1682+
[
1683+
(
1684+
'cost_explorer_tools',
1685+
'cost-explorer',
1686+
'cost_explorer_server',
1687+
'DATABASE_ENGINE',
1688+
('Aurora MySQL', 'Aurora PostgreSQL', 'MySQL', 'PostgreSQL'),
1689+
('Aurora',),
1690+
),
1691+
(
1692+
'cost_explorer_tools',
1693+
'cost-explorer',
1694+
'cost_explorer_server',
1695+
'DEPLOYMENT_OPTION',
1696+
('Single-AZ', 'Multi-AZ'),
1697+
('SingleAZ', 'MultiAZ'),
1698+
),
1699+
(
1700+
'cost_explorer_tools',
1701+
'cost-explorer',
1702+
'cost_explorer_server',
1703+
'INSTANCE_TYPE_FAMILY',
1704+
('m5', 't3', 'c6g', 'r5', 'R5'),
1705+
('Compute Optimized', 'Memory Optimized'),
1706+
),
1707+
(
1708+
'cost_explorer_tools',
1709+
'cost-explorer',
1710+
'cost_explorer_server',
1711+
'OPERATING_SYSTEM',
1712+
('Linux/UNIX', 'Windows (Amazon VPC)'),
1713+
('Windows', 'Linux'),
1714+
),
1715+
(
1716+
'cost_explorer_tools',
1717+
'cost-explorer',
1718+
'cost_explorer_server',
1719+
'PLATFORM',
1720+
('Linux/UNIX', 'Windows (Amazon VPC)'),
1721+
('Windows', 'Linux'),
1722+
),
1723+
(
1724+
'cost_explorer_tools',
1725+
'cost-explorer',
1726+
'cost_explorer_server',
1727+
'PURCHASE_TYPE',
1728+
('On Demand Instances', 'Standard Reserved Instances', 'Savings Plans'),
1729+
('On-Demand',),
1730+
),
1731+
(
1732+
'cost_comparison_tools',
1733+
'cost-comparison',
1734+
'cost_comparison_server',
1735+
'PLATFORM',
1736+
('Linux/UNIX', 'Windows (Amazon VPC)'),
1737+
('Windows', 'Linux'),
1738+
),
1739+
],
1740+
)
1741+
def test_dimension_examples_use_exact_ce_values(
1742+
self, module_name, tool_name, server_attr, key, values, wrong
1743+
):
1744+
"""Dimension examples are values Cost Explorer returns, not console or pricing labels."""
1745+
module = importlib.reload(
1746+
importlib.import_module(
1747+
f'awslabs.billing_cost_management_mcp_server.tools.{module_name}'
1748+
)
1749+
)
1750+
description = _registered_tool(getattr(module, server_attr), tool_name).description or ''
1751+
match = re.search(rf'^- {key}:.*(?:\n .*)*', description, re.MULTILINE)
1752+
assert match is not None
1753+
entry = ' '.join(match.group(0).split())
1754+
for value in values:
1755+
assert re.search(rf'(?<![\w-]){re.escape(value)}(?![\w-])', entry), value
1756+
for value in wrong:
1757+
# A wrong value only counts as a whole list item, so 'Aurora MySQL' is not 'Aurora'.
1758+
assert not re.search(rf'[ (]{re.escape(value)}(?=[,);.]|$)', entry), value
1759+
16381760
def test_description_prefers_monthly_for_whole_month_rankings(self, tool):
16391761
"""Whole-month totals and rankings are steered to MONTHLY; the code default stays DAILY."""
16401762
assert 'DAILY granularity for periods <3 months' not in tool.description

0 commit comments

Comments
 (0)