search_car_deals
Search used car listings from Cars.com, Autotrader, and KBB with filters for price, mileage, year, and vehicle history to find suitable deals.
Instructions
Search for car deals across multiple sources (Cars.com, Autotrader, KBB). Returns listings with prices, mileage, deal ratings, and links.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| make | Yes | Car manufacturer (e.g., Toyota, Honda, Ford) | |
| model | Yes | Car model (e.g., Camry, Civic, F-150) | |
| zip | No | ZIP code for location-based search (default: 90210) | |
| yearMin | No | Minimum model year | |
| yearMax | No | Maximum model year | |
| priceMax | No | Maximum price in dollars | |
| mileageMax | No | Maximum mileage | |
| maxResults | No | Maximum results per source (default: 10) | |
| sources | No | Sources to search: "cars.com", "autotrader", "kbb". Default: all | |
| oneOwner | No | Filter for CARFAX 1-Owner vehicles only | |
| noAccidents | No | Filter for vehicles with no accidents or damage reported | |
| personalUse | No | Filter for vehicles used for personal use only (not rental/fleet) |
Implementation Reference
- src/server.js:32-96 (registration)Tool registration in ListToolsRequestHandler, defining name, description, and complete input schema for 'search_car_deals'.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'search_car_deals', description: 'Search for car deals across multiple sources (Cars.com, Autotrader, KBB). Returns listings with prices, mileage, deal ratings, and links.', inputSchema: { type: 'object', properties: { make: { type: 'string', description: 'Car manufacturer (e.g., Toyota, Honda, Ford)', }, model: { type: 'string', description: 'Car model (e.g., Camry, Civic, F-150)', }, zip: { type: 'string', description: 'ZIP code for location-based search (default: 90210)', }, yearMin: { type: 'integer', description: 'Minimum model year', }, yearMax: { type: 'integer', description: 'Maximum model year', }, priceMax: { type: 'integer', description: 'Maximum price in dollars', }, mileageMax: { type: 'integer', description: 'Maximum mileage', }, maxResults: { type: 'integer', description: 'Maximum results per source (default: 10)', }, sources: { type: 'array', items: { type: 'string' }, description: 'Sources to search: "cars.com", "autotrader", "kbb". Default: all', }, oneOwner: { type: 'boolean', description: 'Filter for CARFAX 1-Owner vehicles only', }, noAccidents: { type: 'boolean', description: 'Filter for vehicles with no accidents or damage reported', }, personalUse: { type: 'boolean', description: 'Filter for vehicles used for personal use only (not rental/fleet)', }, }, required: ['make', 'model'], }, }, ], }; });
- src/server.js:102-213 (handler)Main handler logic for executing 'search_car_deals': parses arguments, conditionally runs scrapers for specified sources, aggregates results, formats as readable markdown output with error handling.if (name === 'search_car_deals') { try { const params = { make: args.make, model: args.model, zip: args.zip || '90210', yearMin: args.yearMin, yearMax: args.yearMax, priceMax: args.priceMax, mileageMax: args.mileageMax, // CarFax history filters oneOwner: args.oneOwner, noAccidents: args.noAccidents, personalUse: args.personalUse, }; const maxResults = args.maxResults || 10; const sources = args.sources || ['cars.com', 'autotrader', 'kbb']; let allListings = []; let errors = []; // Run selected scrapers const scraperPromises = []; if (sources.includes('cars.com')) { scraperPromises.push( scrapeCarscom(params, maxResults) .then(listings => ({ source: 'Cars.com', listings })) .catch(err => ({ source: 'Cars.com', error: err.message, listings: [] })) ); } if (sources.includes('autotrader')) { scraperPromises.push( scrapeAutotrader(params, maxResults) .then(listings => ({ source: 'Autotrader', listings })) .catch(err => ({ source: 'Autotrader', error: err.message, listings: [] })) ); } if (sources.includes('kbb')) { scraperPromises.push( scrapeKBB(params, maxResults) .then(listings => ({ source: 'KBB', listings })) .catch(err => ({ source: 'KBB', error: err.message, listings: [] })) ); } const results = await Promise.all(scraperPromises); for (const result of results) { allListings.push(...result.listings); if (result.error) { errors.push(`${result.source}: ${result.error}`); } } // Format output let output = `# Car Deals Search Results\n\n`; output += `**Search:** ${params.make} ${params.model}`; if (params.yearMin || params.yearMax) { output += ` (${params.yearMin || 'any'}-${params.yearMax || 'any'})`; } if (params.priceMax) output += ` | Max Price: $${params.priceMax.toLocaleString()}`; if (params.mileageMax) output += ` | Max Mileage: ${params.mileageMax.toLocaleString()}`; // Show active CarFax filters const activeFilters = []; if (params.oneOwner) activeFilters.push('1-Owner'); if (params.noAccidents) activeFilters.push('No Accidents'); if (params.personalUse) activeFilters.push('Personal Use'); if (activeFilters.length > 0) output += `\n**CarFax Filters:** ${activeFilters.join(', ')}`; output += `\n**Location:** ${params.zip}\n\n`; if (allListings.length === 0) { output += `No listings found.\n`; } else { output += `Found **${allListings.length}** listings:\n\n`; for (const listing of allListings) { output += listing.format() + '\n\n---\n\n'; } } if (errors.length > 0) { output += `\n**Errors:**\n`; for (const err of errors) { output += `- ${err}\n`; } } return { content: [ { type: 'text', text: output, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error searching for car deals: ${error.message}`, }, ], isError: true, }; } }
- src/scraper.js:63-199 (helper)Core scraping helper for Cars.com: uses Puppeteer to load search results, extracts structured data from listings including CarFax filters.async function scrapeCarscom(params, maxResults = 20) { const listings = []; let browser; try { browser = await launchBrowser(); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); // Build URL let url = 'https://www.cars.com/shopping/results/?'; const urlParams = new URLSearchParams(); urlParams.append('stock_type', 'used'); if (params.make) urlParams.append('makes[]', params.make.toLowerCase()); if (params.model) urlParams.append('models[]', `${params.make.toLowerCase()}-${params.model.toLowerCase()}`); if (params.zip) urlParams.append('zip', params.zip); if (params.yearMin) urlParams.append('year_min', params.yearMin); if (params.yearMax) urlParams.append('year_max', params.yearMax); if (params.priceMax) urlParams.append('list_price_max', params.priceMax); if (params.mileageMax) urlParams.append('mileage_max', params.mileageMax); // CarFax history filters if (params.oneOwner) urlParams.append('one_owner', 'true'); if (params.noAccidents) urlParams.append('no_accidents', 'true'); if (params.personalUse) urlParams.append('personal_use', 'true'); url += urlParams.toString(); await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }); await new Promise(r => setTimeout(r, 5000)); // Extract listings from .vehicle-card elements const rawListings = await page.evaluate(() => { const results = []; const cards = document.querySelectorAll('.vehicle-card'); cards.forEach(card => { const text = card.innerText; const lines = text.split('\n').filter(l => l.trim()); let title = null; let price = null; let mileage = null; let dealRating = null; let dealerName = null; let location = null; for (const line of lines) { const trimmed = line.trim(); // Title: Year Make Model (e.g., "2020 Toyota Camry XSE") if (/^(19|20)\d{2}\s+\w+/.test(trimmed) && !title) { title = trimmed; continue; } // Price: "$XX,XXX" (may have "price drop" suffix) const priceMatch = trimmed.match(/^\$[\d,]+/); if (priceMatch && !price) { price = priceMatch[0]; continue; } // Mileage: "XX,XXX mi." if (/^[\d,]+\s*mi\.?$/i.test(trimmed) && !mileage) { mileage = trimmed; continue; } // Deal rating: "Good Deal", "Great Deal", etc. if (/^(great|good|fair|high|no price)/i.test(trimmed) && !dealRating) { dealRating = trimmed.split('|')[0].trim(); continue; } // Location: "City, ST (XX mi.)" if (/^[A-Z][a-z]+.*,\s*[A-Z]{2}\s*\(/i.test(trimmed) && !location) { location = trimmed; continue; } } // Get dealer name - usually after reviews count const dealerMatch = card.querySelector('.dealer-name'); if (dealerMatch) { dealerName = dealerMatch.innerText.trim(); } else { // Fallback: look for line before reviews for (let i = 0; i < lines.length; i++) { if (lines[i].includes('reviews') && i > 0) { dealerName = lines[i - 1].trim(); break; } } } // Get URL from the card link const linkEl = card.querySelector('a.vehicle-card-link'); const href = linkEl ? linkEl.getAttribute('href') : null; // Check for CarFax badges const fullText = text.toLowerCase(); const isOneOwner = fullText.includes('1-owner') || fullText.includes('one owner'); const noAccidents = fullText.includes('no accident') || fullText.includes('clean'); const personalUse = fullText.includes('personal use'); if (title) { results.push({ title, price, mileage, dealRating, dealerName, location, href, isOneOwner, noAccidents, personalUse }); } }); return results; }); for (const item of rawListings.slice(0, maxResults)) { listings.push(new CarListing({ title: item.title, price: item.price, mileage: item.mileage, dealerName: item.dealerName, dealRating: item.dealRating, url: item.href ? `https://www.cars.com${item.href}` : null, source: 'Cars.com', isOneOwner: item.isOneOwner, noAccidents: item.noAccidents, personalUse: item.personalUse })); } await browser.close(); } catch (err) { if (browser) await browser.close(); throw new Error(`Cars.com scraping failed: ${err.message}`); } return listings; }
- src/scraper.js:204-294 (helper)Scraping helper for Autotrader: Puppeteer-based extraction of car listings with price, mileage, dealer info.async function scrapeAutotrader(params, maxResults = 20) { const listings = []; let browser; try { browser = await launchBrowser(); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); // Build URL const make = params.make ? params.make.toLowerCase() : ''; const model = params.model ? params.model.toLowerCase() : ''; const zip = params.zip || '90210'; let url = `https://www.autotrader.com/cars-for-sale/all-cars`; if (make) url += `/${make}`; if (model) url += `/${model}`; url += `/beverly-hills-ca-${zip}`; // Add query params const urlParams = new URLSearchParams(); if (params.yearMin) urlParams.append('startYear', params.yearMin); if (params.yearMax) urlParams.append('endYear', params.yearMax); if (params.priceMax) urlParams.append('maxPrice', params.priceMax); if (params.mileageMax) urlParams.append('maxMileage', params.mileageMax); if (urlParams.toString()) { url += '?' + urlParams.toString(); } await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }); await new Promise(r => setTimeout(r, 5000)); // Extract listings const rawListings = await page.evaluate(() => { const results = []; // Autotrader uses various selectors for listings const cards = document.querySelectorAll('[data-cmp="inventoryListing"], .inventory-listing'); cards.forEach(card => { const titleEl = card.querySelector('h2, .text-bold'); const priceEl = card.querySelector('[data-cmp="firstPrice"], .first-price'); const mileageEl = card.querySelector('.text-subdued-lighter'); const dealerEl = card.querySelector('.dealer-name, .text-subdued'); const linkEl = card.querySelector('a[href*="/cars-for-sale/"]'); const title = titleEl ? titleEl.innerText.trim() : null; const price = priceEl ? priceEl.innerText.trim() : null; // Get mileage from text let mileage = null; if (mileageEl) { const text = mileageEl.innerText; const match = text.match(/([\d,]+)\s*miles?/i); if (match) mileage = match[0]; } if (title) { results.push({ title, price, mileage, dealerName: dealerEl ? dealerEl.innerText.trim() : null, href: linkEl ? linkEl.getAttribute('href') : null }); } }); return results; }); for (const item of rawListings.slice(0, maxResults)) { listings.push(new CarListing({ title: item.title, price: item.price, mileage: item.mileage, dealerName: item.dealerName, url: item.href ? `https://www.autotrader.com${item.href}` : null, source: 'Autotrader' })); } await browser.close(); } catch (err) { if (browser) await browser.close(); throw new Error(`Autotrader scraping failed: ${err.message}`); } return listings; }
- src/scraper.js:299-406 (helper)Scraping helper for Kelley Blue Book (KBB): extracts listings with emphasis on price analysis and ratings.async function scrapeKBB(params, maxResults = 20) { const listings = []; let browser; try { browser = await launchBrowser(); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); // Build URL const make = params.make ? params.make.toLowerCase() : ''; const model = params.model ? params.model.toLowerCase() : ''; const zip = params.zip || '90210'; let url = `https://www.kbb.com/cars-for-sale/all`; if (make) url += `/${make}`; if (model) url += `/${model}`; url += `/?zip=${zip}`; // Add filters if (params.yearMin) url += `&startYear=${params.yearMin}`; if (params.yearMax) url += `&endYear=${params.yearMax}`; if (params.priceMax) url += `&maxPrice=${params.priceMax}`; if (params.mileageMax) url += `&maxMileage=${params.mileageMax}`; await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }); await new Promise(r => setTimeout(r, 5000)); // Extract listings - KBB uses inventoryListing data-cmp const rawListings = await page.evaluate(() => { const results = []; const cards = document.querySelectorAll('[data-cmp="inventoryListing"]'); cards.forEach(card => { const text = card.innerText; if (!text || text.length < 20) return; const lines = text.split('\n').filter(l => l.trim()); let title = null; let trim = null; let price = null; let mileage = null; let dealRating = null; for (const line of lines) { const trimmed = line.trim(); // Title: Year Make Model if (/^(19|20)\d{2}\s+\w+/.test(trimmed) && !title) { title = trimmed; continue; } // Trim (usually follows title, like "XSE" or "LE") if (title && !trim && /^[A-Z]{1,4}$/.test(trimmed)) { trim = trimmed; continue; } // Price: "$XX,XXX" or just "XX,XXX" (KBB sometimes omits $) const priceMatch = trimmed.match(/^\$?([\d,]+)$/); if (priceMatch && !price && parseInt(priceMatch[1].replace(/,/g, '')) > 1000) { price = trimmed.startsWith('$') ? trimmed : `$${trimmed}`; continue; } // Mileage: "XXK mi" or "XX,XXX mi" if (/^\d+K?\s*mi$/i.test(trimmed) && !mileage) { mileage = trimmed; continue; } // Deal rating: "Good Price", "Great Price", "Fair Price" if (/^(good|great|fair|high)\s*(price|deal)/i.test(trimmed) && !dealRating) { dealRating = trimmed; continue; } } if (title) { if (trim) title = `${title} ${trim}`; results.push({ title, price, mileage, dealRating }); } }); return results; }); for (const item of rawListings.slice(0, maxResults)) { listings.push(new CarListing({ title: item.title, price: item.price, mileage: item.mileage, dealRating: item.dealRating, source: 'KBB' })); } await browser.close(); } catch (err) { if (browser) await browser.close(); throw new Error(`KBB scraping failed: ${err.message}`); } return listings; }