fix(BUY-65685): sentinel-price guard for mcp-railway JSON-RPC tools - #309
fix(BUY-65685): sentinel-price guard for mcp-railway JSON-RPC tools#309BuyWhere wants to merge 1 commit into
Conversation
Parallel to PR #36 in @buywhere/mcp (BUY-65559). The Railway mcp-server (mcp.buywhere.ai) does NOT consume @buywhere/mcp — it returns JSON {price: {amount, currency}} directly from src/routes/mcp.ts. Without this fix, the dominant AI-agent touchpoint still surfaces the .00 sentinel (price.amount=1) that AI agents render as 'Price: $1.00 SGD'. * Add formatPriceField + isSentinelPrice helpers to src/lib/response.ts. Sentinel threshold: amount < 10 (matches PR #36). Sentinel text: 'see merchant (price unavailable in catalog) — click through to confirm' (matches PR #36). * Apply formatPriceField in buildProduct — when sentinel, return the string instead of the {amount, currency} object. Same change in handleFindBestPrice (which directly constructs the price field). * Type the CanonicalProduct.price field as ProductPriceField (= ProductPrice | string). Existing consumers that read price.amount unchanged for non-sentinel rows; sentinel rows surface a string the AI agent renders as the 'see merchant' hint. * Sentinel rows also suppress normalized_price_usd and replace the comparison_attributes[price].value with the hint string. * Update routes/products.ts where it reads p.price.currency to handle the union (string | {currency}). Path (a) per the issue: replace the price field with a sentinel string on JSON-RPC output. Clients do not need to update — the new string is the AI-agent-rendering directive. Out of scope: handleFindSimilar returns a flat price:number shape and the issue lists 5 tools (search, get_product, compare_products, find_best_price, get_deals). Follow-up BUY-65687 to apply sentinel treatment to find_similar's flat shape (separate handling needed). Verified: tsc --noEmit clean for new/changed files. Pre-existing src/routes/webhooks.ts(8,49) dahlia version mismatch is on main HEAD and unrelated. Refs: BUY-65559, PR #36 (BuyWhere/buywhere-mcp#36), BUY-52807
Heartbeat 2026-08-03T20:02:47ZPR #309 still open, 0 reviews, 0 PR-level comments, awaiting human review. BUY-65685 stays in_review per the PR review gate pattern. No new live signal since last heartbeat. |
Heartbeat 2026-08-04T00:01ZPR #309 still open, 0 reviews since 2026-07-31. Awaiting human review per [[buywhere-pr-review-gate-pattern]]. New finding: the canonical sentinel product (871873063695734596, BUY-52807) now returns Live state: https://buywhere.ai/ HTTP 200 (66ms), https://mcp.buywhere.ai/ HTTP 200, https://api.buywhere.ai/ HTTP 200. Sentinels annotated Status: still |
|
Heartbeat 2026-08-07T20:42Z — sentinel-guard still pre-fix on live mcp.buywhere.ai. Probed with X-API-Key: |
|
Heartbeat 2026-08-07T21:01Z — PR #309 still open, 0 reviews, 0 requested-reviewers. Stacked follow-up PR #310 (BUY-65693) remains gated on this merge. Awaiting human review per [[buywhere-pr-review-gate-pattern]]. Smoke confirmed live mcp.buywhere.ai still returns |
Parallel sentinel-price guard for mcp-railway (BUY-65685)
Parallel to PR #36 in @buywhere/mcp (BUY-65559). The Railway mcp-server at
mcp.buywhere.aidoes NOT consume@buywhere/mcp— it returns JSON{price: {amount, currency}}directly fromsrc/routes/mcp.ts. Without this fix, the dominant AI-agent touchpoint still surfaces the.00sentinel (price.amount=1) that AI agents render as "Price: $1.00 SGD".What this PR does
formatPriceField+isSentinelPricehelpers tosrc/lib/response.tsamount < 10(matches PR feat(BUY-9085): add JWS-signed Agent Card at .well-known/agent.json #36)see merchant (price unavailable in catalog) — click through to confirm(matches PR feat(BUY-9085): add JWS-signed Agent Card at .well-known/agent.json #36)formatPriceFieldinbuildProduct(used bysearch_products,get_product,compare_products,get_deals): when sentinel, returns the string instead of the{amount, currency}objecthandleFindBestPrice(which directly constructs the price field)CanonicalProduct.priceasProductPriceField = ProductPrice | string. Existing consumers that readprice.amountkeep working for non-sentinel rows; sentinel rows surface a string the AI agent renders as the "see merchant" hintnormalized_price_usdand replacecomparison_attributes[price].valuewith the hint stringroutes/products.tswhere it readsp.price.currencyto handle the union (string | {currency})Path (a) per the issue
Replaces the
pricefield with a sentinel string on JSON-RPC output. Clients do not need to update — the new string IS the AI-agent-rendering directive. This is the higher-impact fix flagged by the BUY-65585 heartbeat (the BUY-65559 PR #36 only protects npm@buywhere/mcpconsumers; the buywhere.ai mcp-server is the higher-traffic surface).Out of scope (follow-up)
handleFindSimilarreturns a flatprice: numbershape (not{amount, currency}). The issue lists 5 tools (search,get_product,compare_products,find_best_price,get_deals);find_similaris not in the list. Filed BUY-65687 to apply sentinel treatment tofind_similar's flat shape.Verification
tsc --noEmitclean for the new/changed files. (Pre-existingsrc/routes/webhooks.ts(8,49)dahlia version mismatch is onmainHEAD and unrelated — separateBUY-65474thread.)mcp.buywhere.ai/mcpafter Railway redeploy:get_product id=871873063695734596(Gigabyte A16, sentinel) →pricefield is the sentinel string, not{amount: 1, currency: "USD"}find_best_price product_name="gaming laptop"→ all rows inbest_price/alternativeswith sentinel rows have the sentinel stringsearch_productsreturning the sentinel row →results[].priceis the sentinel stringBUY-65685-LIVE-VERIFY-20260731T*.json(will be posted to the issue thread).Related
.00price for Gigabyte Gaming A16)Risk
pricebecomes a string for sentinel rows. Existing consumers that didprice.amount.toFixed(2)will throw on the sentinel string — but that is the WHOLE POINT (the AI agent can no longer render.00). For non-sentinel rows, the shape is identical to before.find_similarnot updated in this PR (see Out of scope).🤖 Generated with Claude Code