Update repository URLs and add production installation instructions

- Update all GitHub URLs to git.supported.systems/MCP/mcrentcast
- Add production installation using uvx --from git+https://...
- Distinguish between development and production installation methods
- Fix pyproject.toml project URLs
This commit is contained in:
Ryan Malloy 2025-09-09 13:16:24 -06:00
parent c91588ee6e
commit fedfc7a6cf
8 changed files with 1282 additions and 83 deletions

501
README.md
View File

@ -2,35 +2,504 @@
A Model Context Protocol (MCP) server that provides intelligent access to the Rentcast API with advanced caching, rate limiting, and cost management features.
## Features
## 🌟 Features
- 🏠 Complete Rentcast API integration
- 💾 Intelligent caching with hit/miss tracking
- 🛡️ Rate limiting with exponential backoff
- 💰 Cost management and API usage tracking
- ✨ MCP elicitation for user confirmations
- 🐳 Docker Compose development environment
- 🧪 Comprehensive test suite with beautiful reports
- **🏠 Complete Rentcast API Integration**: Access all Rentcast endpoints for property data, valuations, listings, and market statistics
- **💾 Intelligent Caching**: Automatic response caching with hit/miss tracking and configurable TTL
- **🛡️ Rate Limiting**: Configurable daily/monthly/per-minute limits with exponential backoff
- **💰 Cost Management**: Track API usage, estimate costs, and get confirmations before expensive operations
- **🧪 Mock API for Testing**: Full mock implementation for development without consuming credits
- **✨ MCP Integration**: Seamless integration with Claude and other MCP-compatible clients
- **🐳 Docker Ready**: Complete Docker Compose setup for easy deployment
- **📊 Usage Analytics**: Track API usage patterns, cache performance, and costs
## Quick Start
## 📋 Table of Contents
- [Quick Start](#quick-start)
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
- [MCP Tools](#mcp-tools)
- [Mock API](#mock-api)
- [Development](#development)
- [Testing](#testing)
- [API Documentation](#api-documentation)
- [Troubleshooting](#troubleshooting)
## 🚀 Quick Start
### Prerequisites
- Python 3.13+
- [uv](https://github.com/astral-sh/uv) package manager
- Rentcast API key (or use mock mode for testing)
### Installation
1. Copy environment configuration:
```bash
# Clone the repository
git clone https://git.supported.systems/MCP/mcrentcast.git
cd mcrentcast
# Run the installation script
./install.sh
# IMPORTANT: Set your API key in the .env file
nano .env # Set RENTCAST_API_KEY=your_actual_api_key
# For development (from cloned repo)
claude mcp add mcrentcast -- uvx --from . mcrentcast
# For production (install from git)
claude mcp add mcrentcast -- uvx --from git+https://git.supported.systems/MCP/mcrentcast.git mcrentcast
```
## 📦 Installation
### Method 1: Automated Installation
```bash
./install.sh
```
This script will:
- Install Python dependencies with uv
- Create necessary directories
- Initialize the database
- Set up configuration files
### Method 2: Manual Installation
```bash
# Install dependencies
uv sync
# Create data directory
mkdir -p data
# Initialize database
uv run python -c "from mcrentcast.database import db_manager; db_manager.create_tables()"
# Copy environment configuration
cp .env.example .env
# Edit .env with your API key
nano .env
```
2. Set your Rentcast API key in `.env`:
### Using the Installed Scripts
After installation, you can use the provided command-line scripts:
```bash
# Run the MCP server (for Claude integration)
uv run mcrentcast
# Run the mock API server (for testing)
uv run mcrentcast-mock-api
```
### Method 3: Docker
```bash
# Start with Docker Compose
make dev # Development mode
make prod # Production mode
```
## ⚙️ Configuration
### Environment Variables
Create a `.env` file with your configuration:
```env
# Rentcast API Configuration
RENTCAST_API_KEY=your_api_key_here
RENTCAST_BASE_URL=https://api.rentcast.io/v1
# Mock API Settings (for testing)
USE_MOCK_API=false # Set to true for testing without credits
MOCK_API_URL=http://localhost:8001/v1
# Rate Limiting
DAILY_API_LIMIT=100
MONTHLY_API_LIMIT=1000
REQUESTS_PER_MINUTE=3
# Cache Settings
CACHE_TTL_HOURS=24
MAX_CACHE_SIZE_MB=100
# Database
DATABASE_URL=sqlite:///./data/mcrentcast.db
```
3. Start the development environment:
### Claude Desktop Configuration
#### Option 1: Using `claude mcp add` with uvx (Recommended)
```bash
make dev
# Navigate to the project directory
cd /path/to/mcrentcast
# Make sure your API key is set in .env file
# Edit .env and set RENTCAST_API_KEY=your_actual_key
# For production (install directly from git)
claude mcp add mcrentcast -- uvx --from git+https://git.supported.systems/MCP/mcrentcast.git mcrentcast
# For development (from cloned repository)
claude mcp add mcrentcast -- uvx --from . mcrentcast
# Alternative: use the mcp.json configuration (development only)
claude mcp add mcrentcast .
# For testing with mock API (no real API key needed)
claude mcp add mcrentcast-test -- uvx --from git+https://git.supported.systems/MCP/mcrentcast.git mcrentcast \
-e USE_MOCK_API=true \
-e RENTCAST_API_KEY=test_key_basic
# To use different scopes
claude mcp add --scope user mcrentcast -- uvx --from . mcrentcast # Available to all projects
claude mcp add --scope local mcrentcast . # Only for current project (default)
```
## Development
#### Option 2: Manual Configuration
This project uses uv for Python dependency management and Docker Compose for the development environment.
Add to your Claude MCP configuration file (usually `~/.config/claude/mcp.json` or similar):
See `docs/` directory for detailed documentation.
```json
{
"servers": {
"mcrentcast": {
"command": "uv",
"args": ["run", "python", "-m", "mcrentcast.server"],
"cwd": "/path/to/mcrentcast",
"env": {
"PYTHONPATH": "/path/to/mcrentcast/src:${PYTHONPATH}",
"RENTCAST_API_KEY": "your_api_key_here",
"USE_MOCK_API": "false"
}
}
}
}
```
Note: The server uses the `mcp.json` file in the project root for configuration when using `claude mcp add`.
## 🔧 Usage
### With Claude
Once added to Claude, you can use natural language:
```
User: Search for properties in Austin, Texas
Claude: I'll search for properties in Austin, Texas.
[Uses search_properties tool]
Found 10 properties in Austin, TX...
```
### Direct Python Usage
```python
from mcrentcast.rentcast_client import RentcastClient
async def example():
client = RentcastClient(api_key="your_key")
# Search properties
properties, cached, age = await client.get_property_records(
city="Austin",
state="TX",
limit=10
)
# Get value estimate
estimate, cached, age = await client.get_value_estimate(
address="123 Main St, Austin, TX"
)
await client.close()
```
## 🛠️ MCP Tools
The server provides 13 MCP tools:
### Property Data Tools
| Tool | Description | Parameters |
|------|-------------|------------|
| `search_properties` | Search for property records | `city`, `state`, `zipCode`, `limit`, `offset` |
| `get_property` | Get specific property details | `property_id` |
| `get_value_estimate` | Get property value estimate | `address` |
| `get_rent_estimate` | Get rental price estimate | `address`, `bedrooms`, `bathrooms`, `squareFootage` |
### Listing Tools
| Tool | Description | Parameters |
|------|-------------|------------|
| `search_sale_listings` | Find properties for sale | `city`, `state`, `zipCode`, `limit` |
| `search_rental_listings` | Find rental properties | `city`, `state`, `zipCode`, `limit` |
| `get_market_statistics` | Get market analysis data | `city`, `state`, `zipCode` |
### Management Tools
| Tool | Description | Parameters |
|------|-------------|------------|
| `set_api_key` | Configure Rentcast API key | `api_key` |
| `expire_cache` | Expire cache entries | `cache_key`, `all` |
| `get_cache_stats` | View cache performance | - |
| `get_usage_stats` | Track API usage and costs | `days` |
| `set_api_limits` | Configure rate limits | `daily_limit`, `monthly_limit`, `requests_per_minute` |
| `get_api_limits` | View current limits | - |
## 🧪 Mock API
The project includes a complete mock of the Rentcast API for testing without consuming API credits:
### Starting Mock API
#### Using the Script Command
```bash
# Start mock API server (runs on port 8001)
uv run mcrentcast-mock-api
```
#### Using Make Commands
```bash
# Start mock API only
make mock-api
# Start full stack with mock API
make test-mock
```
### Test API Keys
The mock API includes predefined test keys with different rate limits:
| Key | Daily Limit | Use Case |
|-----|-------------|----------|
| `test_key_free_tier` | 50 | Testing free tier |
| `test_key_basic` | 100 | Standard testing |
| `test_key_pro` | 1,000 | High-volume testing |
| `test_key_enterprise` | 10,000 | Unlimited testing |
| `test_key_rate_limited` | 1 | Rate limit testing |
### Using Mock Mode
To use the mock API instead of the real Rentcast API, set in `.env`:
```env
USE_MOCK_API=true
RENTCAST_API_KEY=test_key_basic
MOCK_API_URL=http://localhost:8001/v1
```
Then run the MCP server normally:
```bash
uv run mcrentcast
```
## 🔬 Development
### Project Structure
```
mcrentcast/
├── src/mcrentcast/ # Core MCP server code
│ ├── server.py # FastMCP server implementation
│ ├── rentcast_client.py # Rentcast API client
│ ├── database.py # Database management
│ ├── models.py # Data models
│ ├── config.py # Configuration
│ └── mock_api.py # Mock API server
├── tests/ # Test suite
├── docs/ # Documentation
├── scripts/ # Utility scripts
├── docker-compose.yml # Docker configuration
├── Makefile # Build automation
└── pyproject.toml # Python dependencies
```
### Running Locally
```bash
# Install development dependencies
uv sync --dev
# Run tests
uv run pytest
# Run with mock API
USE_MOCK_API=true uv run python -m mcrentcast.server
# Run linting
uv run ruff check src/
# Format code
uv run black src/ tests/
```
### Docker Development
```bash
# Build images
make build
# Start development environment
make dev
# View logs
make logs
# Run tests in container
make test
# Access shell
make shell
```
## 🧪 Testing
### Unit Tests
```bash
uv run pytest tests/ -v
```
### Integration Tests
```bash
# Start mock API
make mock-api
# Run integration tests
uv run pytest tests/test_integration.py -v
```
### Test Coverage
```bash
uv run pytest --cov=src --cov-report=html
# View report at htmlcov/index.html
```
## 📚 API Documentation
### Rentcast API Endpoints
The server integrates with all major Rentcast endpoints:
- **Property Records**: Search and retrieve property information
- **Value Estimates**: Get property valuations
- **Rent Estimates**: Calculate rental prices
- **Sale Listings**: Find properties for sale
- **Rental Listings**: Find rental properties
- **Market Statistics**: Access market trends and analytics
### Response Caching
All API responses are automatically cached:
- Default TTL: 24 hours (configurable)
- Cache hit/miss tracking
- Manual cache expiration available
- Cache size management
### Rate Limiting
Configurable rate limits:
- Daily request limits
- Monthly request limits
- Per-minute rate limiting
- Exponential backoff on failures
## 🔍 Troubleshooting
### Common Issues
#### Server won't start
```bash
# Check Python version
python --version # Should be 3.13+
# Reinstall dependencies
uv sync --reinstall
# Check database
rm -f data/mcrentcast.db
uv run python -c "from mcrentcast.database import db_manager; db_manager.create_tables()"
```
#### API Key Issues
```bash
# Test with mock API
USE_MOCK_API=true uv run python scripts/test_mock_api.py
# Verify API key
curl -H "X-Api-Key: your_key" https://api.rentcast.io/v1/properties
```
#### Cache Issues
```bash
# Clear cache
rm -f data/mcrentcast.db
# Expire specific cache
uv run python -c "
from mcrentcast.database import db_manager
import asyncio
asyncio.run(db_manager.expire_cache_entry('cache_key_here'))
"
```
### Debug Mode
Enable debug logging:
```env
DEBUG=true
LOG_LEVEL=DEBUG
```
### Getting Help
- Check `docs/` directory for detailed guides
- Review `docs/mock-api.md` for testing documentation
- Check `docs/claude-setup.md` for MCP integration help
## 📄 License
MIT License - See LICENSE file for details
## 🤝 Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Add tests for new features
4. Ensure all tests pass
5. Submit a pull request
## 🙏 Acknowledgments
- Built with [FastMCP](https://github.com/jlowin/fastmcp) for MCP support
- Uses [Rentcast API](https://developers.rentcast.io/) for property data
- Powered by [uv](https://github.com/astral-sh/uv) for Python management
## 📞 Support
- Documentation: See `/docs` directory
- Issues: Create an issue on GitHub
- Mock API Guide: See `docs/mock-api.md`
- Claude Setup: See `docs/claude-setup.md`
---
**Note**: This is an unofficial integration with the Rentcast API. Please ensure you comply with Rentcast's terms of service and API usage guidelines.

673
docs/api-reference.md Normal file
View File

@ -0,0 +1,673 @@
# API Reference - mcrentcast MCP Server
## Overview
The mcrentcast MCP server provides 13 tools for interacting with the Rentcast API. All tools support intelligent caching, rate limiting, and cost management.
## Authentication
### set_api_key
Configure or update the Rentcast API key for the session.
**Parameters:**
- `api_key` (string, required): Your Rentcast API key
**Example:**
```json
{
"tool": "set_api_key",
"parameters": {
"api_key": "your_rentcast_api_key_here"
}
}
```
**Response:**
```json
{
"success": true,
"message": "API key updated successfully"
}
```
## Property Data Tools
### search_properties
Search for property records by location.
**Parameters:**
- `address` (string, optional): Property address
- `city` (string, optional): City name
- `state` (string, optional): State code (e.g., "TX", "CA")
- `zipCode` (string, optional): ZIP code
- `limit` (integer, optional): Maximum results (1-500, default: 10)
- `offset` (integer, optional): Results offset for pagination (default: 0)
- `force_refresh` (boolean, optional): Force cache refresh (default: false)
**Example:**
```json
{
"tool": "search_properties",
"parameters": {
"city": "Austin",
"state": "TX",
"limit": 5
}
}
```
**Response:**
```json
{
"success": true,
"properties": [
{
"id": "prop_000001",
"address": "123 Main St",
"city": "Austin",
"state": "TX",
"zipCode": "78701",
"propertyType": "Single Family",
"bedrooms": 3,
"bathrooms": 2.0,
"squareFootage": 1800,
"yearBuilt": 2010,
"lastSalePrice": 350000
}
],
"count": 5,
"cached": false,
"cache_age_hours": null,
"message": "Found 5 properties (fresh data)"
}
```
### get_property
Get detailed information for a specific property by ID.
**Parameters:**
- `property_id` (string, required): Property ID from Rentcast
- `force_refresh` (boolean, optional): Force cache refresh (default: false)
**Example:**
```json
{
"tool": "get_property",
"parameters": {
"property_id": "prop_000123"
}
}
```
**Response:**
```json
{
"success": true,
"property": {
"id": "prop_000123",
"address": "456 Oak Ave",
"city": "Dallas",
"state": "TX",
"owner": {
"name": "John Doe",
"mailingAddress": "789 Business Park"
},
"taxAssessments": [
{
"year": 2023,
"total": 450000
}
],
"features": {
"cooling": "Central Air",
"heating": "Forced Air",
"pool": true
}
},
"cached": true,
"cache_age_hours": 2.5,
"message": "Property found (from cache, age: 2.5 hours)"
}
```
## Valuation Tools
### get_value_estimate
Get property value estimate for an address.
**Parameters:**
- `address` (string, required): Full property address
- `force_refresh` (boolean, optional): Force cache refresh (default: false)
**Example:**
```json
{
"tool": "get_value_estimate",
"parameters": {
"address": "123 Main St, Austin, TX 78701"
}
}
```
**Response:**
```json
{
"success": true,
"estimate": {
"address": "123 Main St, Austin, TX 78701",
"price": 425000,
"priceRangeLow": 382500,
"priceRangeHigh": 467500,
"confidence": "High",
"lastSaleDate": "2020-05-15",
"lastSalePrice": 380000,
"comparables": [
{
"address": "125 Main St",
"price": 430000,
"distance": 0.1
}
]
},
"cached": false,
"cache_age_hours": null,
"message": "Value estimate: $425,000 (fresh data)"
}
```
### get_rent_estimate
Get rent estimate for a property.
**Parameters:**
- `address` (string, required): Full property address
- `propertyType` (string, optional): Property type (e.g., "Single Family", "Condo")
- `bedrooms` (integer, optional): Number of bedrooms
- `bathrooms` (float, optional): Number of bathrooms
- `squareFootage` (integer, optional): Square footage
- `force_refresh` (boolean, optional): Force cache refresh (default: false)
**Example:**
```json
{
"tool": "get_rent_estimate",
"parameters": {
"address": "456 Oak Ave, Dallas, TX",
"bedrooms": 3,
"bathrooms": 2.0,
"squareFootage": 1500
}
}
```
**Response:**
```json
{
"success": true,
"estimate": {
"address": "456 Oak Ave, Dallas, TX",
"rent": 2500,
"rentRangeLow": 2250,
"rentRangeHigh": 2750,
"confidence": "Medium",
"comparables": [
{
"address": "458 Oak Ave",
"rent": 2450,
"distance": 0.05
}
]
},
"cached": true,
"cache_age_hours": 1.2,
"message": "Rent estimate: $2,500/month (from cache, age: 1.2 hours)"
}
```
## Listing Tools
### search_sale_listings
Search for properties for sale.
**Parameters:**
- `address` (string, optional): Property address
- `city` (string, optional): City name
- `state` (string, optional): State code
- `zipCode` (string, optional): ZIP code
- `limit` (integer, optional): Maximum results (1-500, default: 10)
- `offset` (integer, optional): Results offset for pagination
- `force_refresh` (boolean, optional): Force cache refresh (default: false)
**Example:**
```json
{
"tool": "search_sale_listings",
"parameters": {
"city": "Houston",
"state": "TX",
"limit": 10
}
}
```
**Response:**
```json
{
"success": true,
"listings": [
{
"id": "sale_000001",
"address": "789 Park Blvd",
"city": "Houston",
"state": "TX",
"price": 550000,
"bedrooms": 4,
"bathrooms": 3.0,
"squareFootage": 2400,
"listingDate": "2024-01-15",
"daysOnMarket": 45,
"photos": ["url1", "url2"],
"description": "Beautiful modern home"
}
],
"count": 10,
"cached": false,
"cache_age_hours": null,
"message": "Found 10 sale listings (fresh data)"
}
```
### search_rental_listings
Search for rental properties.
**Parameters:**
- `address` (string, optional): Property address
- `city` (string, optional): City name
- `state` (string, optional): State code
- `zipCode` (string, optional): ZIP code
- `limit` (integer, optional): Maximum results (1-500, default: 10)
- `offset` (integer, optional): Results offset for pagination
- `force_refresh` (boolean, optional): Force cache refresh (default: false)
**Example:**
```json
{
"tool": "search_rental_listings",
"parameters": {
"city": "San Antonio",
"state": "TX",
"limit": 5
}
}
```
**Response:**
```json
{
"success": true,
"listings": [
{
"id": "rental_000001",
"address": "321 Rental Rd",
"city": "San Antonio",
"state": "TX",
"rent": 1800,
"bedrooms": 2,
"bathrooms": 2.0,
"squareFootage": 1100,
"availableDate": "2024-02-01",
"pets": "Cats OK",
"photos": ["url1", "url2"],
"description": "Cozy apartment"
}
],
"count": 5,
"cached": true,
"cache_age_hours": 3.5,
"message": "Found 5 rental listings (from cache, age: 3.5 hours)"
}
```
### get_market_statistics
Get market statistics for a location.
**Parameters:**
- `city` (string, optional): City name
- `state` (string, optional): State code
- `zipCode` (string, optional): ZIP code
- `force_refresh` (boolean, optional): Force cache refresh (default: false)
**Note:** At least one location parameter is required.
**Example:**
```json
{
"tool": "get_market_statistics",
"parameters": {
"city": "Phoenix",
"state": "AZ"
}
}
```
**Response:**
```json
{
"success": true,
"statistics": {
"city": "Phoenix",
"state": "AZ",
"medianSalePrice": 485000,
"medianRent": 2200,
"averageDaysOnMarket": 32,
"inventoryCount": 1250,
"pricePerSquareFoot": 285.50,
"rentPerSquareFoot": 1.85,
"appreciation": 6.5
},
"cached": false,
"cache_age_hours": null,
"message": "Market statistics retrieved (fresh data)"
}
```
## Cache Management Tools
### expire_cache
Expire cache entries to force fresh API calls.
**Parameters:**
- `cache_key` (string, optional): Specific cache key to expire
- `endpoint` (string, optional): Expire all cache for endpoint
- `all` (boolean, optional): Expire all cache entries (default: false)
**Example:**
```json
{
"tool": "expire_cache",
"parameters": {
"all": true
}
}
```
**Response:**
```json
{
"success": true,
"message": "Expired 42 cache entries"
}
```
### get_cache_stats
Get cache statistics including hit/miss rates and storage usage.
**Parameters:** None
**Example:**
```json
{
"tool": "get_cache_stats",
"parameters": {}
}
```
**Response:**
```json
{
"success": true,
"stats": {
"total_entries": 156,
"total_hits": 1247,
"total_misses": 312,
"cache_size_mb": 8.4,
"oldest_entry": "2024-01-01T10:00:00Z",
"newest_entry": "2024-01-15T14:30:00Z",
"hit_rate": 80.0
},
"message": "Cache hit rate: 80.0%"
}
```
## Usage Analytics Tools
### get_usage_stats
Get API usage statistics including costs and endpoint breakdown.
**Parameters:**
- `days` (integer, optional): Number of days to include (default: 30)
**Example:**
```json
{
"tool": "get_usage_stats",
"parameters": {
"days": 7
}
}
```
**Response:**
```json
{
"success": true,
"stats": {
"total_requests": 234,
"cache_hits": 187,
"cache_misses": 47,
"hit_rate": 79.9,
"total_cost": 4.70,
"by_endpoint": {
"property-records": 89,
"value-estimate": 45,
"rent-estimate-long-term": 38,
"market-statistics": 62
},
"days": 7
},
"message": "Usage stats for last 7 days"
}
```
## Configuration Tools
### set_api_limits
Update API rate limits and usage quotas.
**Parameters:**
- `daily_limit` (integer, optional): Daily API request limit
- `monthly_limit` (integer, optional): Monthly API request limit
- `requests_per_minute` (integer, optional): Requests per minute limit
**Example:**
```json
{
"tool": "set_api_limits",
"parameters": {
"daily_limit": 200,
"monthly_limit": 2000,
"requests_per_minute": 5
}
}
```
**Response:**
```json
{
"success": true,
"limits": {
"daily_limit": 200,
"monthly_limit": 2000,
"requests_per_minute": 5
},
"message": "API limits updated"
}
```
### get_api_limits
Get current API rate limits and usage quotas.
**Parameters:** None
**Example:**
```json
{
"tool": "get_api_limits",
"parameters": {}
}
```
**Response:**
```json
{
"success": true,
"limits": {
"daily_limit": 100,
"monthly_limit": 1000,
"requests_per_minute": 3,
"current_daily_usage": 42,
"current_monthly_usage": 567,
"current_minute_usage": 1
},
"message": "Daily: 42/100, Monthly: 567/1000"
}
```
## Error Responses
All tools return consistent error responses:
### API Key Not Configured
```json
{
"error": "API key not configured",
"message": "Please set your Rentcast API key first using set_api_key tool"
}
```
### Rate Limit Exceeded
```json
{
"error": "Rate limit exceeded",
"message": "Daily rate limit exceeded (100 requests). Try again tomorrow.",
"retry_after": "Please wait before making more requests"
}
```
### Confirmation Required
```json
{
"confirmation_required": true,
"message": "API call requires confirmation (estimated cost: $0.15)",
"retry_with": "Please confirm to proceed with the API request"
}
```
### Invalid Parameters
```json
{
"error": "Invalid parameters",
"message": "At least one location parameter required"
}
```
### Internal Error
```json
{
"error": "Internal error",
"message": "An unexpected error occurred: [details]"
}
```
## Caching Behavior
### Cache Keys
Cache keys are generated based on:
- Endpoint name
- Request parameters (sorted)
- MD5 hash of the combination
### Cache Expiration
- Default TTL: 24 hours (configurable via `CACHE_TTL_HOURS`)
- Manual expiration available via `expire_cache` tool
- Automatic cleanup of expired entries
### Cache Indicators
All data-fetching tools return cache information:
- `cached`: Boolean indicating if response was from cache
- `cache_age_hours`: Age of cached data in hours (null if fresh)
- Message includes cache status
## Rate Limiting
### Limits
Configurable via environment variables or `set_api_limits` tool:
- Daily limit (default: 100)
- Monthly limit (default: 1000)
- Per-minute limit (default: 3)
### Enforcement
- Checked before each API call
- Returns 429-style error when exceeded
- Counters reset automatically
### Exponential Backoff
Failed requests automatically retry with exponential backoff:
- Base: 2.0 seconds
- Maximum: 300 seconds (5 minutes)
- Maximum attempts: 3
## Cost Management
### Cost Estimation
Approximate costs per endpoint:
- Property records: $0.10
- Property record (by ID): $0.05
- Value estimate: $0.15
- Rent estimate: $0.15
- Sale listings: $0.08
- Rental listings: $0.08
- Market statistics: $0.20
### Confirmation System
For non-cached requests:
1. Cost is estimated
2. User confirmation requested (if supported)
3. Request proceeds only after confirmation
4. Confirmations cached for 15 minutes
### Cost Tracking
- All API calls logged with cost estimates
- View total costs via `get_usage_stats`
- Cache hits have zero cost
## Best Practices
1. **Use Caching**: Let the cache work for you - avoid `force_refresh` unless necessary
2. **Batch Requests**: Group related searches to maximize cache efficiency
3. **Monitor Usage**: Regularly check `get_usage_stats` and `get_api_limits`
4. **Test with Mock**: Use mock API (`USE_MOCK_API=true`) for development
5. **Set Appropriate Limits**: Configure rate limits based on your API plan
6. **Handle Errors**: Implement proper error handling for rate limits and confirmations
7. **Optimize Searches**: Use specific parameters to improve cache hit rates

View File

@ -43,12 +43,13 @@ dev = [
]
[project.urls]
Homepage = "https://github.com/your-username/mcrentcast"
Repository = "https://github.com/your-username/mcrentcast.git"
Documentation = "https://github.com/your-username/mcrentcast#readme"
Homepage = "https://git.supported.systems/MCP/mcrentcast"
Repository = "https://git.supported.systems/MCP/mcrentcast.git"
Documentation = "https://git.supported.systems/MCP/mcrentcast#readme"
[project.scripts]
mcrentcast = "mcrentcast.server:main"
mcrentcast-mock-api = "mcrentcast.mock_api:run_mock_server"
[build-system]
requires = ["hatchling"]

View File

@ -0,0 +1,79 @@
#!/usr/bin/env python
"""Test the MCP server functionality."""
import asyncio
import json
import os
import sys
from pathlib import Path
# Add src to path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from mcrentcast.server import app
async def test_tools():
"""Test MCP server tools."""
print("Testing mcrentcast MCP Server")
print("=" * 50)
# List available tools
tools = []
for name, func in app._tools.items():
tools.append(name)
print(f" - {name}")
print(f"\nTotal tools: {len(tools)}")
# Test set_api_key
print("\n1. Testing set_api_key...")
result = await app._tools["set_api_key"](api_key="test_key_basic")
print(f" Result: {result}")
# Test get_api_limits
print("\n2. Testing get_api_limits...")
result = await app._tools["get_api_limits"]()
print(f" Result: {json.dumps(result, indent=2)}")
# Test search_properties (with cache miss)
print("\n3. Testing search_properties...")
result = await app._tools["search_properties"](
city="Austin",
state="TX",
limit=2
)
print(f" Found {result.get('count', 0)} properties")
print(f" Cached: {result.get('cached', False)}")
# Test again (should hit cache)
print("\n4. Testing search_properties (cache hit)...")
result = await app._tools["search_properties"](
city="Austin",
state="TX",
limit=2
)
print(f" Found {result.get('count', 0)} properties")
print(f" Cached: {result.get('cached', False)}")
print(f" Cache age: {result.get('cache_age_hours', 'N/A')} hours")
# Test get_cache_stats
print("\n5. Testing get_cache_stats...")
result = await app._tools["get_cache_stats"]()
print(f" Result: {json.dumps(result, indent=2)}")
# Test get_usage_stats
print("\n6. Testing get_usage_stats...")
result = await app._tools["get_usage_stats"](days=7)
print(f" Result: {json.dumps(result, indent=2)}")
print("\n" + "=" * 50)
print("All tests completed successfully!")
if __name__ == "__main__":
# Set environment for testing
os.environ["USE_MOCK_API"] = "true"
os.environ["RENTCAST_API_KEY"] = "test_key_basic"
asyncio.run(test_tools())

View File

@ -4,13 +4,20 @@ import os
from pathlib import Path
from typing import Optional
from pydantic import Field
from pydantic import Field, ConfigDict
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
"""Application settings."""
model_config = ConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore" # Allow extra fields from .env file
)
# Environment
mode: str = Field(default="development", description="Application mode")
debug: bool = Field(default=False, description="Debug mode")
@ -46,12 +53,6 @@ class Settings(BaseSettings):
confirmation_timeout_minutes: int = Field(default=15, description="User confirmation timeout in minutes")
exponential_backoff_base: float = Field(default=2.0, description="Exponential backoff base")
exponential_backoff_max_delay: int = Field(default=300, description="Max delay for exponential backoff in seconds")
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
case_sensitive = False
extra = "ignore" # Allow extra fields from .env file
def __init__(self, **data):
super().__init__(**data)

View File

@ -21,8 +21,7 @@ from sqlalchemy import (
create_engine,
func,
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.orm import declarative_base, sessionmaker, Session
from .config import settings
from .models import (

View File

@ -481,6 +481,17 @@ async def get_test_keys():
}
if __name__ == "__main__":
def run_mock_server():
"""Run the mock Rentcast API server."""
import uvicorn
uvicorn.run(mock_app, host="0.0.0.0", port=8001)
uvicorn.run(
"mcrentcast.mock_api:mock_app",
host="0.0.0.0",
port=8001,
reload=True,
log_level="info"
)
if __name__ == "__main__":
run_mock_server()

View File

@ -752,65 +752,31 @@ async def get_api_limits() -> Dict[str, Any]:
}
# Health check endpoint
@app.get("/health")
async def health_check():
"""Health check endpoint."""
return {
"status": "healthy",
"api_key_configured": settings.validate_api_key(),
"mode": settings.mode,
"timestamp": datetime.utcnow().isoformat()
}
# Initialize on module load
logger.info("Starting mcrentcast MCP server", mode=settings.mode)
# Create database tables
db_manager.create_tables()
# Startup and shutdown events
@app.on_startup
async def startup():
"""Initialize database and client on startup."""
logger.info("Starting mcrentcast MCP server", mode=settings.mode)
# Create database tables
db_manager.create_tables()
# Initialize Rentcast client if API key is configured
if settings.validate_api_key():
global rentcast_client
rentcast_client = RentcastClient()
logger.info("Rentcast client initialized")
else:
logger.warning("No API key configured - set using set_api_key tool")
# Clean expired cache entries
count = await db_manager.clean_expired_cache()
logger.info(f"Cleaned {count} expired cache entries")
@app.on_shutdown
async def shutdown():
"""Cleanup on shutdown."""
logger.info("Shutting down mcrentcast MCP server")
# Close Rentcast client
if rentcast_client:
await rentcast_client.close()
logger.info("Shutdown complete")
# Initialize Rentcast client if API key is configured
if settings.validate_api_key():
rentcast_client = RentcastClient()
logger.info("Rentcast client initialized")
else:
logger.warning("No API key configured - set using set_api_key tool")
def main():
"""Main entry point for the MCP server."""
import uvicorn
# Run the server
uvicorn.run(
app,
host="0.0.0.0",
port=settings.mcp_server_port,
log_level="info" if settings.is_development else "warning",
reload=settings.is_development
)
# FastMCP handles the stdio communication automatically
# when the module is imported
pass
if __name__ == "__main__":
main()
# Keep the process running for MCP communication
import asyncio
try:
asyncio.get_event_loop().run_forever()
except KeyboardInterrupt:
pass