Skip to main content
Glama

Rentcast MCP Server

by tandat8503
index.ts•31.1 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { config } from './services/config.js'; import { rentcastAPI } from './services/rentcast-api.js'; import { PropertySearchSchema, RandomPropertiesSchema, MarketAnalysisSchema, AVMSchema, ListingSearchSchema, PropertyDetailSchema, RentEstimateSchema, RentEstimateResponse, ListingTypeSchema } from './types/index.js'; import { z } from 'zod'; // ======================================== // šŸ› ļø SMART HELPER FUNCTION // ======================================== /** * Smart property formatter that handles all Rentcast API data structures * Automatically detects property type and formats accordingly */ function formatPropertyInfo(prop: any): string { const address = prop.formattedAddress || 'Address not available'; // Smart price detection and formatting let priceDisplay = 'N/A'; let priceType = ''; if (prop.price) { // Sale listing or rental listing if (prop.rent || prop.listingType === 'Rental') { // Rental listing - price is monthly rent priceDisplay = `$${Number(prop.price).toLocaleString()}/month`; priceType = ' (Monthly Rent)'; } else { // Sale listing - price is sale price priceDisplay = `$${Number(prop.price).toLocaleString()}`; if (prop.status === 'Active') { priceType = ' (Active Listing)'; } else { priceType = ' (Listing)'; } } } else if (prop.rent) { // Rental listing with rent field priceDisplay = `$${Number(prop.rent).toLocaleString()}/month`; priceType = ' (Monthly Rent)'; } else if (prop.lastSalePrice) { // Property record with last sale price priceDisplay = `$${Number(prop.lastSalePrice).toLocaleString()}`; priceType = ' (Last Sale)'; } else if (prop.history && Object.keys(prop.history).length > 0) { // Try to get price from history const saleDates = Object.keys(prop.history).filter(date => { const historyEntry = prop.history[date as keyof typeof prop.history]; return historyEntry?.event === 'Sale' || historyEntry?.event === 'Sale Listing'; }).sort().reverse(); if (saleDates.length > 0) { const latestSale = prop.history[saleDates[0]!]; if (latestSale?.price) { priceDisplay = `$${Number(latestSale.price).toLocaleString()}`; priceType = ` (${saleDates[0]})`; } else if (latestSale?.event === 'Sale' || latestSale?.event === 'Sale Listing') { priceDisplay = `Sale recorded (${saleDates[0]})`; priceType = ' - No price data'; } } } // If still no price data, show property type and year built if (priceDisplay === 'N/A') { priceDisplay = prop.propertyType || 'Property'; priceType = prop.yearBuilt ? ` (${prop.yearBuilt})` : ''; } // Safe null/undefined checking for all fields const beds = prop.bedrooms != null ? `${prop.bedrooms} bed` : 'N/A bed'; const baths = prop.bathrooms != null ? `${prop.bathrooms} bath` : 'N/A bath'; const sqft = prop.squareFootage != null ? `${Number(prop.squareFootage).toLocaleString()} sqft` : 'N/A'; // Additional details with safe checking const lotSize = prop.lotSize != null ? ` | 🌳 ${Number(prop.lotSize).toLocaleString()} sqft lot` : ''; const yearBuilt = prop.yearBuilt != null ? ` | šŸ“… ${prop.yearBuilt} built` : ''; const lastSaleDate = prop.lastSaleDate ? ` | Date: ${prop.lastSaleDate.split('T')[0]} last sale` : ''; const status = prop.status ? ` | Status: ${prop.status}` : ''; const daysOnMarket = prop.daysOnMarket != null ? ` | Days: ${prop.daysOnMarket} days on market` : ''; const propertyType = prop.propertyType ? ` | Type: ${prop.propertyType}` : ''; // Build comprehensive property info let propertyInfo = `Address: ${address}\nPrice: ${priceDisplay}${priceType} | Beds: ${beds} | Baths: ${baths} | SqFt: ${sqft}`; // Add optional details if (lotSize || yearBuilt || lastSaleDate || status || daysOnMarket || propertyType) { propertyInfo += `${lotSize}${yearBuilt}${lastSaleDate}${status}${daysOnMarket}${propertyType}`; } return propertyInfo; } /** * Format sale market data for market analysis tool */ function formatSaleMarketData(saleData: any): string { if (!saleData) return ''; let result = `\nSales Market:`; // Current month data if (saleData.averagePrice !== undefined) { result += `\nAverage Price: $${Number(saleData.averagePrice).toLocaleString()}`; } if (saleData.medianPrice !== undefined) { result += `\nšŸ“ˆ Median Price: $${Number(saleData.medianPrice).toLocaleString()}`; } if (saleData.averagePricePerSquareFoot !== undefined) { result += `\nšŸ“ Avg Price/Sqft: $${Number(saleData.averagePricePerSquareFoot).toFixed(2)}`; } if (saleData.averageDaysOnMarket !== undefined) { result += `\nAvg Days on Market: ${Number(saleData.averageDaysOnMarket).toFixed(1)}`; } if (saleData.newListings !== undefined) { result += `\nšŸ†• New Listings: ${saleData.newListings}`; } if (saleData.totalListings !== undefined) { result += `\nšŸ“‹ Total Listings: ${saleData.totalListings}`; } // Property type breakdown if (saleData.dataByPropertyType && saleData.dataByPropertyType.length > 0) { result += `\n\nšŸ  By Property Type:`; saleData.dataByPropertyType.slice(0, 3).forEach((typeData: any) => { const avgPrice = typeData.averagePrice ? `$${Number(typeData.averagePrice).toLocaleString()}` : 'N/A'; result += `\n• ${typeData.propertyType}: ${avgPrice} avg`; }); } return result; } /** * Format rental market data for market analysis tool */ function formatRentalMarketData(rentalData: any): string { if (!rentalData) return ''; let result = `\n\nšŸ˜ļø Rental Market:`; // Current month data if (rentalData.averageRent !== undefined) { result += `\nšŸ’° Average Rent: $${Number(rentalData.averageRent).toLocaleString()}/month`; } if (rentalData.medianRent !== undefined) { result += `\nšŸ“ˆ Median Rent: $${Number(rentalData.medianRent).toLocaleString()}/month`; } if (rentalData.averageRentPerSquareFoot !== undefined) { result += `\nšŸ“ Avg Rent/Sqft: $${Number(rentalData.averageRentPerSquareFoot).toFixed(2)}`; } if (rentalData.newListings !== undefined) { result += `\nšŸ†• New Listings: ${rentalData.newListings}`; } if (rentalData.totalListings !== undefined) { result += `\nšŸ“‹ Total Listings: ${rentalData.totalListings}`; } // Property type breakdown if (rentalData.dataByPropertyType && rentalData.dataByPropertyType.length > 0) { result += `\n\nšŸ  By Property Type:`; rentalData.dataByPropertyType.slice(0, 3).forEach((typeData: any) => { const avgRent = typeData.averageRent ? `$${Number(typeData.averageRent).toLocaleString()}/month` : 'N/A'; result += `\n• ${typeData.propertyType}: ${avgRent} avg`; }); } return result; } /** * Format comparables data for AVM and rent estimates tools */ function formatComparables(comparables: any[], isRental: boolean = false): string { if (!comparables || comparables.length === 0) return ''; let resultText = `\n\nšŸ“Š Comparable Properties (${comparables.length}):`; comparables.slice(0, 3).forEach((comp: any, index: number) => { const priceUnit = isRental ? '/month' : ''; const compPrice = comp.price ? `$${Number(comp.price).toLocaleString()}${priceUnit}` : 'N/A'; const distance = comp.distance ? `${(comp.distance * 0.621371).toFixed(2)} miles` : 'N/A'; const correlation = comp.correlation ? `${(comp.correlation * 100).toFixed(1)}% match` : 'N/A'; resultText += `\n${index + 1}. šŸ“ ${comp.formattedAddress}`; resultText += `\n šŸ’° ${compPrice} | šŸ›ļø ${comp.bedrooms || 'N/A'} bed | 🚿 ${comp.bathrooms || 'N/A'} bath`; resultText += `\n šŸ“ ${comp.squareFootage ? `${Number(comp.squareFootage).toLocaleString()} sqft` : 'N/A'} | šŸŒ ${distance} | šŸŽÆ ${correlation}`; }); return resultText; } /** * Build search parameters for property search tools */ function buildPropertySearchParams(params: any, includeLimit: boolean = true): any { const searchParams: any = {}; if (includeLimit && params.limit) { searchParams.limit = params.limit; } if (params.city) searchParams.city = params.city; if (params.state) searchParams.state = params.state; if (params.zipCode) searchParams.zipCode = params.zipCode; if (params.bedrooms) searchParams.bedrooms = params.bedrooms; if (params.bathrooms) searchParams.bathrooms = params.bathrooms; if (params.propertyType) searchParams.propertyType = params.propertyType; return searchParams; } /** * Build search parameters for AVM and rent estimate tools */ function buildAVMSearchParams(params: any): any { const searchParams: any = {}; // Prioritize address if provided, otherwise use other parameters if (params.address) { searchParams.address = params.address; } else if (params.latitude && params.longitude) { searchParams.latitude = params.latitude; searchParams.longitude = params.longitude; } else if (params.propertyId) { searchParams.propertyId = params.propertyId; } // Add additional parameters if available (these improve accuracy) if (params.propertyType) searchParams.propertyType = params.propertyType; if (params.bedrooms !== undefined && params.bedrooms !== null) searchParams.bedrooms = params.bedrooms; if (params.bathrooms !== undefined && params.bathrooms !== null) searchParams.bathrooms = params.bathrooms; if (params.squareFootage !== undefined && params.squareFootage !== null) searchParams.squareFootage = params.squareFootage; return searchParams; } /** * Create standardized error response */ function createErrorResponse(message: string, error?: any): any { const errorText = error ? `${message}: ${error}` : message; return { content: [{ type: "text", text: errorText }] }; } /** * Create standardized success response */ function createSuccessResponse(text: string): any { return { content: [{ type: "text", text: text }] }; } /** * Extract property parameters for estimation tools */ function extractPropertyParams(property: any): string { return `šŸ“ Address: ${property.formattedAddress || 'N/A'}\n` + `šŸŒ Latitude: ${property.latitude || 'N/A'}\n` + `šŸŒ Longitude: ${property.longitude || 'N/A'}\n` + `šŸ  Property Type: ${property.propertyType || 'N/A'}\n` + `šŸ›ļø Bedrooms: ${property.bedrooms || 'N/A'}\n` + `🚿 Bathrooms: ${property.bathrooms || 'N/A'}\n` + `šŸ“ Square Footage: ${property.squareFootage || 'N/A'}`; } /** * Create property value estimation parameters display */ function createPropertyValueParams(property: any, currentPrice?: number): string { const baseParams = extractPropertyParams(property); const priceInfo = currentPrice ? `\nšŸ’° Current Price: $${Number(currentPrice).toLocaleString()}` : ''; return `\n\nšŸ’” **Property Value Estimation Parameters (Copy to get_property_value tool):**\n` + `${baseParams}${priceInfo}\n\n` + `šŸ’” **Copy the Address, Latitude, Longitude, Property Type, Bedrooms, Bathrooms, and Square Footage values above to the get_property_value tool fields!**`; } /** * Create rent estimation parameters display */ function createRentEstimationParams(property: any, currentRent?: number): string { const baseParams = extractPropertyParams(property); const rentInfo = currentRent ? `\nšŸ’° Current Rent: $${Number(currentRent).toLocaleString()}/month` : ''; return `\n\nšŸ’” **Rent Estimation Parameters (Copy to get_rent_estimates tool):**\n` + `${baseParams}${rentInfo}\n\n` + `šŸ’” **Copy the Address, Latitude, Longitude, Property Type, Bedrooms, Bathrooms, and Square Footage values above to the get_rent_estimates tool fields!**`; } // ======================================== // šŸš€ MCP SERVER SETUP // ======================================== const server = new McpServer({ name: "rentcast-mcp", version: "1.0.0" }); // ======================================== // šŸ› ļø MCP TOOLS (Rentcast API Endpoints) // ======================================== // Tool 1: Search Properties server.tool( "search_properties", "Search for properties with basic property information (default: 15, max: 50 for free tier) including city, state, bedrooms, bathrooms, square footage, lot size, and year built. Note: Price data may not be available for all properties.", PropertySearchSchema.shape, async (params) => { try { const searchParams = buildPropertySearchParams(params); const result = await rentcastAPI.searchProperties(searchParams); if (!result.success) { return createErrorResponse("Error searching properties", result.error); } const properties = result.data as any[]; const summary = `Found ${properties.length} properties`; // Process each property individually based on actual API structure const propertyDetails = properties.slice(0, 10).map(prop => { return formatPropertyInfo(prop); }).join('\n\n'); const resultText = `${summary}\n\n${propertyDetails}${properties.length > 10 ? '\n\n... and more properties available' : ''}`; return createSuccessResponse(resultText); } catch (error) { return createErrorResponse("Failed to search properties", error instanceof Error ? error.message : 'Unknown error'); } } ); // Tool 2: Get Random Properties server.tool( "get_random_properties", "Get random properties with comprehensive info (default: 10, max: 50 for free tier) for market analysis including price history, lot size, and year built", RandomPropertiesSchema.shape, async (params) => { try { const searchParams = buildPropertySearchParams(params); const result = await rentcastAPI.getRandomProperties(searchParams); if (!result.success) { return createErrorResponse("Error getting random properties", result.error); } const properties = result.data as any[]; const summary = `Retrieved ${properties.length} random properties`; // Process each property individually const sampleProperties = properties.slice(0, 5).map(prop => { return formatPropertyInfo(prop); }).join('\n\n'); const resultText = `${summary}\n\nSample Properties:\n\n${sampleProperties}${properties.length > 5 ? '\n\n... and more properties available' : ''}`; return createSuccessResponse(resultText); } catch (error) { return createErrorResponse("Failed to get random properties", error instanceof Error ? error.message : 'Unknown error'); } } ); // Tool 3: Market Analysis server.tool( "analyze_market", "Get comprehensive market statistics and trends for specific locations", MarketAnalysisSchema.shape, async (params) => { try { const searchParams: any = { dataType: params.dataType }; if (params.zipCode) searchParams.zipCode = params.zipCode; if (params.city) searchParams.city = params.city; if (params.state) searchParams.state = params.state; const result = await rentcastAPI.getMarketData(searchParams); if (!result.success) { return createErrorResponse("Error analyzing market", result.error); } // Simplified market data handling - focus on the structure we know API returns const market = Array.isArray(result.data) ? result.data[0] : result.data; if (!market || (!market.saleData && !market.rentalData)) { return createErrorResponse("No market data found for the specified location"); } let resultText = `šŸ“Š Market Statistics for ${market.zipCode ? `ZIP: ${market.zipCode}` : (params.city ? `${params.city}, ${params.state}` : 'Location')}\n`; // Add location info if (market.zipCode) { resultText += `\nšŸ“ Location: ZIP ${market.zipCode}`; } if (market.city && market.state) { resultText += `\nšŸ™ļø ${market.city}, ${market.state}`; } // Format market data if (market.saleData) { resultText += formatSaleMarketData(market.saleData); } if (market.rentalData) { resultText += formatRentalMarketData(market.rentalData); } return createSuccessResponse(resultText); } catch (error) { return createErrorResponse("Failed to analyze market", error instanceof Error ? error.message : 'Unknown error'); } } ); // Tool 4: Property Valuation (AVM) server.tool( "get_property_value", "Get automated property value estimates with comparable properties", AVMSchema.shape, async (params: z.infer<typeof AVMSchema>) => { try { const searchParams = buildAVMSearchParams(params); // Additional validation to ensure we have required parameters if (!searchParams.propertyId && !searchParams.address && (!searchParams.latitude || !searchParams.longitude)) { return createErrorResponse( "āŒ **Missing Required Parameters for Property Valuation**\n\n" + "šŸ’” **You must provide ONE of the following options:**\n\n" + "**Option 1: Property Address**\n" + "• `address`: Full property address (e.g., '1011 W 23rd St, Apt 101, Austin, TX 78705')\n\n" + "**Option 2: GPS Coordinates**\n" + "• `latitude`: Property latitude (e.g., 30.287007)\n" + "• `longitude`: Property longitude (e.g., -97.748941)\n\n" + "**Option 3: Property ID**\n" + "• `propertyId`: Unique identifier from Rentcast database\n\n" + "šŸ” **Optional Parameters (improve accuracy):**\n" + "• `propertyType`: Apartment, House, Condo, etc.\n" + "• `bedrooms`: Number of bedrooms\n" + "• `bathrooms`: Number of bathrooms\n" + "• `squareFootage`: Property size in sq ft" ); } const result = await rentcastAPI.getPropertyValue(searchParams); if (!result.success) { return createErrorResponse("Error getting property value", result.error); } const avm = result.data as any; if (!avm) { return createErrorResponse("No property value data found"); } let resultText = `šŸ’° Estimated Value: ${avm.price ? `$${Number(avm.price).toLocaleString()}` : 'N/A'}`; const range = avm.priceRangeLow && avm.priceRangeHigh ? ` (Range: $${Number(avm.priceRangeLow).toLocaleString()} - $${Number(avm.priceRangeHigh).toLocaleString()})` : ''; resultText += `${range}`; if (avm.comparables && avm.comparables.length > 0) { resultText += formatComparables(avm.comparables); } return createSuccessResponse(resultText); } catch (error) { return createErrorResponse("Failed to get property value", error instanceof Error ? error.message : 'Unknown error'); } } ); // Tool 5: Rent Estimates server.tool( "get_rent_estimates", "Get long-term rent estimates with comparable rental properties. This tool helps you estimate monthly rental prices for properties based on location, property characteristics, and market data.", RentEstimateSchema.shape, async (params: z.infer<typeof RentEstimateSchema>) => { try { // Validate parameters using Zod schema const validatedParams = RentEstimateSchema.parse(params); // Build search parameters for rent estimates const searchParams: Record<string, any> = {}; if (validatedParams.propertyId) searchParams.propertyId = validatedParams.propertyId; if (validatedParams.address) searchParams.address = validatedParams.address; if (validatedParams.latitude) searchParams.latitude = validatedParams.latitude; if (validatedParams.longitude) searchParams.longitude = validatedParams.longitude; if (validatedParams.propertyType) searchParams.propertyType = validatedParams.propertyType; if (validatedParams.bedrooms) searchParams.bedrooms = validatedParams.bedrooms; if (validatedParams.bathrooms) searchParams.bathrooms = validatedParams.bathrooms; if (validatedParams.squareFootage) searchParams.squareFootage = validatedParams.squareFootage; // Additional validation to ensure we have required parameters if (!searchParams.propertyId && !searchParams.address && (!searchParams.latitude || !searchParams.longitude)) { return createErrorResponse( "āŒ **Missing Required Parameters for Rent Estimates**\n\n" + "šŸ’” **You must provide ONE of the following options:**\n\n" + "**Option 1: Property Address**\n" + "• `address`: Full property address (e.g., '1011 W 23rd St, Apt 101, Austin, TX 78705')\n\n" + "**Option 2: GPS Coordinates**\n" + "• `latitude`: Property latitude (e.g., 30.287007)\n" + "• `longitude`: Property longitude (e.g., -97.748941)\n\n" + "**Option 3: Property ID**\n" + "• `propertyId`: Unique identifier from Rentcast database\n\n" + "šŸ” **Optional Parameters (improve accuracy):**\n" + "• `propertyType`: Apartment, House, Condo, etc.\n" + "• `bedrooms`: Number of bedrooms\n" + "• `bathrooms`: Number of bathrooms\n" + "• `squareFootage`: Property size in sq ft\n\n" + "šŸ“‹ **Example Usage:**\n" + "```json\n" + "{\n" + ' "address": "1011 W 23rd St, Apt 101, Austin, TX 78705",\n' + ' "propertyType": "Apartment",\n' + ' "bedrooms": 1,\n' + ' "bathrooms": 1,\n' + ' "squareFootage": 450\n' + "}\n" + "```" ); } const result = await rentcastAPI.getRentEstimates(searchParams); if (!result.success) { return createErrorResponse("Error getting rent estimates", result.error); } const rentData = result.data as RentEstimateResponse; if (!rentData) { return createErrorResponse("No rent estimate data found"); } // Format the response let resultText = `šŸ  **Rent Estimate Results**\n\n`; // Add usage tips resultText += `šŸ’” **Tool Usage Tips:**\n`; resultText += `• Use this tool to estimate monthly rental prices for properties\n`; resultText += `• Provide more details (bedrooms, bathrooms, square footage) for better accuracy\n`; resultText += `• Results include comparable properties for market analysis\n\n`; // Property identification if (rentData.address) { resultText += `šŸ“ **Property:** ${rentData.address}\n`; } if (rentData.propertyType) { resultText += `šŸ  **Type:** ${rentData.propertyType}\n`; } if (rentData.bedrooms !== undefined) { resultText += `šŸ›ļø **Bedrooms:** ${rentData.bedrooms}\n`; } if (rentData.bathrooms !== undefined) { resultText += `🚿 **Bathrooms:** ${rentData.bathrooms}\n`; } if (rentData.squareFootage) { resultText += `šŸ“ **Square Footage:** ${rentData.squareFootage.toLocaleString()} sqft\n`; } resultText += `\nšŸ’° **Estimated Monthly Rent:** `; if (rentData.rent) { resultText += `$${Number(rentData.rent).toLocaleString()}/month`; // Add rent range if available if (rentData.rentRangeLow && rentData.rentRangeHigh) { resultText += `\nšŸ“Š **Rent Range:** $${Number(rentData.rentRangeLow).toLocaleString()} - $${Number(rentData.rentRangeHigh).toLocaleString()}/month`; } } else { resultText += `N/A`; } // Add comparables if available if (rentData.comparables && rentData.comparables.length > 0) { resultText += `\n\nšŸ˜ļø **Comparable Properties:**\n`; rentData.comparables.slice(0, 5).forEach((comp, index) => { resultText += `\n${index + 1}. **${comp.address}**\n`; resultText += ` šŸ’° Rent: $${Number(comp.rent).toLocaleString()}/month`; if (comp.bedrooms !== undefined) resultText += ` | šŸ›ļø ${comp.bedrooms} bed`; if (comp.bathrooms !== undefined) resultText += ` | 🚿 ${comp.bathrooms} bath`; if (comp.squareFootage) resultText += ` | šŸ“ ${comp.squareFootage.toLocaleString()} sqft`; if (comp.distance) resultText += ` | šŸ“ ${comp.distance.toFixed(1)} miles away`; }); } // Add helpful footer resultText += `\n\nšŸ” **Need More Data?**\n`; resultText += `• Use \`get_property_details\` to get comprehensive property information\n`; resultText += `• Use \`get_rental_listings\` to see actual rental listings in the area\n`; resultText += `• Use \`analyze_market\` to understand rental market trends\n\n`; return createSuccessResponse(resultText); } catch (error) { if (error instanceof z.ZodError) { const errorDetails = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createErrorResponse(`Invalid parameters: ${errorDetails}`); } return createErrorResponse("Failed to get rent estimates", error instanceof Error ? error.message : 'Unknown error'); } } ); // Tool 6: Sale Listings server.tool( "get_sale_listings", "Get sale listings with comprehensive property information. This tool searches for properties currently for sale.", ListingSearchSchema.shape, async (params) => { try { const searchParams = buildPropertySearchParams(params); const result = await rentcastAPI.getSaleListings(searchParams); if (!result.success) { return createErrorResponse("Error getting sale listings", result.error); } const listings = result.data as any[]; const summary = `Found ${listings.length} sale listings`; const listingDetails = listings.slice(0, 8).map(listing => { // Use actual Rentcast API data structure const propertyInfo = formatPropertyInfo(listing); // Add compact parameter suggestions const params = `\nšŸ’” **Quick Parameters:** Address: "${listing.formattedAddress}", Lat: ${listing.latitude}, Lng: ${listing.longitude}, Type: "${listing.propertyType}", Beds: ${listing.bedrooms || 'N/A'}, Baths: ${listing.bathrooms || 'N/A'}, SqFt: ${listing.squareFootage || 'N/A'}`; return propertyInfo + params; }).join('\n\n'); const resultText = `${summary}\n\n${listingDetails}${listings.length > 8 ? '\n\n... and more listings available' : ''}`; return createSuccessResponse(resultText); } catch (error) { return createErrorResponse("Failed to get sale listings", error instanceof Error ? error.message : 'Unknown error'); } } ); // Tool 7: Property Details (Enhanced) server.tool( "get_property_details", "Get detailed property information and prepare parameters for property value estimation", PropertyDetailSchema.shape, async (params) => { try { // This tool helps prepare property data for other tools const result = await rentcastAPI.getProperty(params.id); if (!result.success) { return createErrorResponse("Error getting property details", result.error); } const property = result.data as any; if (!property) { return createErrorResponse("No property details found"); } // Format property details const propertyInfo = formatPropertyInfo(property); // Prepare parameters for property value estimation const valueEstimationParams = createPropertyValueParams(property); const rentEstimationParams = createRentEstimationParams(property); const additionalInfo = `\n\nšŸ’” **Copy these values to the get_property_value tool to get an automated valuation estimate!**\n\n` + `šŸ  **Rent Estimation Parameters:**\n` + `${extractPropertyParams(property)}\n\n` + `šŸ’” **Copy these values to the get_rent_estimates tool to get rent estimates!**`; const resultText = propertyInfo + valueEstimationParams + additionalInfo; return createSuccessResponse(resultText); } catch (error) { return createErrorResponse("Failed to get property details", error instanceof Error ? error.message : 'Unknown error'); } } ); // Tool 8: Rental Listings server.tool( "get_rental_listings", "Get rental listings with comprehensive property information. This tool searches for properties currently for rent.", ListingSearchSchema.shape, async (params) => { try { const searchParams = buildPropertySearchParams(params); const result = await rentcastAPI.getRentalListings(searchParams); if (!result.success) { return createErrorResponse("Error getting rental listings", result.error); } const listings = result.data as any[]; const summary = `Found ${listings.length} rental listings`; const listingDetails = listings.slice(0, 8).map(listing => { // Use actual Rentcast API data structure const propertyInfo = formatPropertyInfo(listing); // Add compact parameter suggestions const params = `\nšŸ’” **Quick Parameters:** Address: "${listing.formattedAddress}", Lat: ${listing.latitude}, Lng: ${listing.longitude}, Type: "${listing.propertyType}", Beds: ${listing.bedrooms || 'N/A'}, Baths: ${listing.bathrooms || 'N/A'}, SqFt: ${listing.squareFootage || 'N/A'}`; return propertyInfo + params; }).join('\n\n'); const resultText = `${summary}\n\n${listingDetails}${listings.length > 8 ? '\n\n... and more listings available' : ''}`; return createSuccessResponse(resultText); } catch (error) { return createErrorResponse("Failed to get rental listings", error instanceof Error ? error.message : 'Unknown error'); } } ); // Tool 8: Property Details (Enhanced - already defined above) // This tool was moved to Tool 7 above for better organization // ======================================== // šŸš€ START SERVER // ======================================== async function main() { try { const transport = new StdioServerTransport(); await server.connect(transport); } catch (error) { process.exit(1); } } // Handle process termination process.on('SIGINT', () => { process.exit(0); }); process.on('SIGTERM', () => { process.exit(0); }); // Start the server main().catch((error) => { process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tandat8503/mcp_rentcast'

If you have feedback or need assistance with the MCP directory API, please join our Discord server