Skip to content

Commit 3e0620f

Browse files
fix(billing-cost-management-mcp-server): format cost-optimization get_recommendation timestamp like list_recommendations (#4859)
get_recommendation built last_refresh_timestamp with str(), so a live call returned '2026-10-23 21:40:14+00:00' where list_recommendations returns '2026-10-23T21:40:14' for the same recommendation, and a response without lastRefreshTimestamp (GetRecommendation has no required output members) returned the string 'None'. It now uses format_timestamp, as list_recommendations does, and returns None when the field is absent. Also remove the `if not recommendation` warning branch. botocore always adds ResponseMetadata, so the response is never empty; a missing recommendation raises ResourceNotFoundException (confirmed live), which is already reported as a warning naming the ID. The branch only ran for a mocked {} response, and its two tests are removed.
1 parent 02cd0aa commit 3e0620f

2 files changed

Lines changed: 28 additions & 36 deletions

File tree

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

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -221,23 +221,9 @@ async def get_recommendation(
221221

222222
# Make the API call
223223
await ctx_logger.info(f'Fetching recommendation {recommendation_id}')
224-
response = coh_client.get_recommendation(**request_params)
225-
226-
# The response IS the recommendation data
227-
recommendation = response
228-
229-
if not recommendation:
230-
await ctx_logger.warning(
231-
f'No recommendation found for recommendation {recommendation_id}'
232-
)
233-
return format_response(
234-
'warning',
235-
{
236-
'recommendation_id': recommendation_id,
237-
'message': 'No recommendation found for the specified recommendation.',
238-
},
239-
'No recommendation found. The recommendation may not have optimization opportunities, or the recommendation ID may be incorrect.',
240-
)
224+
# The response IS the recommendation data. A missing recommendation raises
225+
# ResourceNotFoundException (handled below); boto3 never returns an empty response.
226+
recommendation = coh_client.get_recommendation(**request_params)
241227

242228
# Build response using actual API fields
243229
formatted_response = {
@@ -255,7 +241,7 @@ async def get_recommendation(
255241
'currency_code': recommendation.get('currencyCode'),
256242
'implementation_effort': recommendation.get('implementationEffort'),
257243
'source': recommendation.get('source'),
258-
'last_refresh_timestamp': str(recommendation.get('lastRefreshTimestamp')),
244+
'last_refresh_timestamp': format_timestamp(recommendation.get('lastRefreshTimestamp')),
259245
'lookback_period_in_days': recommendation.get('recommendationLookbackPeriodInDays'),
260246
'cost_calculation_lookback_period_in_days': recommendation.get(
261247
'costCalculationLookbackPeriodInDays'

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

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
EFFICIENCY_PARETO_MAX_PAGES,
3131
EFFICIENCY_PARETO_PAGE_SIZE,
3232
)
33-
from datetime import datetime
33+
from datetime import datetime, timezone
3434
from fastmcp import Context
3535
from unittest.mock import AsyncMock, MagicMock
3636

@@ -646,23 +646,6 @@ async def test_non_client_error_returns_structured_error(self, mock_context, moc
646646
class TestGetRecommendationErrorHandling:
647647
"""Test error handling scenarios for get_recommendation."""
648648

649-
async def test_empty_recommendation_response(self, mock_context, mock_coh_client):
650-
"""Test get_recommendation with empty recommendation in response."""
651-
mock_coh_client.get_recommendation.return_value = {}
652-
653-
result = await get_recommendation(mock_context, mock_coh_client, 'i-1234567890abcdef0')
654-
655-
assert result['status'] == 'warning'
656-
657-
async def test_no_recommendation_key(self, mock_context, mock_coh_client):
658-
"""Test get_recommendation with no recommendation key in response."""
659-
mock_coh_client.get_recommendation.return_value = {}
660-
661-
result = await get_recommendation(mock_context, mock_coh_client, 'i-1234567890abcdef0')
662-
663-
assert result['status'] == 'warning'
664-
assert 'No recommendation found' in result['message']
665-
666649
async def test_validation_exception(self, mock_context, mock_coh_client):
667650
"""Test get_recommendation ValidationException handling."""
668651
from botocore.exceptions import ClientError
@@ -1002,6 +985,29 @@ async def test_recommendation_without_optional_fields(self, mock_context, mock_c
1002985
assert rec['source'] is None
1003986
assert rec['lookback_period_in_days'] is None
1004987
assert rec['estimated_monthly_savings'] is None
988+
assert rec['last_refresh_timestamp'] is None
989+
990+
async def test_last_refresh_timestamp_matches_list_recommendations(
991+
self, mock_context, mock_coh_client
992+
):
993+
"""get_recommendation formats lastRefreshTimestamp the same way list_recommendations does."""
994+
refreshed = datetime(2026, 6, 1, tzinfo=timezone.utc)
995+
mock_coh_client.get_recommendation.return_value = {
996+
'recommendationId': 'rec-1',
997+
'lastRefreshTimestamp': refreshed,
998+
}
999+
mock_coh_client.list_recommendations.return_value = {
1000+
'items': [{'recommendationId': 'rec-1', 'lastRefreshTimestamp': refreshed}]
1001+
}
1002+
1003+
got = await get_recommendation(mock_context, mock_coh_client, 'rec-1')
1004+
listed = await list_recommendations(mock_context, mock_coh_client)
1005+
1006+
assert got['data']['last_refresh_timestamp'] == '2026-06-01T00:00:00'
1007+
assert (
1008+
listed['data']['recommendations'][0]['last_refresh_timestamp']
1009+
== got['data']['last_refresh_timestamp']
1010+
)
10051011

10061012
async def test_recommendation_with_cost_breakdown_no_implementation(
10071013
self, mock_context, mock_coh_client

0 commit comments

Comments
 (0)