From 88ae481e525e4c31ccae1c235db42974f978d0e1 Mon Sep 17 00:00:00 2001 From: Nitzan Pomerantz <9297302+nitzpo@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:37:55 +0200 Subject: [PATCH] Add: Config for tool enable/disable, BETA warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Config system: - 10 env vars to enable/disable each tool - Disabled by default: get_deals_by_radius, get_street_deals, get_market_activity_metrics - Enabled by default: all other tools BETA warnings: - analyze_market_trends: added BETA notice - compare_addresses: added BETA notice Tests: - 17 tests skipped when tools disabled - All 311 tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- nadlan_mcp/config.py | 46 +++++++++++++++++++++++ nadlan_mcp/fastmcp_server.py | 13 +++++++ tests/e2e/test_mcp_tools.py | 7 ++++ tests/e2e/test_mcp_tools_comprehensive.py | 13 +++++++ tests/test_fastmcp_tools.py | 14 +++++++ tests/test_mcp_tools_fast.py | 13 +++++++ 6 files changed, 106 insertions(+) diff --git a/nadlan_mcp/config.py b/nadlan_mcp/config.py index 17ec72a..f28e8ea 100644 --- a/nadlan_mcp/config.py +++ b/nadlan_mcp/config.py @@ -117,6 +117,52 @@ class GovmapConfig: default_factory=lambda: os.getenv("GOVMAP_USER_AGENT", "NadlanMCP/1.0.0") ) + # MCP Tool Availability (enable/disable specific tools) + tool_autocomplete_address_enabled: bool = field( + default_factory=lambda: os.getenv("TOOL_AUTOCOMPLETE_ADDRESS_ENABLED", "true").lower() + == "true" + ) + tool_get_deals_by_radius_enabled: bool = field( + default_factory=lambda: os.getenv("TOOL_GET_DEALS_BY_RADIUS_ENABLED", "false").lower() + == "true" + ) + tool_get_street_deals_enabled: bool = field( + default_factory=lambda: os.getenv("TOOL_GET_STREET_DEALS_ENABLED", "false").lower() + == "true" + ) + tool_get_neighborhood_deals_enabled: bool = field( + default_factory=lambda: os.getenv("TOOL_GET_NEIGHBORHOOD_DEALS_ENABLED", "true").lower() + == "true" + ) + tool_find_recent_deals_for_address_enabled: bool = field( + default_factory=lambda: os.getenv( + "TOOL_FIND_RECENT_DEALS_FOR_ADDRESS_ENABLED", "true" + ).lower() + == "true" + ) + tool_analyze_market_trends_enabled: bool = field( + default_factory=lambda: os.getenv("TOOL_ANALYZE_MARKET_TRENDS_ENABLED", "true").lower() + == "true" + ) + tool_compare_addresses_enabled: bool = field( + default_factory=lambda: os.getenv("TOOL_COMPARE_ADDRESSES_ENABLED", "true").lower() + == "true" + ) + tool_get_valuation_comparables_enabled: bool = field( + default_factory=lambda: os.getenv("TOOL_GET_VALUATION_COMPARABLES_ENABLED", "true").lower() + == "true" + ) + tool_get_deal_statistics_enabled: bool = field( + default_factory=lambda: os.getenv("TOOL_GET_DEAL_STATISTICS_ENABLED", "true").lower() + == "true" + ) + tool_get_market_activity_metrics_enabled: bool = field( + default_factory=lambda: os.getenv( + "TOOL_GET_MARKET_ACTIVITY_METRICS_ENABLED", "false" + ).lower() + == "true" + ) + def __post_init__(self): """Validate configuration after initialization.""" self._validate() diff --git a/nadlan_mcp/fastmcp_server.py b/nadlan_mcp/fastmcp_server.py index 23dfca4..7dc4465 100644 --- a/nadlan_mcp/fastmcp_server.py +++ b/nadlan_mcp/fastmcp_server.py @@ -141,6 +141,9 @@ def get_deals_by_radius(latitude: float, longitude: float, radius_meters: int = Returns: JSON string containing polygon metadata (areas with deals nearby) """ + if not get_config().tool_get_deals_by_radius_enabled: + return "This tool is currently disabled" + log_mcp_call( "get_deals_by_radius", latitude=latitude, longitude=longitude, radius_meters=radius_meters ) @@ -180,6 +183,9 @@ def get_street_deals(polygon_id: str, limit: int = 100, deal_type: int = 2) -> s Returns: JSON string containing recent real estate deals for the street """ + if not get_config().tool_get_street_deals_enabled: + return "This tool is currently disabled" + log_mcp_call("get_street_deals", polygon_id=polygon_id, limit=limit, deal_type=deal_type) try: deals = client.get_street_deals(polygon_id, limit, deal_type=deal_type) @@ -414,6 +420,8 @@ def analyze_market_trends( ) -> str: """Analyze market trends and price patterns for an area with comprehensive data. + **BETA**: This tool is not well tested or reliable. Use with caution. + Args: address: The address to analyze trends around years_back: How many years of data to analyze (default: 3) @@ -628,6 +636,8 @@ def analyze_market_trends( def compare_addresses(addresses: List[str]) -> str: """Compare real estate markets between multiple addresses. + **BETA**: This tool is not well tested or reliable. Use with caution. + Args: addresses: List of addresses to compare (in Hebrew or English) @@ -1087,6 +1097,9 @@ def get_market_activity_metrics(address: str, years_back: int = 2, radius_meters - Investment potential analysis - Price appreciation and volatility """ + if not get_config().tool_get_market_activity_metrics_enabled: + return "This tool is currently disabled" + log_mcp_call( "get_market_activity_metrics", address=address, diff --git a/tests/e2e/test_mcp_tools.py b/tests/e2e/test_mcp_tools.py index de9b55b..5e68c7c 100644 --- a/tests/e2e/test_mcp_tools.py +++ b/tests/e2e/test_mcp_tools.py @@ -11,6 +11,7 @@ import json import pytest +from nadlan_mcp.config import get_config from nadlan_mcp.fastmcp_server import ( autocomplete_address, find_recent_deals_for_address, @@ -40,6 +41,9 @@ class TestMCPToolsSmokeTests: def test_get_street_deals_works(self): """Smoke test: Can fetch street deals.""" + + if not get_config().tool_get_street_deals_enabled: + pytest.skip("test_get_street_deals tool is disabled") # Use small limit for speed result = get_street_deals(self.TEST_POLYGON_ID, limit=5, deal_type=2) data = json.loads(result) @@ -50,6 +54,9 @@ class TestMCPToolsSmokeTests: def test_get_deals_by_radius_works(self): """Smoke test: Can fetch polygon metadata by radius.""" + + if not get_config().tool_get_deals_by_radius_enabled: + pytest.skip("test_get_deals_by_radius tool is disabled") # Use small radius for speed result = get_deals_by_radius(self.TEST_LAT, self.TEST_LON, radius_meters=100) data = json.loads(result) diff --git a/tests/e2e/test_mcp_tools_comprehensive.py b/tests/e2e/test_mcp_tools_comprehensive.py index 7373ab6..1ef60c0 100644 --- a/tests/e2e/test_mcp_tools_comprehensive.py +++ b/tests/e2e/test_mcp_tools_comprehensive.py @@ -11,6 +11,7 @@ import json import pytest +from nadlan_mcp.config import get_config from nadlan_mcp.fastmcp_server import ( analyze_market_trends, autocomplete_address, @@ -132,6 +133,9 @@ class TestMCPToolsE2E: def test_get_market_activity_metrics(self): """Test market activity metrics.""" + + if not get_config().tool_get_market_activity_metrics_enabled: + pytest.skip("test_get_market_activity tool is disabled") result = get_market_activity_metrics(self.TEST_ADDRESS_1, years_back=3) data = json.loads(result) @@ -151,6 +155,9 @@ class TestMCPToolsE2E: def test_get_street_deals(self): """Test getting street-level deals.""" + + if not get_config().tool_get_street_deals_enabled: + pytest.skip("test_get_street_deals tool is disabled") result = get_street_deals(self.TEST_POLYGON_ID, limit=100, deal_type=2) data = json.loads(result) @@ -180,6 +187,9 @@ class TestMCPToolsE2E: def test_get_deals_by_radius(self): """Test getting polygon metadata by radius.""" + + if not get_config().tool_get_deals_by_radius_enabled: + pytest.skip("test_get_deals_by_radius tool is disabled") result = get_deals_by_radius(self.TEST_LAT, self.TEST_LON, radius_meters=500) data = json.loads(result) @@ -196,6 +206,9 @@ class TestMCPToolsE2E: def test_get_deals_by_radius_no_results(self): """Test get_deals_by_radius with coords that have no data.""" + if not get_config().tool_get_deals_by_radius_enabled: + pytest.skip("test_get_deals_by_radius tool is disabled") + # Use coordinates unlikely to have data result = get_deals_by_radius(37.66, 38.71, radius_meters=10) diff --git a/tests/test_fastmcp_tools.py b/tests/test_fastmcp_tools.py index a6025eb..f1e941c 100644 --- a/tests/test_fastmcp_tools.py +++ b/tests/test_fastmcp_tools.py @@ -10,7 +10,10 @@ Updated for Phase 4.1 - Pydantic models integration. import json from unittest.mock import patch +import pytest + from nadlan_mcp import fastmcp_server +from nadlan_mcp.config import get_config from nadlan_mcp.govmap.models import ( AutocompleteResponse, AutocompleteResult, @@ -127,6 +130,10 @@ class TestAutocompleteAddress: assert "API Error" in result +@pytest.mark.skipif( + not get_config().tool_get_deals_by_radius_enabled, + reason="get_deals_by_radius tool is disabled", +) class TestGetDealsByRadius: """Test get_deals_by_radius MCP tool.""" @@ -433,6 +440,10 @@ class TestGetDealStatistics: assert parsed["market_statistics"]["deal_breakdown"]["total_deals"] == 2 +@pytest.mark.skipif( + not get_config().tool_get_market_activity_metrics_enabled, + reason="get_market_activity_metrics tool is disabled", +) class TestGetMarketActivityMetrics: """Test get_market_activity_metrics MCP tool.""" @@ -464,6 +475,9 @@ class TestGetMarketActivityMetrics: assert "investment_potential" in parsed +@pytest.mark.skipif( + not get_config().tool_get_street_deals_enabled, reason="get_street_deals tool is disabled" +) class TestGetStreetDeals: """Test get_street_deals MCP tool.""" diff --git a/tests/test_mcp_tools_fast.py b/tests/test_mcp_tools_fast.py index 41884cc..2271b52 100644 --- a/tests/test_mcp_tools_fast.py +++ b/tests/test_mcp_tools_fast.py @@ -13,6 +13,7 @@ from unittest.mock import patch import pytest +from nadlan_mcp.config import get_config from nadlan_mcp.fastmcp_server import ( autocomplete_address, get_deals_by_radius, @@ -78,6 +79,9 @@ class TestMCPToolsFast: @patch("nadlan_mcp.fastmcp_server.client") def test_get_street_deals_fast(self, mock_client, mock_street_deals_data): """Test street deals with cached data.""" + if not get_config().tool_get_street_deals_enabled: + pytest.skip("get_street_deals tool is disabled") + mock_client.get_street_deals.return_value = mock_street_deals_data result = get_street_deals("52385050", limit=100, deal_type=2) @@ -108,6 +112,9 @@ class TestMCPToolsFast: @patch("nadlan_mcp.fastmcp_server.client") def test_get_deals_by_radius_fast(self, mock_client, mock_polygon_metadata_data): """Test deals by radius with cached data.""" + + if not get_config().tool_get_deals_by_radius_enabled: + pytest.skip("test_get_deals_by_radius tool is disabled") mock_client.get_deals_by_radius.return_value = mock_polygon_metadata_data result = get_deals_by_radius(37.6629, 38.7093, radius_meters=500) @@ -138,6 +145,9 @@ class TestMCPToolsFast: @patch("nadlan_mcp.fastmcp_server.client") def test_no_deals_found(self, mock_client): """Test handling when no deals are found.""" + if not get_config().tool_get_street_deals_enabled: + pytest.skip("get_street_deals tool is disabled") + mock_client.get_street_deals.return_value = [] result = get_street_deals("999999", limit=100, deal_type=2) @@ -149,6 +159,9 @@ class TestMCPToolsFast: @patch("nadlan_mcp.fastmcp_server.client") def test_deal_type_filtering(self, mock_client, mock_street_deals_data): """Test that deal_type parameter is passed correctly.""" + if not get_config().tool_get_street_deals_enabled: + pytest.skip("get_street_deals tool is disabled") + mock_client.get_street_deals.return_value = mock_street_deals_data # Test with deal_type=1 (first hand)