# Nadlan-MCP Architecture ## Overview Nadlan-MCP is a Model Context Protocol (MCP) server that provides Israeli real estate data to AI agents (LLMs). The architecture follows a clean separation of concerns with three main layers: 1. **MCP Tools Layer** - Exposes functions to LLM clients 2. **Business Logic Layer** - Data analysis and processing 3. **API Client Layer** - Communicates with Govmap API ## Design Principles ### 1. MCP Provides Data, LLM Provides Intelligence The server's role is to retrieve, structure, and optionally summarize data. Complex analysis, decision-making, and predictions are left to the LLM client. **Example:** - ✅ MCP: Provides comparable property deals with filters - ✅ LLM: Analyzes comparables and estimates property value - ❌ MCP: Calculates ML-based property valuation ### 2. Structured by Default All tools return detailed, structured data by default. Optional `summarized_response` parameter provides condensed summaries when needed. ### 3. Reliability First - Automatic retry with exponential backoff - Rate limiting to respect API limits - Comprehensive input validation - Detailed error messages ### 4. Configuration Over Hard-coding All settings (timeouts, retries, rate limits) are configurable via environment variables or code. ## System Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ LLM Client (AI Agent) │ │ (Claude, GPT, etc.) │ └────────────────────────────┬────────────────────────────────┘ │ MCP Protocol ↓ ┌─────────────────────────────────────────────────────────────┐ │ MCP Tools Layer │ │ ┌───────────────┐ ┌─────────────────┐ ┌──────────────┐ │ │ │autocomplete │ │find_recent_deals│ │analyze_market│ │ │ │_address │ │_for_address │ │_trends │ │ │ └───────┬───────┘ └────────┬────────┘ └──────┬───────┘ │ │ │ │ │ │ │ ┌───────┴───────────────────┴────────────────────┴──────┐ │ │ │ fastmcp_server.py (Tool Definitions) │ │ │ └───────────────────────────┬───────────────────────────┘ │ └────────────────────────────┬─┴────────────────────────────┘ │ ↓ ┌─────────────────────────────────────────────────────────────┐ │ Business Logic Layer │ │ ┌────────────────────┐ ┌──────────────────────────────┐ │ │ │ Market Analysis │ │ Deal Processing & Filtering │ │ │ │ (analyze_market │ │ (_is_same_building, etc.) │ │ │ │ _trends logic) │ │ │ │ │ └────────────────────┘ └──────────────────────────────┘ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ govmap.py (GovmapClient) │ │ │ │ • Validation • Retry Logic • Rate Limiting │ │ │ └─────────────────────────┬──────────────────────────────┘ │ └───────────────────────────┬┴──────────────────────────────┘ │ HTTP/JSON ↓ ┌─────────────────────────────────────────────────────────────┐ │ Govmap API Layer │ │ (Israeli Government Real Estate API) │ │ ┌───────────────┐ ┌────────────┐ ┌──────────────────┐ │ │ │ Autocomplete │ │ Deals by │ │ Street/ │ │ │ │ Endpoint │ │ Radius │ │ Neighborhood │ │ │ │ │ │ Endpoint │ │ Deals Endpoints │ │ │ └───────────────┘ └────────────┘ └──────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## Component Details ### Configuration Layer (`config.py`) **Purpose:** Centralized configuration management **Key Components:** - `GovmapConfig` - Dataclass with all configuration settings - Environment variable support for all settings - Validation on initialization **Configuration Options:** - API settings (base URL, user agent) - Timeout settings (connect, read) - Retry settings (max retries, backoff) - Rate limiting (requests per second) - Default search parameters **Usage:** ```python from nadlan_mcp.config import get_config, set_config, GovmapConfig # Use global config config = get_config() # Override config custom_config = GovmapConfig(max_retries=5, requests_per_second=10.0) set_config(custom_config) ``` ### API Client Layer (`govmap.py`) **Purpose:** Communicate with Govmap API with reliability features **Key Components:** #### GovmapClient Class **Responsibilities:** - Make HTTP requests to Govmap API - Implement retry logic with exponential backoff - Enforce rate limiting - Validate inputs - Parse and validate responses **Key Methods:** - `autocomplete_address()` - Search for addresses - `get_gush_helka()` - Get block/parcel data - `get_deals_by_radius()` - Get nearby deals - `get_street_deals()` - Get street-level deals - `get_neighborhood_deals()` - Get neighborhood deals - `find_recent_deals_for_address()` - Main comprehensive search **Reliability Features:** 1. **Retry Logic** - Exponential backoff on failures 2. **Rate Limiting** - Tracks request times, enforces delays 3. **Input Validation** - Validates all inputs before API calls 4. **Timeouts** - Configurable connect and read timeouts ### MCP Tools Layer (`fastmcp_server.py`) **Purpose:** Expose functionality to LLM clients via MCP protocol **Key Components:** - FastMCP server instance - Tool definitions (decorated functions) - JSON response formatting - Error handling for user-friendly messages **Tool Design Pattern:** ```python @mcp.tool() def tool_name(param: type, summarized_response: bool = False) -> str: """ Tool description for LLM. Args: param: Parameter description summarized_response: - False (default): Full structured data for LLM processing - True: Condensed summary with key insights Returns: JSON string with data or summary """ # 1. Call business logic / API client # 2. Format response # 3. Return JSON string ``` ## Data Flow ### Example: Find Recent Deals for Address ``` 1. LLM Request └─> "Find deals for Sokolov 38 Holon in last 2 years" 2. MCP Tool: find_recent_deals_for_address() ├─> Validate inputs └─> Call GovmapClient 3. GovmapClient Workflow: ├─> autocomplete_address("סוקולוב 38 חולון") │ ├─> Rate limit check │ ├─> HTTP POST (with retry) │ └─> Parse coordinates │ ├─> get_deals_by_radius(coordinates, 30m) │ ├─> Rate limit check │ ├─> HTTP GET (with retry) │ └─> Extract polygon_ids │ ├─> For each polygon_id: │ ├─> get_street_deals(polygon_id) │ └─> get_neighborhood_deals(polygon_id) │ ├─> Filter & Prioritize: │ ├─> Same building deals (priority 0) │ ├─> Street deals (priority 1) │ └─> Neighborhood deals (priority 2) │ └─> Sort by priority then date 4. Format Response └─> JSON with deals + metadata 5. Return to LLM └─> LLM analyzes and responds to user ``` ## Error Handling Strategy ### Validation Errors (ValueError) - Invalid address format - Negative/zero parameters - Out-of-range values - **Action:** Immediate failure with clear message ### Network Errors (requests.RequestException) - Connection failures - Timeouts - HTTP errors - **Action:** Retry with exponential backoff (up to configured max_retries) ### API Response Errors - Invalid JSON - Unexpected format - Missing required fields - **Action:** Raise ValueError with specific details ### Rate Limiting - Track last request time - Sleep if necessary before request - **Action:** Transparent to caller, automatic ## Performance Considerations ### Current Implementation **Optimizations:** - Connection pooling (requests.Session) - Rate limiting prevents API throttling - Early validation reduces unnecessary API calls - Polygon deduplication in find_recent_deals_for_address() **Limitations:** - Synchronous (blocking) API calls - No caching (MVP decision) - Sequential polygon queries ### Future Optimizations (Not in MVP) **Phase 1: In-Memory Caching** - Cache autocomplete results (1 hour TTL) - Cache deal results (30 minute TTL) - Reduce API load by 60-80% **Phase 2: Async/Parallel** - Convert to async/await - Parallel polygon queries - 3-5x faster for multi-polygon searches **Phase 3: Production Caching** - Redis for distributed caching - Cache warming strategies - Cross-instance consistency ## Security & Rate Limiting ### Rate Limiting **Current:** 5 requests/second (configurable) **Rationale:** - Respects Govmap API (no published limits, being conservative) - Prevents accidental DoS - Balances responsiveness with safety **Implementation:** - Tracks last request timestamp - Sleeps if interval too short - Per-instance (not distributed) ### Input Validation All user inputs are validated before use: - Address strings: length, type, non-empty - Coordinates: numeric, reasonable bounds - Integers: positive, max values - Deal types: enum validation (1 or 2) **Prevents:** - Injection attacks - API errors from malformed requests - Expensive/dangerous operations ### API Key Management **Current:** No API keys required (public API) **Future:** If API keys added: - Store in environment variables only - Never log or expose in responses - Rotate regularly ## Testing Strategy ### Unit Tests - Mock API responses - Test validation logic - Test error handling - Test retry logic ### Integration Tests - Real API calls (marked `@pytest.mark.integration`) - VCR.py for recording/replaying - Test complete workflows ### Validation Tests - Invalid inputs - Edge cases - Boundary conditions ## Configuration Options ### Environment Variables ```bash # API Settings GOVMAP_BASE_URL=https://www.govmap.gov.il/api/ GOVMAP_USER_AGENT=NadlanMCP/1.0.0 # Timeouts (seconds) GOVMAP_CONNECT_TIMEOUT=10 GOVMAP_READ_TIMEOUT=30 # Retry Settings GOVMAP_MAX_RETRIES=3 GOVMAP_RETRY_MIN_WAIT=1 GOVMAP_RETRY_MAX_WAIT=10 # Rate Limiting GOVMAP_REQUESTS_PER_SECOND=5.0 # Defaults GOVMAP_DEFAULT_RADIUS=50 GOVMAP_DEFAULT_YEARS_BACK=2 GOVMAP_DEFAULT_DEAL_LIMIT=100 ``` ### Tuning Guidelines **High Reliability (Production):** - `MAX_RETRIES=5` - `RETRY_MAX_WAIT=30` - `REQUESTS_PER_SECOND=3.0` **High Performance (Development):** - `MAX_RETRIES=2` - `RETRY_MAX_WAIT=5` - `REQUESTS_PER_SECOND=10.0` **Conservative (Shared API):** - `MAX_RETRIES=3` - `RETRY_MAX_WAIT=10` - `REQUESTS_PER_SECOND=2.0` ## Future Architecture Evolution ### Phase 2: Data Models (Pydantic) Add structured models for type safety: ```python class Deal(BaseModel): deal_id: str address: str deal_amount: float asset_area: float price_per_sqm: float deal_date: datetime property_type: str # ... ``` ### Phase 3: Separation of Concerns ``` nadlan_mcp/ ├── api_client.py # Pure API communication ├── analyzers/ │ ├── market.py # Market analysis logic │ ├── valuation.py # Valuation helpers │ └── filtering.py # Deal filtering ├── models.py # Pydantic models └── fastmcp_server.py # Thin MCP tool definitions ``` ### Phase 4: Database Layer (Optional) For historical tracking and faster queries: ``` ├── database/ │ ├── models.py # SQLAlchemy models │ ├── crud.py # CRUD operations │ └── migrations/ # Alembic migrations ``` ## Deployment ### Development ```bash python run_fastmcp_server.py ``` ### Production (MCP Client) ```json { "servers": { "nadlan-mcp": { "command": "python", "args": ["/path/to/nadlan-mcp/run_fastmcp_server.py"], "env": { "GOVMAP_REQUESTS_PER_SECOND": "3.0", "GOVMAP_MAX_RETRIES": "5" } } } } ``` ## API Limitations ### Govmap API Constraints **Known Limitations:** - No published rate limits (being conservative with 5 req/sec) - No authentication required (public API) - Data freshness: Updated periodically (not real-time) - Coverage: Official Israeli government records only **Best Practices:** - Use appropriate time ranges (2-5 years recommended) - Limit radius searches (under 1000m for performance) - Cache results when possible (future feature) - Respect rate limiting ### MCP Protocol Constraints **Token Limits:** - Large deal lists can exceed LLM context windows - Use `summarized_response=True` for large datasets - Default limits (50-100 deals) are token-optimized ## Monitoring & Debugging ### Logging Levels **INFO:** Normal operations - Requests being made - Successful responses **WARNING:** Recoverable issues - Retrying after failures - Rate limiting delays - Validation warnings **ERROR:** Failures - Exhausted retries - Invalid responses - Configuration errors ### Debug Mode Enable debug logging: ```python import logging logging.basicConfig(level=logging.DEBUG) ``` ## References - [Govmap API](https://www.govmap.gov.il/) - [MCP Protocol](https://modelcontextprotocol.io/) - [FastMCP Documentation](https://github.com/jlowin/fastmcp) - [Israeli Real Estate Data](https://data.gov.il/)