|
| 1 | +--- |
| 2 | +title: "How I built a price-comparison MCP server (and how you can call it from Claude or Cursor)" |
| 3 | +slug: "how-i-built-a-price-comparison-mcp-server" |
| 4 | +subtitle: "An MCP server that finds the cheapest price across Amazon, Best Buy, Shopee, Lazada, Apple Store, and 4 more retailers — across 9 countries — in under 2 seconds. Here's the architecture and how to wire it into Claude or Cursor in 5 minutes." |
| 5 | +tags: "mcp, modelcontextprotocol, claude, cursor, llm, devtools, api, comparison, ecommerce, indie-hacker" |
| 6 | +domain: "buywhere.hashnode.dev" |
| 7 | +canonical: "https://buywhere.ai/blog/how-i-built-a-price-comparison-mcp-server" |
| 8 | +enableToc: true |
| 9 | +--- |
| 10 | + |
| 11 | +# I was checking 5 sites to buy one thing. So I built an MCP server. |
| 12 | + |
| 13 | +Every time I want to buy a laptop, headphones, or a vacuum, I do the same dance: open Amazon, switch to Shopee, then Best Buy, then Lazada, then Apple Store, then spend 20 minutes copy-pasting prices into a spreadsheet. |
| 14 | + |
| 15 | +Last quarter, I missed a SGD 350 price drop on a Zenbook 14 OLED because Amazon.sg undercut ASUS Store SG by exactly that much, and I didn't know. So I did what any reasonable engineer would do at 1am: I started building an MCP server. |
| 16 | + |
| 17 | +Three weeks later, **BuyWhere** is live at [buywhere.ai](https://buywhere.ai?utm_source=hashnode&utm_medium=social&utm_campaign=june30_25k&utm_content=hashnode_mcp_build_2026w24). It is a price-comparison MCP server that searches **9 retailers across 9 countries** in parallel and returns the cheapest real-time price, all callable from inside Claude or Cursor. |
| 18 | + |
| 19 | +This post covers: the architecture, the parts that surprised me, the code, and a 5-minute wiring guide so you can call it from Claude or Cursor by the end of this read. |
| 20 | + |
| 21 | +## What BuyWhere actually does |
| 22 | + |
| 23 | +Given a product query (free text or structured) and a country code, BuyWhere: |
| 24 | + |
| 25 | +1. Fans out the query to the configured retailers for that country (US: Amazon, Best Buy, Walmart, eBay, Apple, B&H. SG: Amazon.sg, Shopee SG, Lazada SG, Apple Store SG, Challenger, Best Denki, Harvey Norman. … 7 more country configs.) |
| 26 | +2. Normalizes each retailer's product page into `{title, price, currency, url, retailer, in_stock}` records |
| 27 | +3. Sorts by price ascending, returns the top N |
| 28 | +4. Caches per-(query, country, retailer) tuples for 5 minutes to stay polite |
| 29 | +5. Returns JSON that's directly consumable by an LLM |
| 30 | + |
| 31 | +The MCP surface is small on purpose — three tools and one resource: |
| 32 | + |
| 33 | +```jsonc |
| 34 | +{ |
| 35 | + "tools": [ |
| 36 | + { "name": "search_prices", "description": "Search products and return ranked prices across retailers in a country" }, |
| 37 | + { "name": "compare_product", "description": "Resolve a product to a canonical SKU and return its price across all configured retailers" }, |
| 38 | + { "name": "list_cheapest", "description": "Top N cheapest products in a category for a country" } |
| 39 | + ], |
| 40 | + "resources": [ |
| 41 | + { "uri": "buywhere://merchant/{country}", "name": "Merchant list with status" } |
| 42 | + ] |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +That's it. No scraping pipeline in the LLM. The MCP server does the dirty work; the LLM just asks "what's the cheapest iPhone 17 in Singapore right now?" and gets an answer. |
| 47 | + |
| 48 | +## The architecture (with the parts that surprised me) |
| 49 | + |
| 50 | +Three things surprised me during the build: |
| 51 | + |
| 52 | +**1. Retailer normalization is 80% of the work.** Every adapter deals with different units (USD vs SGD vs JPY), different tax semantics (US prices are pre-tax; SG prices include GST), different "in stock" semantics, and wildly different rate limits. I ended up with a single `Money` type and a per-retailer currency converter pinned to a 1-hour exchange-rate snapshot. |
| 53 | + |
| 54 | +**2. The MCP spec is genuinely small.** Once you internalize `tools` + `resources` + JSON-RPC, the surface area is small enough to fit on a sticky note. I went from "first 200-line scaffold" to "shipping in 3 weeks" mostly because the protocol doesn't get in your way. |
| 55 | + |
| 56 | +**3. Caching is the difference between a demo and a product.** Without caching, the same query from 4 different LLM users in 5 minutes would have hit 36 retailer endpoints. The 5-minute cache per (query, country, retailer) tuple brought the steady-state hit rate to ~12% and kept me under Shopee's and Lazada's rate limits. |
| 57 | + |
| 58 | +## The code: 5 minutes to wire it into Claude or Cursor |
| 59 | + |
| 60 | +This is the part you came for. Three steps. |
| 61 | + |
| 62 | +### Step 1 — Grab an API key |
| 63 | + |
| 64 | +Sign up at [buywhere.ai/api-keys](https://buywhere.ai/api-keys?utm_source=hashnode&utm_medium=social&utm_campaign=june30_25k&utm_content=hashnode_mcp_build_2026w24) (free tier is 1,000 calls/month, no card required). You'll get `bw_live_…` formatted keys. |
| 65 | + |
| 66 | +### Step 2 — Add BuyWhere to your Claude Desktop config |
| 67 | + |
| 68 | +Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows): |
| 69 | + |
| 70 | +```json |
| 71 | +{ |
| 72 | + "mcpServers": { |
| 73 | + "buywhere": { |
| 74 | + "command": "npx", |
| 75 | + "args": ["-y", "@buywhere/mcp-server"], |
| 76 | + "env": { |
| 77 | + "BUYWHERE_API_KEY": "bw_live_replace_me" |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +Restart Claude Desktop. Done. You should see a small 🔌 icon in the input box, and the tools `search_prices`, `compare_product`, and `list_cheapest` will appear. |
| 85 | + |
| 86 | +### Step 3 — Try it |
| 87 | + |
| 88 | +Open a new Claude conversation and ask: |
| 89 | + |
| 90 | +> "What is the cheapest iPhone 17 in Singapore right now?" |
| 91 | +
|
| 92 | +Claude will call `search_prices` and return something like: |
| 93 | + |
| 94 | +```json |
| 95 | +{ |
| 96 | + "results": [ |
| 97 | + { "retailer": "Shopee SG", "price": 1219, "currency": "SGD", "url": "https://shopee.sg/…" }, |
| 98 | + { "retailer": "Amazon.sg", "price": 1249, "currency": "SGD", "url": "https://www.amazon.sg/…" }, |
| 99 | + { "retailer": "Lazada SG", "price": 1259, "currency": "SGD", "url": "https://www.lazada.sg/…" }, |
| 100 | + { "retailer": "Challenger", "price": 1299, "currency": "SGD", "url": "https://www.challenger.sg/…" }, |
| 101 | + { "retailer": "Apple Store SG", "price": 1299, "currency": "SGD", "url": "https://www.apple.com/sg/" } |
| 102 | + ], |
| 103 | + "cheapest": "Shopee SG", |
| 104 | + "savingsVsMSRP": 80 |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +For Cursor, the wiring is identical — `Cursor → Settings → MCP → Add new global MCP server` and paste the same JSON. |
| 109 | + |
| 110 | +## What I'd do differently |
| 111 | + |
| 112 | +- **Skip the eBay adapter** unless you have a real use case — the affiliate API is more paperwork than it's worth at low volume. |
| 113 | +- **Add a `compare_products` (plural) tool** that batches 5-10 SKUs at once for "I have a shortlist, find the cheapest right now" workflows. |
| 114 | +- **Set up an A/B for the cache TTL** — 5 minutes is conservative for "in stock" but stale for "price". I'd split it next time. |
| 115 | + |
| 116 | +## Try it |
| 117 | + |
| 118 | +- Sign up: [buywhere.ai/api-keys](https://buywhere.ai/api-keys?utm_source=hashnode&utm_medium=social&utm_campaign=june30_25k&utm_content=hashnode_mcp_build_2026w24) |
| 119 | +- Docs + schema: [buywhere.ai/quickstart](https://buywhere.ai/quickstart?utm_source=hashnode&utm_medium=social&utm_campaign=june30_25k&utm_content=hashnode_mcp_build_2026w24) |
| 120 | +- Source repo: [github.com/BuyWhere/buywhere-mcp](https://github.com/BuyWhere/buywhere-mcp) |
| 121 | +- npm: `npm i -g @buywhere/mcp-server` |
| 122 | + |
| 123 | +— Lyra, BuyWhere team |
0 commit comments