Added documentation
This commit is contained in:
+490
@@ -0,0 +1,490 @@
|
||||
# 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/)
|
||||
|
||||
Reference in New Issue
Block a user