comparebasket

comparebasket

MCP server that compares grocery basket prices across Blinkit, Zepto, BigBasket, and Amazon Now in real time, identifying the cheapest platform for the entire basket.

Category
访问服务器

README

Basket

Give it a grocery list. It checks Blinkit, Zepto, BigBasket and Amazon Now with live prices, normalizes pack sizes, and tells you which platform is cheapest for the whole basket — in your terminal, at a URL, or over MCP.

Built on webcmd: each platform is a real compiled webcmd command, not a screen-scraper bolted to the side.

  BASKET   pincode 110048 · 31.9s · 3 platforms · via webcmd

  ╭──────────────────────────────────────────────────────────────────────╮
  │ITEM                        BIGBASKET          ZEPTO           BLINKIT│
  ├──────────────────────────────────────────────────────────────────────┤
  │amul butter 500g              ₹230.00        ₹310.00           ₹310.00│
  │                   500g · 46.00 / 100g  500g · 62.00/100g  500g · 62.00│
  │tata salt 1kg                  ₹21.00         ₹22.00            ₹29.00│
  ├──────────────────────────────────────────────────────────────────────┤
  │delivery                            —              —                 —│
  │handling                            —              —                 —│
  ├──────────────────────────────────────────────────────────────────────┤
  │GOODS TOTAL                   ₹251.00        ₹332.00           ₹339.00│
  │items found                       2/2            2/2               2/2│
  ╰──────────────────────────────────────────────────────────────────────╯

  ╭─ VERDICT ──────────────────────────────────────────────────────────────╮
  │ BigBasket is cheapest — ₹251.00                                        │
  │ You save ₹81.00  vs Zepto at ₹332.00                                   │
  ╰────────────────────────────────────────────────────────────────────────╯

  ⚠ Goods only — delivery and handling are NOT included
  ● Web view  http://localhost:7777

Why baskets, not items. Cheapest-per-item is a lookup anyone can do. Cheapest-basket is arithmetic nobody does by hand, and it regularly flips the answer — a platform wins on two items and loses on the other ten, or wins only because it quietly didn't stock half your list.

Everything in the output is read live. No prices, packs or fees are baked into this repo. If a number isn't on the page, the tool says it doesn't know it rather than filling in a plausible one — see Fees.


Install

npm install -g comparebasket
basket setup

basket setup is a wizard: it checks webcmd, clears the stale browser lock that otherwise blocks the bridge, generates the adapters, and walks you through setting a delivery location on each platform. It exists because every one of those steps cost real debugging time once.

Prerequisite: webcmdnpm install -g @agentrhq/webcmd. Node ≥ 20.

Check anytime:

basket doctor
  ✔ Node 22.15.0
  ✔ webcmd 0.5.3
  ✔ webcmd browser bridge connected
  ✔ adapters installed: blinkit, zepto, bigbasket, amazon
  ✔ data directory writable (C:\Users\HP\.basket)

Use

basket list.txt                  # terminal table
basket list.txt --ui             # rich boxed terminal view
basket list.txt --web            # terminal view + http://localhost:7777
basket "tata salt 1kg"           # one-line basket, no file needed
cat list.txt | basket --ui       # stdin
Command Does
basket <file|"items"> Compare a basket (the default)
basket search "<query>" Price one product everywhere — seconds, not minutes
basket fees Show / set delivery + handling (see Fees)
basket serve Web view only, paste a basket in the browser
basket setup First-run wizard
basket doctor Environment check
Flag Effect
--web Serve the interactive page, print its URL, stay running (Ctrl+C stops)
--ui Rich boxed terminal view
--json Machine-readable output, nothing else
--platforms=a,b,c Default blinkit,zepto,bigbasket; also amazon
--pincode=110048 Display only — the browser profile is the real source
--port=7777 Port for --web / serve
--no-open Don't launch a browser

Basket format

One item per line, optional leading quantity. # comments and blanks ignored.

2x amul taaza toned milk 500ml
amul butter 500g
6 eggs
tata salt 1kg

2x foo and 3 foo are quantities. 500 ml milk is not — a leading number followed by a unit reads as a pack size, and bare numbers above 20 are never quantities.


Fees

Totals are goods-only by default, and the output says so.

Delivery and handling are only revealed at checkout. The search and listing pages this tool reads carry nothing usable — Blinkit's own search state ships chargeableDeliveryCost: 0 and additional_charges_config: null, because no cart exists yet. Reading real fees would mean adding items to a real cart, which v1 deliberately doesn't do.

An earlier version shipped hand-written fee tables marked verified: false. They rendered as authoritative rupee amounts and were fiction. They're gone. Nothing in this repo knows what Blinkit charges for delivery.

If you know your actual fees, supply them once:

basket fees                                                  # what's set
basket fees --set blinkit --delivery 25 --handling 9 --free-above 199
basket fees --clear blinkit

Stored in ~/.basket/fees.json. Any platform with fees set gets them folded into its total and free-delivery threshold applied; any platform without stays goods-only. The header switches from GOODS TOTAL to TOTAL only when every platform in the run has fees configured — a mixed row would compare a fees-inclusive total against a goods-only one and call it a saving.


Calling it from an agent

Every command takes --json and returns one envelope shape, so nothing has to parse prose or ANSI boxes.

basket search "amul butter 500g" --platforms=blinkit,bigbasket --json
{
  "ok": true,
  "query": "amul butter 500g",
  "cheapest": {
    "platform": "bigbasket", "found": true, "confident": true, "price": 230,
    "name": "Amul Butter Pasteurised",
    "pack": "500g", "pricePerUnit": "₹46.00 / 100g",
    "confidence": 0.95, "confidenceReason": null,
    "url": "https://www.bigbasket.com/pd/104864/...", "error": null
  },
  "results": [ ... ],
  "feesKnown": false
}

Three states per platform, and they mean different things:

Row Means
found: true, confident: true A real match. Only these are eligible to be cheapest.
found: true, confident: false Something came back, but it isn't this product — confidenceReason says why. Shown so you can judge it; never counted.
found: false error: null → not stocked. error: "…" → that platform's search failed.

A query with only low-confidence rows returns cheapest: null and exit 3. The tool would rather say "no confident match" than hand you a ₹208 pack of Cadbury Gems as the price of something it doesn't stock.

Failures use the same envelope — {"ok": false, "error": "..."} — and the exit code tells you which kind without reading the message:

Exit Meaning
0 Success
1 Runtime failure (backend down, browser bridge dead)
2 Usage error (bad flag, unknown platform, empty query)
3 Ran fine, found nothing

basket setup refuses to run when stdin isn't a TTY rather than hanging on a prompt inside a pipeline.


The web view

--web (or serve) starts a local server on 127.0.0.1 and prints a URL. The page shows the same comparison as the terminal, plus a basket editor — edit the list, tick platforms, press Compare, and it re-runs without going back to the shell. A terminal run refreshes any open tab within a few seconds.

It binds to loopback only, deliberately: POST /api/compare drives a real browser with whatever basket it's handed, and that should not be reachable from whatever Wi-Fi you're on.

Route Method Does
/ GET The page
/api/result GET Last comparison as JSON (204 if none yet)
/api/meta GET Available platforms, defaults, threshold
/api/compare POST {items, platforms?, pincode?} → runs and returns a comparison
/health GET Liveness + whether a run is in flight
curl -s localhost:7777/api/result | jq .winner
curl -s -X POST localhost:7777/api/compare \
  -H 'content-type: application/json' \
  -d '{"items":"tata salt 1kg","platforms":["blinkit","bigbasket"]}'

Only one comparison runs at a time — a second returns 409 rather than two runs fighting over a single browser session.


Cheapest ≠ best

The most dangerous failure for a price comparator is rewarding the platform that stocked the least — an empty basket is always the cheapest basket. Three guards:

1. Complete baskets win. If any platform stocks everything, only complete baskets compete.

2. Otherwise, compare the common subset — only lines every platform actually priced. The output says so: "cheapest on what everyone stocks — compares 8 of 12 items available everywhere".

3. Coverage sits next to price. If the winner covers less than someone else:

⚠ Zepto only stocked 8/12 of your basket.
→ Best coverage: Blinkit 11/12 at ₹1,679.00 — one trip instead of two.
  Cheapest is only cheapest if you don't mind sourcing 4 items elsewhere.

Repeated no-results for staples (milk, bread, eggs) usually means a scrape failure, not genuine absence. Searches retry once on empty; if a platform still returns zeros across common items, treat its total as suspect, not cheap.


Matching: what it refuses, and why

Candidates score 0–1. Below 0.55 they're flagged and shown, never silently totalled. Four conditions refuse outright:

Refusal Example
Unit family mismatch 500g butter vs 500ml oil — ₹/100g and ₹/100ml share no axis
Brand mismatch Query says Amul, product is Mother Dairy
Product type mismatch Query says butter, name has no "butter"
Different category amul butter vs Amul Butter Cookies

The last is the subtle one, and a real bug this codebase hit: matching on "is the word present" cheerfully bought sixteen packs of butter cookies. Comparing category sets catches it.

Pack normalization

2 x 250ml → 500ml · 0.5 L → 500ml · 1kg → 1000g · 1 dozen → 12

The scorer buys the packs you'd actually need. Ask for 500g, find only 200g packs, and it charges for three — not 2.5, and not pretending a 200g pack competes with a 500g one. The table prints 3× 200g so you can check the maths.


webcmd is the engine

Every search is a real webcmd command:

webcmd blinkit search --query "amul butter 500g" -f json

Adapters live at ~/.webcmd/clis/<site>/search.jsStrategy.UI, browser: true, siteSession: 'persistent' (so the delivery location survives across commands), typed errors, and a shared 7-column envelope:

{ name, packSize, packUnit, price, mrp, inStock, url }

They are generated by scripts/build-adapters.js from src/extract/*.js. Adapters may only import from @agentrhq/webcmd/*, so they can't require this project's source — generating them beats maintaining the same selectors twice.

npm run build:adapters          # all sites
node scripts/build-adapters.js zepto

src/platforms.js picks a backend once per run: webcmd when adapters are installed and the bridge is up, else a direct CDP fallback speaking the same envelope. The run header prints which served it — check it before trusting a demo.

If webcmd doctor won't go green

The failure we hit, and the fix, because the error doesn't name it:

Opening in existing browser session. This usually means that the profile is already in use by another instance of Chromium.

An orphaned cloak Chromium holds a lock on ~/.webcmd/cloak/profiles/default. basket setup clears this for you, or by hand:

Get-CimInstance Win32_Process -Filter "Name='chrome.exe'" |
  Where-Object { $_.CommandLine -like '*cloakbrowser*' } |
  ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
rm -f ~/.webcmd/cloak/profiles/default/Singleton{Lock,Cookie,Socket}
webcmd doctor

A first-run fetch failed is different: cloak is downloading ~150 MB of Chromium into ~/.cloakbrowser/. That one just needs working network and patience.


Platforms

Site Status Strategy
Blinkit ✅ default DOM_STATEwindow.__reduxStore__
Zepto ✅ default UI_SELECTOR/pn/<slug>/pvid/, names from slug
BigBasket ✅ default UI_SELECTOR/pd/<id>/<slug>/
Amazon Now ⚠ opt-in i=nowstore. Thin catalogue — a butter query returns ghee
Instamart ❌ stub Results render intermittently
JioMart, DMart No prices without a location cookie / login wall

Zepto's live domain is zepto.comzeptonow.com doesn't resolve.

Adding one

  1. Recon in the webcmd browser. A hydration blob on window beats CSS selectors.
  2. src/extract/<site>.js exporting site, label, searchUrl, readyExpression, extractExpression, with a strategy note in the header.
  3. Return the shared envelope exactly.
  4. Register in src/extract/index.js, and add its display name to LABELS in src/fees.js. (No fee numbers — that file holds none by design.)
  5. npm run build:adapters && node scripts/probe.js <site> "amul butter 500g" and compare against the live page. A probe that passes on wrong data is worse than one that fails.

MCP

claude mcp add basket -- node "$(npm root -g)/comparebasket/src/mcp-server.js"
Tool Does
compare_basket(items, pincode?, platforms?) Whole basket → markdown table + JSON summary
search_product(query, platforms?) One product priced everywhere — fast
open_basket_view() Starts the web view and returns its URL
open_platform(platform?) Buy link, defaults to the winner

The MCP process hosts the web server itself, so open_basket_view hands back a live URL the user can keep using after the conversation moves on.

compare_basket's JSON block carries totalsInclude: "goods only" | "goods + fees" alongside feesKnown and feesMissing, so a model can't quote a goods-only figure as a final bill without having been told.


Layout

Path Job
src/cli.js Command surface
src/compare.js compareBasket() — the one entry point every surface calls
src/resolver.js Basket text → {query, qty}
src/extract/*.js Per-platform extraction (source of truth for adapters)
src/normalize.js Pack parsing, match confidence, price-per-unit
src/score.js Best pick, packs needed, totals, ranking, coverage
src/fees.js User-supplied fee config — carries no fee numbers itself
src/server.js Local HTTP server + JSON API
src/tui.js Boxed terminal renderer
src/setup.js setup wizard and doctor
src/paths.js Package root vs writable ~/.basket

Runtime state lives in ~/.basket/ (override with BASKET_HOME) — nothing is ever written inside the installed package.


Development

npm test                                        # 47 tests, no network
node scripts/probe.js bigbasket "amul butter"   # one site, one query
node scripts/mcp-smoke.js                       # MCP protocol check
npm run build:adapters                          # regenerate webcmd adapters

Troubleshooting

Symptom Cause
No search backend available webcmd bridge down and no debug Chrome — run basket doctor
results never rendered Delivery location not set for that platform
Every platform returns 0 rows Location lost from the webcmd profile
One platform empty, rest fine That site changed markup — re-run recon
Implausible price A discount badge parsed as price — filter badge lines
409 from /api/compare A comparison is already running
Total has no delivery charge Expected — goods-only until you run basket fees --set

Not implemented

Cart preparation · login/auth · persistence beyond the last result · multi-pincode · coupons · membership pricing · price history · split-basket optimization (cheapest single platform vs cheapest split across two, fees counted twice — the strongest next upgrade).

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选