Files
nadlan-mcp/ARCHITECTURE.md
Nitzan Pomerantz e4aa6487ff Ruff fixes
2025-10-30 22:24:40 +02:00

21 KiB

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                         │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │              govmap/ Package                            │ │
│  │  ┌─────────────┐  ┌──────────────┐  ┌───────────────┐ │ │
│  │  │ client.py   │  │ validators.py│  │ filters.py    │ │ │
│  │  │ (API calls) │  │ (validation) │  │ (filtering)   │ │ │
│  │  └─────────────┘  └──────────────┘  └───────────────┘ │ │
│  │  ┌─────────────┐  ┌──────────────┐  ┌───────────────┐ │ │
│  │  │statistics.py│  │market_       │  │ utils.py      │ │ │
│  │  │ (stats)     │  │analysis.py   │  │ (helpers)     │ │ │
│  │  └─────────────┘  └──────────────┘  └───────────────┘ │ │
│  │  • Retry Logic  • Rate Limiting  • Validation        │ │
│  └─────────────────────────┬───────────────────────────────┘ │
└───────────────────────────┬┴──────────────────────────────┘
                            │ 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:

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/ package)

Purpose: Modular package for Govmap API interaction and data processing

Package Structure:

  • models.py - Pydantic v2 data models (9 models, type-safe, validated)
  • client.py - GovmapClient class with API methods (returns models)
  • validators.py - Input validation functions
  • filters.py - Deal filtering logic (accepts/returns models)
  • statistics.py - Statistical calculations (returns models)
  • market_analysis.py - Market analysis functions (returns models)
  • utils.py - Helper utilities
  • __init__.py - Public API exports

Pydantic Models Layer (govmap/models.py) NEW in v2.0.0

Purpose: Type-safe, validated data models for all API responses and business logic

9 Comprehensive Models:

  • CoordinatePoint - ITM coordinates (frozen/immutable)
  • Address - Israeli address with optional coordinates
  • AutocompleteResult & AutocompleteResponse - Search results
  • Deal - Real estate transaction with computed price_per_sqm field
  • DealStatistics - Statistical aggregations
  • MarketActivityScore - Market activity metrics
  • InvestmentAnalysis - Investment potential analysis
  • LiquidityMetrics - Market liquidity metrics
  • DealFilters - Filter criteria with validation

Key Features:

  • Field Aliasing: API camelCase ↔ Python snake_case (e.g., dealAmountdeal_amount)
  • Computed Fields: Auto-calculate price per sqm using @computed_field
  • Validation: Automatic data validation with clear error messages
  • Serialization: Easy conversion to/from JSON via .model_dump()
  • Type Safety: Full IDE autocomplete and mypy support

Usage:

from nadlan_mcp.govmap import GovmapClient
from nadlan_mcp.govmap.models import Deal, AutocompleteResponse

client = GovmapClient()

# Returns AutocompleteResponse model
result = client.autocomplete_address("חולון")
coords = result.results[0].coordinates  # Optional[CoordinatePoint]

# Returns List[Deal]
deals = client.get_street_deals("polygon123")
for deal in deals:
    price = deal.deal_amount  # float (snake_case)
    price_per_sqm = deal.price_per_sqm  # Computed field!

# Serialize to dict/JSON when needed
deal_dict = deal.model_dump()
deal_json = deal.model_dump_json()

GovmapClient Class (govmap/client.py)

Responsibilities:

  • Make HTTP requests to Govmap API
  • Parse JSON responses into Pydantic models
  • Implement retry logic with exponential backoff
  • Enforce rate limiting
  • Delegate to specialized modules for validation, filtering, analysis

Core API Methods (all return Pydantic models):

  • autocomplete_address()AutocompleteResponse
  • get_gush_helka()Dict (parcel metadata)
  • get_deals_by_radius()List[Deal]
  • get_street_deals()List[Deal]
  • get_neighborhood_deals()List[Deal]
  • find_recent_deals_for_address()List[Deal]

Business Logic Methods (delegate to modules, return models):

  • filter_deals_by_criteria()List[Deal]
  • calculate_deal_statistics()DealStatistics
  • calculate_market_activity_score()MarketActivityScore
  • analyze_investment_potential()InvestmentAnalysis
  • get_market_liquidity()LiquidityMetrics

Reliability Features:

  1. Retry Logic - Exponential backoff on failures
  2. Rate Limiting - Tracks request times, enforces delays
  3. Input Validation - Delegates to validators module
  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:

@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

# 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

# Performance
GOVMAP_MAX_POLYGONS=10  # Max polygons to query per search (limits API calls)

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
  • MAX_POLYGONS=5 (faster, fewer results)

Conservative (Shared API):

  • MAX_RETRIES=3
  • RETRY_MAX_WAIT=10
  • REQUESTS_PER_SECOND=2.0

Future Architecture Evolution

Phase 3: Package Refactoring ( COMPLETED)

COMPLETED - See .cursor/plans/PHASE3-REFACTORING.md for detailed plan

The monolithic govmap.py (1,454 lines) has been refactored into a modular package:

nadlan_mcp/
├── __init__.py                 # Backward compatibility
├── config.py                   # ✅ Configuration
├── main.py                     # ✅ Entry point
├── fastmcp_server.py          # ✅ MCP tools
└── govmap/                     # 📦 NEW PACKAGE
    ├── __init__.py             # Public API exports
    ├── client.py               # Core API client (~300 lines)
    ├── validators.py           # Input validation (~100 lines)
    ├── models.py               # ✅ Pydantic v2 models (~340 lines)
    ├── filters.py              # Deal filtering (~150 lines)
    ├── statistics.py           # Statistical calculations (~150 lines)
    ├── market_analysis.py      # Market analysis (~400 lines)
    └── utils.py                # Helper utilities (~100 lines)

Benefits:

  • Single Responsibility Principle - each module has one job
  • Easier testing - test modules in isolation
  • Better maintainability - smaller files (100-400 lines)
  • Reusability - import only what you need
  • 100% backward compatible - existing code still works

Module Responsibilities:

  1. client.py - Pure API interactions with Govmap

    • HTTP requests, rate limiting, response parsing
    • No business logic, just API calls
  2. validators.py - Input validation

    • Address, coordinate, integer, date validation
    • Pure functions, clear error messages
  3. filters.py - Deal filtering logic

    • Property type, rooms, price, area, floor filters
    • Composable filter functions
  4. statistics.py - Statistical calculations

    • Mean, median, percentiles, std dev
    • Pure math functions, no I/O
  5. market_analysis.py - Market analysis functions

    • Activity scoring, investment analysis, liquidity
    • Works with data, no API calls
  6. utils.py - Shared utilities

    • Address matching, text normalization, helpers
    • Reusable across modules
  7. models.py - Pydantic v2 data models IMPLEMENTED

    • 9 models: Deal, Address, AutocompleteResponse, DealStatistics, etc.
    • Type safety, validation, computed fields
    • Field aliasing for API compatibility

Backward Compatibility:

# OLD CODE (still works)
from nadlan_mcp import GovmapClient
client = GovmapClient()

# NEW CODE (also works)
from nadlan_mcp.govmap import GovmapClient
from nadlan_mcp.govmap.filters import filter_deals_by_criteria

Phase 4: Pydantic Data Models IMPLEMENTED in v2.0.0

Comprehensive Pydantic v2 models with type safety and validation:

from nadlan_mcp.govmap.models import Deal, DealStatistics, AutocompleteResponse

# Deal model with computed fields
deal = Deal(
    objectid=123,
    deal_amount=1500000.0,
    deal_date="2024-01-15",
    asset_area=85.0
)
# price_per_sqm automatically computed!
assert deal.price_per_sqm == 17647.06

# Field aliases support both API and Python naming
deal = Deal(dealAmount=1500000, assetArea=85, ...)  # API style
deal = Deal(deal_amount=1500000, asset_area=85, ...)  # Python style

9 Models Implemented:

  • CoordinatePoint, Address, AutocompleteResult, AutocompleteResponse
  • Deal, DealStatistics, DealFilters
  • MarketActivityScore, InvestmentAnalysis, LiquidityMetrics

See MIGRATION.md for v1.x → v2.0 upgrade guide.

Phase 5: Database Layer (Future - Optional)

For historical tracking and faster queries:

├── database/
│   ├── models.py       # SQLAlchemy models
│   ├── crud.py         # CRUD operations
│   └── migrations/     # Alembic migrations

Deployment

Development

python run_fastmcp_server.py

Production (MCP Client)

{
  "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:

import logging
logging.basicConfig(level=logging.DEBUG)

References