Skip to main content
Glama

get_component_details

Retrieve technical specifications and compare prices across Brazilian stores for PC components using a product URL.

Instructions

Detalhes completos de um componente: especificações técnicas, preços por loja (PIX e normal), menor preço

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesURL do componente (ex: '/peca/W6UIQZ/processador-amd-ryzen-7-5800x3d' ou URL completa)

Implementation Reference

  • Main handler function getComponentDetails that fetches and parses component details from meupc.net. Extracts name, category, image, technical specifications, prices from multiple stores, and calculates the lowest price. Returns a JSON string with all component details.
    export async function getComponentDetails(params: GetComponentDetailsParams): Promise<string> {
      const { url } = params;
      const $ = await fetchPage(url);
    
      const name = $("h1.title").first().text().trim();
    
      // Extrair categoria do breadcrumb
      const breadcrumbs = $("nav.breadcrumb li a");
      let category: string | null = null;
      breadcrumbs.each((_, el) => {
        const href = $(el).attr("href") ?? "";
        if (href.match(/meupc\.net\/\w/) && !href.includes("/peca/")) {
          category = $(el).text().trim();
        }
      });
    
      // Imagem principal
      const image = $("div.glide ul.glide__slides li.glide__slide figure img").first().attr("src")
        ?? $("figure.image img").first().attr("src")
        ?? null;
    
      // Especificações técnicas (primeira table.table.is-striped)
      const specs: Record<string, string> = {};
      $("table.table.is-striped tr").each((_, row) => {
        const label = $(row).find("th").text().trim();
        if (!label) return;
    
        const td = $(row).find("td");
        // Verificar valores booleanos (ícones sim/não)
        const yesIcon = td.find('span.icon[title="Sim"]');
        const noIcon = td.find('span.icon[title="Não"], span.icon[title="Nao"], span.icon[title="Não/Nenhum"], span.icon[title="Nao/Nenhum"]');
    
        let value: string;
        if (yesIcon.length > 0) {
          value = "Sim";
        } else if (noIcon.length > 0) {
          value = "Não";
        } else {
          value = td.text().trim();
        }
    
        if (label && value) {
          specs[label] = value;
        }
      });
    
      // Preços por loja (table.table.is-responsive)
      const prices: StorePrice[] = [];
      $("table.table.is-responsive tbody tr").each((_, row) => {
        const $row = $(row);
        const storeImg = $row.find("th a.loja-img img");
        const store = storeImg.attr("alt")?.trim() ?? storeImg.attr("title")?.trim() ?? "";
        if (!store) return;
    
        const priceTd = $row.find('td[data-label="Preco"], td.table-responsive-fullwidth.has-text-right');
        const priceText = priceTd.find("a.has-text-weight-bold").first().text().trim();
        const price = parsePrice(priceText);
    
        const pixTd = $row.find('td[data-label="Preco PIX"]');
        const pixText = pixTd.find("a.has-text-weight-bold").first().text().trim();
        const pricePix = parsePrice(pixText);
    
        const buyLink = $row.find("a.button.is-buy").attr("href") ?? null;
    
        // Verificar disponibilidade — se não tem preço, não está disponível
        const available = price !== null || pricePix !== null;
    
        prices.push({
          store,
          price: price ?? 0,
          pricePix,
          url: buyLink ? absoluteUrl(buyLink) : null,
          available,
        });
      });
    
      // Menor preço (PIX se disponível, senão normal)
      const allPrices = prices
        .filter(p => p.available)
        .flatMap(p => [p.pricePix, p.price].filter((v): v is number => v !== null && v > 0));
      const lowestPrice = allPrices.length > 0 ? Math.min(...allPrices) : null;
    
      const result: ComponentDetails = {
        name,
        category,
        specs,
        prices,
        lowestPrice,
        url: absoluteUrl(url),
        image: image ? absoluteUrl(image) : null,
      };
    
      return JSON.stringify(result, null, 2);
    }
  • Zod schema definition for input validation. getComponentDetailsSchema defines a single 'url' parameter accepting either a relative path or full URL to a component page.
    export const getComponentDetailsSchema = z.object({
      url: z.string().describe("URL do componente (ex: '/peca/W6UIQZ/processador-amd-ryzen-7-5800x3d' ou URL completa)"),
    });
    
    export type GetComponentDetailsParams = z.infer<typeof getComponentDetailsSchema>;
  • src/index.ts:34-41 (registration)
    MCP tool registration for 'get_component_details'. Registers the handler with the server using the schema shape and async handler that returns the result as text content.
    server.tool(
      "get_component_details",
      "Detalhes completos de um componente: especificações técnicas, preços por loja (PIX e normal), menor preço",
      getComponentDetailsSchema.shape,
      async (params) => ({
        content: [{ type: "text", text: await getComponentDetails(params) }],
      })
    );
  • Helper utilities used by getComponentDetails: fetchPage (fetches and parses HTML), absoluteUrl (converts relative URLs to absolute), and parsePrice (extracts numeric values from price strings).
    export async function fetchPage(path: string): Promise<CheerioAPI> {
      const url = path.startsWith("http") ? path : `${BASE_URL}${path.startsWith("/") ? "" : "/"}${path}`;
    
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
    
      try {
        const response = await fetch(url, {
          headers: {
            "User-Agent": USER_AGENT,
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
          },
          signal: controller.signal,
        });
    
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText} — ${url}`);
        }
    
        const html = await response.text();
        return cheerio.load(html);
      } finally {
        clearTimeout(timeout);
      }
    }
    
    export function absoluteUrl(path: string | undefined | null): string {
      if (!path) return "";
      if (path.startsWith("http")) return path;
      return `${BASE_URL}${path.startsWith("/") ? "" : "/"}${path}`;
    }
    
    export function parsePrice(text: string | undefined | null): number | null {
      if (!text) return null;
      const cleaned = text.replace(/[R$\s.]/g, "").replace(",", ".");
      const num = parseFloat(cleaned);
      return isNaN(num) ? null : num;
    }
  • Type definitions for ComponentDetails and StorePrice interfaces that define the structure of the data returned by getComponentDetails handler.
    export interface ComponentDetails {
      name: string;
      category: string | null;
      specs: Record<string, string>;
      prices: StorePrice[];
      lowestPrice: number | null;
      url: string;
      image: string | null;
    }
    
    export interface StorePrice {
      store: string;
      price: number;
      pricePix: number | null;
      url: string | null;
      available: boolean;
    }

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/leosebben/mcp-meupc'

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