Normalize MCP response structures across all tools
Implement changes from MCP_NORMALIZATION_FIX.md to provide consistent
response structure across all MCP tools. This fixes the bot integration
issue where different tools returned data in different structures.
Changes:
- get_valuation_comparables:
- Rename "comparables" → "deals"
- Move "total_comparables" → "market_statistics.deal_breakdown.total_deals"
- Add "search_parameters" section with "filters_applied"
- Move "statistics" → "market_statistics"
- find_recent_deals_for_address:
- Rename "price_stats.average_price" → "price_statistics.mean"
- Rename "price_stats.median_price" → "price_statistics.median"
- Rename "area_stats" → "area_statistics"
- Rename "price_per_sqm_stats" → "price_per_sqm_statistics"
- get_deal_statistics:
- Add "search_parameters" section
- Move "statistics" → "market_statistics"
- Add "market_statistics.deal_breakdown.total_deals"
- analyze_market_trends:
- Add "market_statistics.deal_breakdown.total_deals"
- Keep existing tool-specific fields (yearly_trends, etc.)
- get_market_activity_metrics:
- Add "market_statistics.deal_breakdown.total_deals"
- Keep existing tool-specific metrics
All tools now follow standard structure:
{
"search_parameters" or "analysis_parameters": {...},
"market_statistics": {
"deal_breakdown": {"total_deals": N},
"price_statistics": {"mean": ..., "median": ...},
"area_statistics": {...},
"price_per_sqm_statistics": {...}
},
"deals": [...]
}
Updated tests to match new normalized structure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -75,11 +75,12 @@ class TestMCPToolsE2E:
|
||||
result = analyze_market_trends(self.TEST_ADDRESS_1, years_back=3, radius_meters=100)
|
||||
data = json.loads(result)
|
||||
|
||||
# Check response structure
|
||||
assert "market_summary" in data
|
||||
assert "total_deals" in data["market_summary"]
|
||||
assert isinstance(data["market_summary"]["total_deals"], int)
|
||||
assert data["market_summary"]["total_deals"] >= 0
|
||||
# Check response structure (normalized in MCP_NORMALIZATION_FIX)
|
||||
assert "market_statistics" in data
|
||||
assert "deal_breakdown" in data["market_statistics"]
|
||||
assert "total_deals" in data["market_statistics"]["deal_breakdown"]
|
||||
assert isinstance(data["market_statistics"]["deal_breakdown"]["total_deals"], int)
|
||||
assert data["market_statistics"]["deal_breakdown"]["total_deals"] >= 0
|
||||
|
||||
def test_get_valuation_comparables(self):
|
||||
"""Test getting valuation comparables."""
|
||||
@@ -88,13 +89,17 @@ class TestMCPToolsE2E:
|
||||
)
|
||||
data = json.loads(result)
|
||||
|
||||
assert "total_comparables" in data
|
||||
assert isinstance(data["total_comparables"], int)
|
||||
assert data["total_comparables"] >= 0
|
||||
# Normalized structure: total_comparables -> market_statistics.deal_breakdown.total_deals
|
||||
assert "market_statistics" in data
|
||||
assert "deal_breakdown" in data["market_statistics"]
|
||||
assert "total_deals" in data["market_statistics"]["deal_breakdown"]
|
||||
assert isinstance(data["market_statistics"]["deal_breakdown"]["total_deals"], int)
|
||||
assert data["market_statistics"]["deal_breakdown"]["total_deals"] >= 0
|
||||
|
||||
if data["total_comparables"] > 0:
|
||||
assert "comparables" in data
|
||||
comp = data["comparables"][0]
|
||||
# Normalized structure: comparables -> deals
|
||||
if data["market_statistics"]["deal_breakdown"]["total_deals"] > 0:
|
||||
assert "deals" in data
|
||||
comp = data["deals"][0]
|
||||
assert "deal_amount" in comp
|
||||
# rooms field is optional and excluded when None
|
||||
# Just verify the comparable has basic required fields
|
||||
@@ -105,11 +110,13 @@ class TestMCPToolsE2E:
|
||||
result = get_deal_statistics(self.TEST_ADDRESS_1, years_back=3)
|
||||
data = json.loads(result)
|
||||
|
||||
# Check response structure
|
||||
assert "statistics" in data
|
||||
if "sample_size" in data["statistics"]:
|
||||
assert isinstance(data["statistics"]["sample_size"], int)
|
||||
assert data["statistics"]["sample_size"] >= 0
|
||||
# Check response structure (normalized: statistics -> market_statistics)
|
||||
assert "market_statistics" in data
|
||||
# Check for total_deals in normalized location
|
||||
if "deal_breakdown" in data["market_statistics"]:
|
||||
assert "total_deals" in data["market_statistics"]["deal_breakdown"]
|
||||
assert isinstance(data["market_statistics"]["deal_breakdown"]["total_deals"], int)
|
||||
assert data["market_statistics"]["deal_breakdown"]["total_deals"] >= 0
|
||||
|
||||
def test_get_market_activity_metrics(self):
|
||||
"""Test market activity metrics."""
|
||||
|
||||
@@ -364,10 +364,13 @@ class TestGetValuationComparables:
|
||||
)
|
||||
parsed = json.loads(result)
|
||||
|
||||
assert "filters_applied" in parsed
|
||||
assert "statistics" in parsed
|
||||
assert "comparables" in parsed
|
||||
assert parsed["filters_applied"]["property_type"] == "דירה"
|
||||
# Normalized structure: filters_applied is now in search_parameters
|
||||
assert "search_parameters" in parsed
|
||||
assert "filters_applied" in parsed["search_parameters"]
|
||||
assert parsed["search_parameters"]["filters_applied"]["property_type"] == "דירה"
|
||||
# Normalized structure: comparables -> deals
|
||||
assert "deals" in parsed
|
||||
assert "market_statistics" in parsed
|
||||
|
||||
@patch("nadlan_mcp.fastmcp_server.client")
|
||||
def test_comparables_strips_bloat(self, mock_client):
|
||||
@@ -392,7 +395,8 @@ class TestGetValuationComparables:
|
||||
result = fastmcp_server.get_valuation_comparables("test address")
|
||||
parsed = json.loads(result)
|
||||
|
||||
comparable = parsed["comparables"][0]
|
||||
# Normalized structure: comparables -> deals
|
||||
comparable = parsed["deals"][0]
|
||||
assert "shape" not in comparable
|
||||
assert "sourceorder" not in comparable
|
||||
# source_polygon_id is kept when added by processing
|
||||
@@ -421,9 +425,12 @@ class TestGetDealStatistics:
|
||||
result = fastmcp_server.get_deal_statistics("test address")
|
||||
parsed = json.loads(result)
|
||||
|
||||
assert "address" in parsed
|
||||
assert "statistics" in parsed
|
||||
assert parsed["statistics"]["total_deals"] == 2 # Field name is total_deals in model
|
||||
# Normalized structure: address is now in search_parameters, statistics -> market_statistics
|
||||
assert "search_parameters" in parsed
|
||||
assert "address" in parsed["search_parameters"]
|
||||
assert "market_statistics" in parsed
|
||||
assert "deal_breakdown" in parsed["market_statistics"]
|
||||
assert parsed["market_statistics"]["deal_breakdown"]["total_deals"] == 2
|
||||
|
||||
|
||||
class TestGetMarketActivityMetrics:
|
||||
|
||||
Reference in New Issue
Block a user