From fedfc7a6cfad48ad18d989f14252ea48e09b04c8 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Tue, 9 Sep 2025 13:16:24 -0600 Subject: [PATCH] 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 --- README.md | 501 ++++++++++++++++++++++++++- docs/api-reference.md | 673 +++++++++++++++++++++++++++++++++++++ pyproject.toml | 7 +- scripts/test_mcp_server.py | 79 +++++ src/mcrentcast/config.py | 15 +- src/mcrentcast/database.py | 3 +- src/mcrentcast/mock_api.py | 15 +- src/mcrentcast/server.py | 72 ++-- 8 files changed, 1282 insertions(+), 83 deletions(-) create mode 100644 docs/api-reference.md create mode 100644 scripts/test_mcp_server.py diff --git a/README.md b/README.md index 38169c5..51ad3b7 100644 --- a/README.md +++ b/README.md @@ -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. \ No newline at end of file +```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. \ No newline at end of file diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..c1d80b9 --- /dev/null +++ b/docs/api-reference.md @@ -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 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e7ddca5..2c87e59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"] diff --git a/scripts/test_mcp_server.py b/scripts/test_mcp_server.py new file mode 100644 index 0000000..d97a05b --- /dev/null +++ b/scripts/test_mcp_server.py @@ -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()) \ No newline at end of file diff --git a/src/mcrentcast/config.py b/src/mcrentcast/config.py index 08a73e7..bbbf08d 100644 --- a/src/mcrentcast/config.py +++ b/src/mcrentcast/config.py @@ -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) diff --git a/src/mcrentcast/database.py b/src/mcrentcast/database.py index 30d6d1f..2f17dfd 100644 --- a/src/mcrentcast/database.py +++ b/src/mcrentcast/database.py @@ -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 ( diff --git a/src/mcrentcast/mock_api.py b/src/mcrentcast/mock_api.py index ac7ed83..f3a4098 100644 --- a/src/mcrentcast/mock_api.py +++ b/src/mcrentcast/mock_api.py @@ -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) \ No newline at end of file + 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() \ No newline at end of file diff --git a/src/mcrentcast/server.py b/src/mcrentcast/server.py index a4d0715..fd55999 100644 --- a/src/mcrentcast/server.py +++ b/src/mcrentcast/server.py @@ -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() \ No newline at end of file + # Keep the process running for MCP communication + import asyncio + try: + asyncio.get_event_loop().run_forever() + except KeyboardInterrupt: + pass \ No newline at end of file