Skip to main content
Glama
klappe-pm

Real Estate MCP Server

by klappe-pm
zillow-service.ts8 kB
import { httpClient } from './http-client.js'; import { dataTransformer } from './data-transformer.js'; import { appConfig } from '../config/index.js'; import { Property, SearchQuery, ZillowPropertyResponse } from '../types/real-estate.js'; import { APIError, DataSourceError, logError } from '../utils/errors.js'; /** * Zillow API service using RapidAPI */ export class ZillowService { private readonly baseUrl = 'https://zillow-com1.p.rapidapi.com'; private readonly headers = { 'x-rapidapi-host': appConfig.rapidApiHostZillow, 'x-rapidapi-key': appConfig.rapidApiKey, }; /** * Search properties using Zillow API */ async searchProperties(query: SearchQuery): Promise<Property[]> { try { console.log(`Searching Zillow for properties: ${query.location}`); // Transform our search query to Zillow API parameters const zillowParams = this.buildZillowSearchParams(query); const response = await httpClient.get<any>( `${this.baseUrl}/propertyExtendedSearch`, { headers: this.headers, params: zillowParams, provider: 'zillow', } ); // Handle different response structures from Zillow API const properties = this.extractPropertiesFromResponse(response); // Transform to our Property interface const transformedProperties = properties.map(prop => dataTransformer.transformZillowProperty(prop) ); console.log(`Found ${transformedProperties.length} properties from Zillow`); return transformedProperties; } catch (error) { logError(error as Error, 'Zillow Search'); if (error instanceof APIError) { throw new DataSourceError( `Zillow search failed: ${error.message}`, 'Zillow', true // fallback available ); } throw error; } } /** * Get detailed property information by Zillow property ID */ async getPropertyDetails(zpid: string): Promise<Property | null> { try { console.log(`Getting Zillow property details for ZPID: ${zpid}`); const response = await httpClient.get<any>( `${this.baseUrl}/property`, { headers: this.headers, params: { zpid }, provider: 'zillow', } ); if (!response || !response.zpid) { return null; } const property = dataTransformer.transformZillowProperty(response); console.log(`Retrieved property details for ${property.address.street || 'Unknown Address'}`); return property; } catch (error) { logError(error as Error, 'Zillow Property Details'); if (error instanceof APIError && error.statusCode === 404) { return null; // Property not found } throw new DataSourceError( `Failed to get property details from Zillow: ${error}`, 'Zillow' ); } } /** * Get property photos */ async getPropertyPhotos(zpid: string): Promise<string[]> { try { const response = await httpClient.get<any>( `${this.baseUrl}/images`, { headers: this.headers, params: { zpid }, provider: 'zillow', } ); // Extract photo URLs from response const photos: string[] = []; if (response.images) { response.images.forEach((img: any) => { if (img.url) { photos.push(img.url); } }); } return photos; } catch (error) { logError(error as Error, 'Zillow Photos'); return []; // Return empty array on error, photos are not critical } } /** * Get property price history */ async getPriceHistory(zpid: string): Promise<any[]> { try { const response = await httpClient.get<any>( `${this.baseUrl}/priceHistory`, { headers: this.headers, params: { zpid }, provider: 'zillow', } ); return response.priceHistory || []; } catch (error) { logError(error as Error, 'Zillow Price History'); return []; // Return empty array on error } } /** * Get comparable properties (comps) */ async getComparableProperties(zpid: string): Promise<Property[]> { try { const response = await httpClient.get<any>( `${this.baseUrl}/similarProperties`, { headers: this.headers, params: { zpid }, provider: 'zillow', } ); if (!response.comparables) { return []; } return response.comparables.map((comp: ZillowPropertyResponse) => dataTransformer.transformZillowProperty(comp) ); } catch (error) { logError(error as Error, 'Zillow Comparables'); return []; } } /** * Build Zillow API parameters from our SearchQuery */ private buildZillowSearchParams(query: SearchQuery): Record<string, any> { const params: Record<string, any> = { location: query.location, }; if (query.minPrice) params.price_min = query.minPrice; if (query.maxPrice) params.price_max = query.maxPrice; if (query.minBeds) params.beds_min = query.minBeds; if (query.minBaths) params.baths_min = query.minBaths; if (query.minSqft) params.sqft_min = query.minSqft; if (query.maxSqft) params.sqft_max = query.maxSqft; if (query.propertyType) { params.home_type = this.mapPropertyTypeToZillow(query.propertyType); } // Default to first page if not specified params.page = query.page || 1; // Limit results to prevent excessive API usage params.limit = 25; return params; } /** * Map our property types to Zillow's expected values */ private mapPropertyTypeToZillow(propertyType: string): string { const typeMap: Record<string, string> = { 'single_family': 'Houses', 'condo': 'Condos', 'townhouse': 'Townhomes', 'apartment': 'Apartments', 'multi_family': 'Multi-family', 'land': 'Lots', 'manufactured': 'Manufactured', }; return typeMap[propertyType.toLowerCase()] || propertyType; } /** * Extract properties array from various Zillow API response structures */ private extractPropertiesFromResponse(response: any): ZillowPropertyResponse[] { // Handle different response structures that Zillow API might return if (response.results) { return Array.isArray(response.results) ? response.results : []; } if (response.searchResults && response.searchResults.listResults) { return response.searchResults.listResults; } if (response.props) { return Array.isArray(response.props) ? response.props : []; } if (Array.isArray(response)) { return response; } // If it's a single property object, wrap it in an array if (response.zpid) { return [response]; } console.warn('Unknown Zillow API response structure:', Object.keys(response)); return []; } /** * Check if Zillow service is available */ async healthCheck(): Promise<boolean> { try { // Try a minimal API call to check if service is responsive await httpClient.get( `${this.baseUrl}/property`, { headers: this.headers, params: { zpid: '123456789' }, // Use a dummy ZPID provider: 'zillow', cacheTtl: 60, // Short cache for health checks } ); return true; // Any response (even 404) means service is up } catch (error) { const apiError = error as APIError; // 404 is expected for dummy ZPID, means service is working if (apiError.statusCode === 404) { return true; } // Other errors indicate service issues return false; } } } // Singleton instance export const zillowService = new ZillowService();

Latest Blog Posts

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/klappe-pm/Real-Estate-MCP'

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