Skip to main content
Glama

get_car_details

Retrieve detailed Turo car listing information including specs, features, reviews, cancellation policy, and host details to evaluate rental options.

Instructions

Retrieve detailed information about a specific Turo car listing, including specs, features, reviews, cancellation policy, and host info.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
listing_idYesThe Turo listing ID (numeric string). Found in the listing URL: turo.com/us/en/car-rental/.../vehicles/{listing_id}

Implementation Reference

  • The implementation of the get_car_details tool, which uses a browser to navigate to a Turo listing URL, extracts vehicle details via page.evaluate, and returns the scraped data.
    export async function getCarDetails(listingId: string): Promise<CarDetails> {
      const page = await newPage();
    
      try {
        const url = `https://turo.com/us/en/car-rental/united-states/vehicles/${listingId}`;
        await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
        await waitForNavigation(page);
        await sleep(2000);
    
        const details = await page.evaluate((id: string) => {
          const getText = (selector: string): string => {
            const el = document.querySelector(selector) as HTMLElement | null;
            return el?.textContent?.trim() || "";
          };
    
          const getAttr = (selector: string, attr: string): string => {
            const el = document.querySelector(selector);
            return el?.getAttribute(attr) || "";
          };
    
          // Title / name
          const titleEl = document.querySelector(
            'h1, [class*="vehicleName"], [class*="vehicle-name"]'
          ) as HTMLElement | null;
          const titleText = titleEl?.textContent?.trim() || "";
          const yearMatch = titleText.match(/(\d{4})/);
          const year = yearMatch ? parseInt(yearMatch[1]) : 0;
          const nameParts = titleText.replace(/\d{4}\s*/, "").trim().split(" ");
          const make = nameParts[0] || "Unknown";
          const model = nameParts.slice(1).join(" ") || "Unknown";
    
          // Price
          const priceEl = document.querySelector(
            '[class*="price"], [class*="dailyRate"]'
          ) as HTMLElement | null;
          const priceText = priceEl?.textContent || "";
          const priceMatch = priceText.match(/\$?([\d,]+)/);
          const daily_rate = priceMatch ? parseFloat(priceMatch[1].replace(",", "")) : 0;
    
          // Rating
          const ratingEl = document.querySelector(
            '[class*="rating"], [aria-label*="rating"]'
          ) as HTMLElement | null;
          const ratingText = ratingEl?.textContent || ratingEl?.getAttribute("aria-label") || "0";
          const ratingMatch = ratingText.match(/([\d.]+)/);
          const rating = ratingMatch ? parseFloat(ratingMatch[1]) : 0;
    
          // Trip count
          const tripEl = document.querySelector('[class*="trip"]') as HTMLElement | null;
          const tripText = tripEl?.textContent || "";
          const tripMatch = tripText.match(/([\d,]+)/);
          const trip_count = tripMatch ? parseInt(tripMatch[1].replace(",", "")) : 0;
    
          // Location
          const locationEl = document.querySelector(
            '[class*="location"], [class*="address"]'
          ) as HTMLElement | null;
          const location = locationEl?.textContent?.trim() || "";
    
          // Description
          const descEl = document.querySelector(
            '[class*="description"], [class*="about"]'
          ) as HTMLElement | null;
          const description = descEl?.textContent?.trim() || "";
    
          // Host
          const hostNameEl = document.querySelector(
            '[class*="hostName"], [class*="host-name"]'
          ) as HTMLElement | null;
          const host_name = hostNameEl?.textContent?.trim() || "Host";
    
          const hostLinkEl = document.querySelector(
            'a[href*="/profile/"]'
          ) as HTMLAnchorElement | null;
          const hostHref = hostLinkEl?.href || "";
          const hostIdMatch = hostHref.match(/\/profile\/(\d+)/);
          const host_id = hostIdMatch ? hostIdMatch[1] : "";
    
          // Vehicle specs
          const specsMap: Record<string, string> = {};
          document.querySelectorAll('[class*="spec"], [class*="detail"]').forEach((el) => {
            const labelEl = el.querySelector('[class*="label"], dt') as HTMLElement | null;
            const valueEl = el.querySelector('[class*="value"], dd') as HTMLElement | null;
            if (labelEl && valueEl) {
              specsMap[labelEl.textContent?.trim().toLowerCase() || ""] =
                valueEl.textContent?.trim() || "";
            }
          });
    
          // Photos
          const photos: string[] = [];
          document.querySelectorAll('img[src*="turo"], img[src*="cdn"]').forEach((img) => {
            const src = (img as HTMLImageElement).src;
            if (src && !photos.includes(src)) photos.push(src);
          });
    
          // Features / badges
          const features: string[] = [];
          document
            .querySelectorAll('[class*="feature"], [class*="badge"], [class*="amenity"]')
            .forEach((el) => {
              const text = (el as HTMLElement).textContent?.trim();
              if (text && !features.includes(text)) features.push(text);
            });
    
          // Guidelines
          const guidelines: string[] = [];
          document
            .querySelectorAll('[class*="guideline"], [class*="rule"], [class*="policy"] li')
            .forEach((el) => {
              const text = (el as HTMLElement).textContent?.trim();
              if (text && !guidelines.includes(text)) guidelines.push(text);
            });
    
          // Reviews
          const reviews: Review[] = [];
          document
            .querySelectorAll('[class*="review"], [class*="Review"]')
            .forEach((reviewEl, idx) => {
              if (idx >= 10) return;
              const authorEl = reviewEl.querySelector(
                '[class*="author"], [class*="name"]'
              ) as HTMLElement | null;
              const ratingEl2 = reviewEl.querySelector(
                '[class*="rating"]'
              ) as HTMLElement | null;
              const dateEl = reviewEl.querySelector(
                '[class*="date"], time'
              ) as HTMLElement | null;
              const commentEl = reviewEl.querySelector(
                '[class*="comment"], [class*="text"], p'
              ) as HTMLElement | null;
    
              const rText = ratingEl2?.getAttribute("aria-label") || ratingEl2?.textContent || "0";
              const rMatch = rText.match(/([\d.]+)/);
    
              reviews.push({
                id: `review-${idx}`,
                author: authorEl?.textContent?.trim() || "Guest",
                rating: rMatch ? parseFloat(rMatch[1]) : 0,
                date:
                  dateEl?.getAttribute("datetime") || dateEl?.textContent?.trim() || "",
                comment: commentEl?.textContent?.trim() || "",
              });
            });
    
          // Cancellation policy
          const cancelEl = document.querySelector(
            '[class*="cancellation"], [class*="cancel"]'
          ) as HTMLElement | null;
          const cancellation_policy = cancelEl?.textContent?.trim() || "See listing for details";
    
          // Minimum age
          const ageEl = document.querySelector('[class*="age"]') as HTMLElement | null;
          const ageText = ageEl?.textContent || "";
          const ageMatch = ageText.match(/(\d+)/);
          const minimum_age = ageMatch ? parseInt(ageMatch[1]) : 21;
    
          const listing_url = window.location.href;
    
          return {
            id,
            make,
            model,
            year,
            daily_rate,
            rating,
            trip_count,
            location,
            listing_url,
            host_name,
            host_id,
            description,
            features,
            guidelines,
            photos,
            reviews,
            cancellation_policy,
            minimum_age,
            vehicle_type: specsMap["type"] || specsMap["category"] || "Car",
            engine: specsMap["engine"] || undefined,
            transmission: specsMap["transmission"] || undefined,
            fuel_type: specsMap["fuel type"] || specsMap["fuel"] || undefined,
            mpg: specsMap["mpg"] ? parseFloat(specsMap["mpg"]) : undefined,
            odometer: specsMap["odometer"]
              ? parseInt(specsMap["odometer"].replace(/,/g, ""))
              : undefined,
          };
        }, listingId);
    
        return details as CarDetails;
      } finally {
        await page.close();
      }
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description carries full burden. Compensates by disclosing return data categories (specs, reviews, cancellation policy, host info), providing context for what the tool delivers. However, omits operational traits: does not state this is read-only/safe, mention rate limits, auth requirements, or caching behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single 13-word sentence with zero waste. Front-loaded with action verb, efficiently enumerates return data categories without filler. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description effectively compensates by enumerating the rich data returned (specs, features, reviews, etc.). For a single-parameter retrieval tool, this is functionally complete despite lacking annotations, though could benefit from explicit read-only declaration.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema has 100% description coverage for listing_id (including URL location hint), doing the heavy lifting. Description adds minimal parameter-specific semantics beyond implying the 'specific' listing constraint, warranting baseline score for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Uses specific verb 'Retrieve' with clear resource 'Turo car listing' and distinguishes from siblings: contrasts with search_cars (find vs. get details) and get_host_profile (car vs. host focus). Lists specific data categories (specs, features, reviews, cancellation policy, host info) to define scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The word 'specific' implies this requires an identifier, suggesting usage comes after discovery, but lacks explicit workflow guidance like 'Use after search_cars to get details' or 'Do not use for host-only info.' Relies on agent to infer from parameter schema.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/markswendsen-code/mcp-turo'

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