Skip to main content
Glama

sephora_get_product

Retrieve detailed Sephora product information including descriptions, ingredients, usage instructions, available variants, and customer reviews by providing the product URL.

Instructions

Get detailed information about a specific Sephora product including full description, ingredients, how-to-use instructions, available variants (shades/sizes), and customer reviews.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
product_urlYesFull Sephora product URL, e.g. https://www.sephora.com/product/...
include_reviewsNoWhether to include customer reviews (default: true)
max_reviewsNoMaximum number of reviews to return (1-10)

Implementation Reference

  • The main handler function that executes the sephora_get_product tool logic. It navigates to the product URL, waits for the page to load, and extracts comprehensive product information including name, brand, price, rating, description, ingredients, how-to-use instructions, images, variants (shades/sizes), categories, stock status, loyalty points, and optional customer reviews.
    export async function getProduct(input: GetProductInput): Promise<string> {
      const session = getSession();
      const page = await session.navigateTo(input.product_url);
    
      // Wait for product page to load
      try {
        await page.waitForSelector(
          '[data-comp="ProductSummary"], [class*="product-summary"], h1',
          { timeout: 15000 }
        );
      } catch {
        await page.waitForTimeout(3000);
      }
    
      const details = await page.evaluate(
        ({
          url,
          maxReviews,
          includeReviews,
        }: {
          url: string;
          maxReviews: number;
          includeReviews: boolean;
        }): ProductDetails => {
          // Product name
          const nameEl =
            document.querySelector('[data-at="product_name"]') ??
            document.querySelector('[class*="product-header"] h1') ??
            document.querySelector("h1");
    
          // Brand
          const brandEl =
            document.querySelector('[data-at="brand_name"]') ??
            document.querySelector('[class*="brand-name"]') ??
            document.querySelector('[class*="brandName"]');
    
          // Price
          const priceEl =
            document.querySelector('[data-at="price"]') ??
            document.querySelector('[class*="price-value"]') ??
            document.querySelector('[itemprop="price"]');
    
          // Rating
          const ratingEl = document.querySelector(
            '[data-at="ratings_count"], [aria-label*="stars"], [class*="star-rating"]'
          );
          const ratingText = ratingEl?.textContent?.trim() ?? "";
          const ratingMatch = ratingText.match(/(\d+\.?\d*)/);
    
          const reviewCountEl = document.querySelector(
            '[data-at="ratings_count_number"], [class*="review-count"]'
          );
          const reviewText = reviewCountEl?.textContent?.trim() ?? "";
          const reviewMatch = reviewText.match(/(\d[\d,]*)/);
    
          // Description
          const descEl =
            document.querySelector('[data-at="product_description"]') ??
            document.querySelector('[class*="description-content"]') ??
            document.querySelector('[itemprop="description"]');
    
          // Ingredients
          const ingredientsEl =
            document.querySelector('[data-at="ingredients"]') ??
            document.querySelector('[class*="ingredients"]');
    
          // How to use
          const howToUseEl =
            document.querySelector('[data-at="how_to_use"]') ??
            document.querySelector('[class*="how-to-use"]');
    
          // Images
          const images: string[] = [];
          const imgEls = document.querySelectorAll(
            '[data-comp="ProductImages"] img, [class*="product-image"] img'
          );
          imgEls.forEach((img) => {
            const src = (img as HTMLImageElement).src;
            if (src && !images.includes(src)) images.push(src);
          });
    
          // Variants (shades/sizes)
          const variants: ProductVariant[] = [];
          const variantEls = document.querySelectorAll(
            '[data-at="sku_item"], [class*="sku-item"], [class*="shade-selector"] button'
          );
          variantEls.forEach((el) => {
            const variantName =
              el.getAttribute("data-shade-name") ??
              el.getAttribute("aria-label") ??
              el.textContent?.trim() ??
              "";
            const variantId =
              el.getAttribute("data-sku-id") ??
              el.getAttribute("data-id") ??
              String(Math.random());
            const variantPrice =
              el.querySelector('[class*="price"]')?.textContent?.trim() ?? "";
            const hex =
              (el as HTMLElement).style?.backgroundColor ??
              el.getAttribute("data-hex") ??
              undefined;
            const inStock =
              !el.classList.contains("out-of-stock") &&
              !el.hasAttribute("disabled");
    
            variants.push({
              id: variantId,
              name: variantName,
              price: variantPrice,
              shade: el.getAttribute("data-shade-name") ?? undefined,
              hex: hex || undefined,
              inStock,
            });
          });
    
          // Breadcrumb categories
          const categories: string[] = [];
          document
            .querySelectorAll('[data-at="breadcrumb"] a, nav[aria-label="breadcrumb"] a')
            .forEach((el) => {
              const text = el.textContent?.trim();
              if (text) categories.push(text);
            });
    
          // Loyalty points
          const loyaltyEl = document.querySelector(
            '[data-at="loyalty_points"], [class*="loyalty-points"]'
          );
          const loyaltyText = loyaltyEl?.textContent?.trim() ?? "";
          const loyaltyMatch = loyaltyText.match(/(\d+)/);
    
          // Stock status
          const outOfStockEl = document.querySelector(
            '[data-at="out_of_stock"], [class*="out-of-stock"]'
          );
    
          // Product ID from URL
          const idMatch = url.match(/-([A-Z0-9]+)(?:\?|$)/);
          const productId = idMatch?.[1] ?? "unknown";
    
          // Reviews
          const reviews: Review[] = [];
          if (includeReviews) {
            const reviewEls = document.querySelectorAll(
              '[data-comp="Review"], [class*="review-item"], [class*="ReviewItem"]'
            );
            const limit = Math.min(reviewEls.length, maxReviews);
            for (let i = 0; i < limit; i++) {
              const rev = reviewEls[i];
              const authorEl = rev.querySelector(
                '[data-at="reviewer_name"], [class*="author"]'
              );
              const ratingEl = rev.querySelector('[aria-label*="stars"]');
              const titleEl = rev.querySelector(
                '[data-at="review_headline"], [class*="review-title"], h3'
              );
              const textEl = rev.querySelector(
                '[data-at="review_body"], [class*="review-text"], p'
              );
              const dateEl = rev.querySelector(
                '[data-at="review_date"], time, [class*="date"]'
              );
              const helpfulEl = rev.querySelector('[class*="helpful-count"]');
    
              const revRatingText = ratingEl?.getAttribute("aria-label") ?? "";
              const revRatingMatch = revRatingText.match(/(\d+\.?\d*)/);
    
              reviews.push({
                author: authorEl?.textContent?.trim() ?? "Anonymous",
                rating: revRatingMatch ? parseFloat(revRatingMatch[1]) : 0,
                title: titleEl?.textContent?.trim() ?? "",
                text: textEl?.textContent?.trim() ?? "",
                date: dateEl?.textContent?.trim() ?? "",
                helpful: helpfulEl
                  ? parseInt(helpfulEl.textContent?.replace(/\D/g, "") ?? "0", 10)
                  : undefined,
              });
            }
          }
    
          return {
            id: productId,
            name: nameEl?.textContent?.trim() ?? "Unknown Product",
            brand: brandEl?.textContent?.trim() ?? "Unknown Brand",
            price: priceEl?.textContent?.trim() ?? "Price unavailable",
            rating: ratingMatch ? parseFloat(ratingMatch[1]) : undefined,
            reviewCount: reviewMatch
              ? parseInt(reviewMatch[1].replace(/,/g, ""), 10)
              : undefined,
            description: descEl?.textContent?.trim() ?? "",
            ingredients: ingredientsEl?.textContent?.trim(),
            howToUse: howToUseEl?.textContent?.trim(),
            variants: variants.slice(0, 20),
            images: images.slice(0, 5),
            categories,
            isAvailable: !outOfStockEl,
            loyaltyPoints: loyaltyMatch ? parseInt(loyaltyMatch[1], 10) : undefined,
            reviews: includeReviews ? reviews : undefined,
            url,
          };
        },
        {
          url: input.product_url,
          maxReviews: input.max_reviews ?? 5,
          includeReviews: input.include_reviews ?? true,
        }
      );
    
      return JSON.stringify({ success: true, product: details });
    }
  • Input validation schema using Zod that defines the parameters for the sephora_get_product tool: product_url (required URL), include_reviews (optional boolean, default true), and max_reviews (optional number 1-10, default 5).
    export const getProductSchema = z.object({
      product_url: z
        .string()
        .url()
        .describe("Full Sephora product URL, e.g. https://www.sephora.com/product/..."),
      include_reviews: z
        .boolean()
        .optional()
        .default(true)
        .describe("Whether to include customer reviews (default: true)"),
      max_reviews: z
        .number()
        .int()
        .min(1)
        .max(10)
        .optional()
        .default(5)
        .describe("Maximum number of reviews to return (1-10)"),
    });
  • src/index.ts:109-113 (registration)
    Tool registration in the TOOLS array that defines the MCP tool with name 'sephora_get_product', description, and input schema converted from Zod to JSON schema format.
      name: "sephora_get_product",
      description:
        "Get detailed information about a specific Sephora product including full description, ingredients, how-to-use instructions, available variants (shades/sizes), and customer reviews.",
      inputSchema: zodToJsonSchema(getProductSchema) as Tool["inputSchema"],
    },
  • Tool dispatcher handler that validates input arguments using getProductSchema.parse() and calls the getProduct function with the validated input.
    sephora_get_product: async (args) => {
      const input = getProductSchema.parse(args);
      return getProduct(input);
    },
Behavior2/5

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

With no annotations provided, the description carries full burden but only covers what data is returned, not behavioral aspects like rate limits, authentication needs, error conditions, or pagination for reviews. It mentions reviews can be included/limited but doesn't describe review format or sorting.

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 sentence efficiently lists all key information elements without waste. Front-loaded with core purpose, followed by specific data components. Every word earns its place.

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

Completeness3/5

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

For a read-only tool with 3 parameters (100% schema coverage) but no annotations or output schema, the description adequately covers what data is returned but lacks behavioral context (rate limits, errors) and output format details. Minimum viable but with clear gaps in transparency.

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?

Schema description coverage is 100%, so parameters are fully documented in the schema. The description adds no additional parameter semantics beyond implying product_url identifies 'a specific Sephora product' and that reviews are part of the output. Baseline 3 is appropriate when schema does the heavy lifting.

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?

The description clearly states the verb 'Get' and resource 'detailed information about a specific Sephora product', listing specific data elements (description, ingredients, instructions, variants, reviews). It distinguishes from siblings like sephora_search_products (search vs. specific product) and sephora_add_to_basket (retrieval vs. action).

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 description implies usage for retrieving detailed product data, but doesn't explicitly state when to use this vs. alternatives like sephora_search_products (for browsing) or sephora_get_rewards (for loyalty info). No explicit exclusions or prerequisites are mentioned.

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-sephora'

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