search_cars
Search for vehicles on Turbo.az using filters like make, model, price range, year, fuel type, and transmission to find matching car listings.
Instructions
Car search on Turbo.az. Search by make, model, price range, year, etc.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| make | No | Car make (e.g. BMW, Mercedes, Toyota) | |
| model | No | Car model (e.g. X5, E-Class, Camry) | |
| price_min | No | Minimum price (AZN) | |
| price_max | No | Maximum price (AZN) | |
| year_min | No | Minimum year of manufacture | |
| year_max | No | Maximum year of manufacture | |
| fuel_type | No | Fuel type: petrol, diesel, gas, electric, hybrid | |
| transmission | No | Transmission: automatic, manual | |
| limit | No | Result count limit (default: 20) |
Implementation Reference
- src/scraper.py:180-320 (handler)The handler function search_cars which performs the web scraping of car listings.
async def search_cars( self, make: Optional[str] = None, model: Optional[str] = None, price_min: Optional[int] = None, price_max: Optional[int] = None, year_min: Optional[int] = None, year_max: Optional[int] = None, fuel_type: Optional[str] = None, transmission: Optional[str] = None, limit: int = 20 ) -> dict: """Searches for cars (gets ID from make/model name and builds full URL).""" fuel_mapping = { "benzin": 1, "dizel": 2, "qaz": 3, "elektrik": 6, "hibrid": 7, "plug-in hibrid": 8, } transmission_mapping = { "mexaniki": 1, "avtomat": 2, "robot": 3, "variator": 4, } fuel_id = fuel_mapping.get((fuel_type or "").lower()) if fuel_type else None transmission_id = transmission_mapping.get((transmission or "").lower()) if transmission else None def _scrape(): driver = self._get_driver() results = [] make_id = None model_id = None try: if make: driver.get(f"{BASE_URL}/autos") WebDriverWait(driver, 20).until( EC.presence_of_element_located((By.CSS_SELECTOR, '.tz-dropdown[data-id="q_make"]')) ) time.sleep(0.3) try: driver.find_element(By.CSS_SELECTOR, '.tz-dropdown[data-id="q_make"] .tz-dropdown__selected').click() time.sleep(0.2) except Exception: pass make_opts = self._parse_tz_dropdown_options(driver, "q_make") make_lower = make.strip().lower() for val, label in make_opts: txt = label.lower() if txt == make_lower or txt.startswith(make_lower + " ") or txt.startswith(make_lower + "(") or make_lower in txt: make_id = val break if not make_id: all_makes = [label for _, label in make_opts[:20]] logger.warning("Make not found. Sample options: %s", all_makes) return {"success": False, "error": f"Make not found: {make}"} if model: # Open make dropdown and click make option so model list is populated try: make_cont = driver.find_element(By.CSS_SELECTOR, '.tz-dropdown[data-id="q_make"]') make_cont.find_element(By.CSS_SELECTOR, ".tz-dropdown__selected").click() time.sleep(0.3) opts_el = make_cont.find_elements(By.CSS_SELECTOR, ".tz-dropdown__list .tz-dropdown__option") for el in opts_el: if (el.get_attribute("data-val") or "").strip() == make_id: el.click() break time.sleep(0.5) except Exception: pass # Open model dropdown so options are in DOM try: driver.find_element(By.CSS_SELECTOR, '.tz-dropdown[data-id="q_model"] .tz-dropdown__selected').click() time.sleep(0.2) except Exception: pass model_opts = self._parse_tz_dropdown_options(driver, "q_model") model_lower = model.strip().lower() for val, label in model_opts: txt = label.lower() if txt == model_lower or txt.startswith(model_lower + " ") or txt.startswith(model_lower + "("): model_id = val break url = self._build_search_url( make_id=make_id, model_id=model_id, price_min=price_min, price_max=price_max, year_min=year_min, year_max=year_max, fuel_id=fuel_id, transmission_id=transmission_id, ) logger.info(f"Searching: {url}") driver.get(url) WebDriverWait(driver, 20).until( EC.presence_of_element_located((By.CLASS_NAME, "products-i")) ) # Find listings items = driver.find_elements(By.CLASS_NAME, "products-i")[:limit] for item in items: try: car = {} # Title and link link_elem = item.find_element(By.CLASS_NAME, "products-i__link") car["url"] = link_elem.get_attribute("href") car["id"] = car["url"].split("/")[-1].split("-")[0] # Image try: img = item.find_element(By.CSS_SELECTOR, ".products-i__top img") car["image"] = img.get_attribute("src") except NoSuchElementException: car["image"] = None # Title (make + model) try: title = item.find_element(By.CLASS_NAME, "products-i__name") car["title"] = title.text.strip() except NoSuchElementException: car["title"] = "N/A" # Price try: price = item.find_element(By.CLASS_NAME, "products-i__price") car["price"] = price.text.strip() except NoSuchElementException: car["price"] = "N/A" # Additional info (year, engine, mileage) try: attrs = item.find_elements(By.CLASS_NAME, "products-i__attributes") if attrs: attr_text = attrs[0].text.strip() parts = [p.strip() for p in attr_text.split(",")] if len(parts) >= 1: car["year"] = parts[0] if len(parts) >= 2: car["engine"] = parts[1] if len(parts) >= 3: car["mileage"] = parts[2] except NoSuchElementException: pass - src/server.py:87-115 (registration)Registration of the search_cars tool in the MCP server using the list_tools decorator.
Tool( name="search_cars", description="Car search on Turbo.az. Search by make, model, price range, year, etc.", inputSchema={ "type": "object", "properties": { "make": { "type": "string", "description": "Car make (e.g. BMW, Mercedes, Toyota)" }, "model": { "type": "string", "description": "Car model (e.g. X5, E-Class, Camry)" }, "price_min": { "type": "integer", "description": "Minimum price (AZN)" }, "price_max": { "type": "integer", "description": "Maximum price (AZN)" }, "year_min": { "type": "integer", "description": "Minimum year of manufacture" }, "year_max": { "type": "integer", "description": "Maximum year of manufacture"