Search Amazon.in
search_amazon_inSearch Amazon India for products by keyword and get ranked results with cheapest in-stock and best-value picks.
Instructions
Search amazon.in for products by keyword and return ranked listings.
This tool scrapes the public amazon.in search page (no API key needed). It returns a normalised list of results plus two convenience picks:
cheapest_in_stock: lowest price among listings showing stock
best_value: weighted score = rating × log10(reviews+10) / sqrt(price), requires >=10 reviews
Args:
query (string, 2-200 chars): search keywords
max_results (int, 1-20, default 5): number of listings to return
include_sponsored (bool, default false): include ad listings
Returns: JSON with schema: { "query": string, "total_results": number, "results": [ { "asin": string, "title": string, "url": string, "image": string, "price_inr": number, "price_display": string, "mrp_inr": number, "rating": number, "review_count": number, "prime": boolean, "sponsored": boolean, "in_stock": boolean, "delivery": string, "price_history_url": string } ], "cheapest_in_stock": , "best_value": }
Error handling:
"Amazon served a bot-check page" → wait 30-60s and retry
"Failed to reach amazon.in" → transient network or throttling
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Keyword search query (e.g., 'bluetooth speaker under 2000') | |
| max_results | No | Maximum listings to return (1-20). Default 5. | |
| include_sponsored | No | Include sponsored / ad listings. Defaults to false. |
Implementation Reference
- src/index.ts:148-230 (registration)Tool registration of 'search_amazon_in' via server.registerTool() with input schema, description, and the async handler function.
server.registerTool( "search_amazon_in", { title: "Search Amazon.in", description: `Search amazon.in for products by keyword and return ranked listings. This tool scrapes the public amazon.in search page (no API key needed). It returns a normalised list of results plus two convenience picks: - cheapest_in_stock: lowest price among listings showing stock - best_value: weighted score = rating × log10(reviews+10) / sqrt(price), requires >=10 reviews Args: - query (string, 2-200 chars): search keywords - max_results (int, 1-20, default 5): number of listings to return - include_sponsored (bool, default false): include ad listings Returns: JSON with schema: { "query": string, "total_results": number, "results": [ { "asin": string, "title": string, "url": string, "image": string, "price_inr": number, "price_display": string, "mrp_inr": number, "rating": number, "review_count": number, "prime": boolean, "sponsored": boolean, "in_stock": boolean, "delivery": string, "price_history_url": string } ], "cheapest_in_stock": <SearchResultItem>, "best_value": <SearchResultItem> } Error handling: - "Amazon served a bot-check page" → wait 30-60s and retry - "Failed to reach amazon.in" → transient network or throttling`, inputSchema: SearchInputSchema.shape, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, }, async (params) => { try { const q = encodeURIComponent(params.query); const url = `${AMAZON_BASE}/s?k=${q}`; const html = await fetchHtml(url); let items = parseSearch(html); if (!params.include_sponsored) { items = items.filter((it) => !it.sponsored); } const limited = items.slice(0, params.max_results); const ranks = rankResults(limited); const output: RankedResults = { query: params.query, total_results: limited.length, results: limited, ...ranks, }; const text = JSON.stringify(output, null, 2); return { content: [{ type: "text", text: truncate(text, output) }], structuredContent: output as unknown as Record<string, unknown>, }; } catch (err) { return { content: [{ type: "text", text: friendlyError(err) }] }; } } ); - src/index.ts:200-230 (handler)The async handler function for search_amazon_in: encodes query, fetches HTML, parses search results, filters sponsored items, limits results, ranks by cheapest/best-value, and returns JSON.
async (params) => { try { const q = encodeURIComponent(params.query); const url = `${AMAZON_BASE}/s?k=${q}`; const html = await fetchHtml(url); let items = parseSearch(html); if (!params.include_sponsored) { items = items.filter((it) => !it.sponsored); } const limited = items.slice(0, params.max_results); const ranks = rankResults(limited); const output: RankedResults = { query: params.query, total_results: limited.length, results: limited, ...ranks, }; const text = JSON.stringify(output, null, 2); return { content: [{ type: "text", text: truncate(text, output) }], structuredContent: output as unknown as Record<string, unknown>, }; } catch (err) { return { content: [{ type: "text", text: friendlyError(err) }] }; } } ); - src/index.ts:105-124 (schema)Zod schema SearchInputSchema defines input validation for query (2-200 chars), max_results (1-20, default 5), and include_sponsored (boolean, default false).
const SearchInputSchema = z .object({ query: z .string() .min(2, "Query must be at least 2 characters") .max(200, "Query must not exceed 200 characters") .describe("Keyword search query (e.g., 'bluetooth speaker under 2000')"), max_results: z .number() .int() .min(1) .max(20) .default(5) .describe("Maximum listings to return (1-20). Default 5."), include_sponsored: z .boolean() .default(false) .describe("Include sponsored / ad listings. Defaults to false."), }) .strict(); - src/index.ts:43-77 (helper)rankResults() helper: finds cheapest_in_stock and best_value items from search results using a scoring algorithm (rating * log10(reviews+smoothing) / sqrt(price)).
function rankResults(items: SearchResultItem[]): { cheapest_in_stock?: SearchResultItem; best_value?: SearchResultItem; } { const inStock = items.filter( (it) => it.in_stock && typeof it.price_inr === "number" ); if (!inStock.length) return {}; const cheapest_in_stock = [...inStock].sort( (a, b) => (a.price_inr ?? Infinity) - (b.price_inr ?? Infinity) )[0]; // Best value: rating × log10(reviews + smoothing) / sqrt(price). // Requires at least MIN_REVIEWS_FOR_BEST_VALUE reviews to avoid noisy picks. const scored = inStock .filter( (it) => typeof it.rating === "number" && typeof it.review_count === "number" && (it.review_count ?? 0) >= MIN_REVIEWS_FOR_BEST_VALUE ) .map((it) => ({ it, score: (it.rating! * Math.log10(it.review_count! + LOG_REVIEWS_SMOOTHING)) / Math.sqrt(it.price_inr!), })) .sort((a, b) => b.score - a.score); const result: ReturnType<typeof rankResults> = {}; if (cheapest_in_stock) result.cheapest_in_stock = cheapest_in_stock; if (scored.length) result.best_value = scored[0]!.it; return result; } - src/parse.ts:193-207 (helper)parseSearch() helper: parses Amazon.in search result HTML using cheerio, extracting ASIN, title, price, rating, delivery info for each result card.
export function parseSearch(html: string): SearchResultItem[] { const $: CheerioAPI = cheerio.load(html); const items: SearchResultItem[] = []; $('div[data-component-type="s-search-result"]').each((_, el) => { const card = $(el); const asin = (card.attr("data-asin") || "").trim(); if (!asin || !ASIN_REGEX.test(asin)) return; const item = buildSearchItem(card, asin); if (item) items.push(item); }); return items; }