amazon_search
Search Amazon products by keyword to get an Opportunity Score (0–100) based on demand, rating gap, and price. Identify product opportunities.
Instructions
Search Amazon products by keyword. Returns an Opportunity Score (0–100) for each result based on demand, rating gap, and price.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes | Search keyword, e.g. 'yoga mat' | |
| marketplace | No | Marketplace: US, UK, DE, CA, AU (default: US) |
Implementation Reference
- src/tools/amazon.ts:118-195 (handler)The main handler function 'searchAmazon' that executes the amazon_search tool logic. It scrapes Amazon search results using cheerio, parses product data, calculates opportunity scores, and returns an AmazonSearchResult.
export async function searchAmazon( keyword: string, marketplace: string = "US" ): Promise<AmazonSearchResult> { const domain = MARKETPLACE_DOMAINS[marketplace.toUpperCase()] ?? MARKETPLACE_DOMAINS.US; const url = `https://${domain}/s?k=${encodeURIComponent(keyword)}`; const res = await fetch(url, { headers: HEADERS }); if (res.status === 503 || res.status === 429) { throw new Error("Amazon rate limited — retry in 30s"); } if (!res.ok) { throw new Error(`Amazon search failed: HTTP ${res.status}`); } const html = await res.text(); // Detect CAPTCHA / robot check if (html.includes("Type the characters you see") || html.includes("robot check")) { throw new Error("Amazon rate limited — retry in 30s"); } const $ = cheerio.load(html); const products: AmazonProduct[] = []; $('[data-component-type="s-search-result"]').each((_, el) => { const $el = $(el); const asin = $el.attr("data-asin") ?? ""; if (!asin) return; const title = $el.find("h2 a span").first().text().trim(); const priceWhole = $el.find(".a-price-whole").first().text().trim(); const priceFraction = $el.find(".a-price-fraction").first().text().trim(); const priceStr = priceWhole ? `${priceWhole}${priceFraction || "00"}` : null; const price = parsePrice(priceStr); const originalPriceStr = $el.find(".a-text-price .a-offscreen").first().text().trim(); const original_price = parsePrice(originalPriceStr); const ratingStr = $el.find(".a-icon-alt").first().text().trim(); const rating = ratingStr ? parseFloat(ratingStr) : null; const reviewStr = $el.find('[aria-label*="ratings"] span, .a-size-base.puis-normal-weight-text').first().text().trim(); const review_count = parseReviewCount(reviewStr); const seller = $el.find(".a-size-base-plus.a-color-base.s-underline-text").first().text().trim() || "Amazon"; const prime = $el.find(".s-prime").length > 0; const badge = $el.find(".a-badge-text").first().text().trim() || null; const image_url = $el.find(".s-image").first().attr("src") ?? null; const opportunity_score = calculateOpportunityScore({ review_count, rating, price, prime }); products.push({ asin, title, price, original_price, rating, review_count, seller, prime, badge, image_url, url: `https://${domain}/dp/${asin}`, opportunity_score, }); }); return { keyword, marketplace: marketplace.toUpperCase(), total_results: products.length, products, searched_at: new Date().toISOString(), }; } - src/tools/amazon.ts:18-24 (schema)Type definitions for AmazonSearchResult and AmazonProduct interfaces used as the output schema for the amazon_search tool.
export interface AmazonSearchResult { keyword: string; marketplace: string; total_results: number; products: AmazonProduct[]; searched_at: string; } - src/mcp-stdio.ts:71-90 (registration)Tool registration in the MCP ListTools schema: defines the 'amazon_search' tool name, description, and inputSchema (keyword required, marketplace optional with enum).
{ name: "amazon_search", description: "Search Amazon products by keyword. Returns an Opportunity Score (0–100) for each result based on demand, rating gap, and price.", inputSchema: { type: "object", properties: { keyword: { type: "string", description: "Search keyword, e.g. 'yoga mat'", }, marketplace: { type: "string", description: "Marketplace: US, UK, DE, CA, AU (default: US)", enum: ["US", "UK", "DE", "CA", "AU"], }, }, required: ["keyword"], }, }, - src/mcp-stdio.ts:191-197 (registration)Tool call dispatch in the MCP CallTool handler: routes 'amazon_search' requests to the searchAmazon function, extracting keyword and marketplace from arguments.
case "amazon_search": { const { keyword, marketplace } = args as { keyword: string; marketplace?: string; }; result = await searchAmazon(keyword, marketplace); break; - src/tools/amazon.ts:71-103 (helper)Helper function 'calculateOpportunityScore' that computes a 0-100 opportunity score based on review count, rating, price, and Prime availability.
function calculateOpportunityScore(product: { review_count: number | null; rating: number | null; price: number | null; prime: boolean; }): number { let score = 50; // base // Review count (demand) const reviews = product.review_count ?? 0; if (reviews > 10000) score += 20; else if (reviews > 1000) score += 15; else if (reviews > 100) score += 10; else if (reviews > 10) score += 5; else score -= 5; // untested market // Rating (quality gap — lower rating = more room to win) const rating = product.rating ?? 4.0; if (rating < 3.5) score += 15; else if (rating < 4.0) score += 8; else if (rating >= 4.5) score -= 5; // Price sweet spot ($15–$60) const price = product.price ?? 0; if (price >= 15 && price <= 60) score += 10; else if (price > 60) score += 5; else score -= 5; // Prime availability (easier fulfillment) if (product.prime) score += 5; return Math.max(0, Math.min(100, score)); }