feat: launch-ready — watch_price + rate limit + disclosure + CI guard - #22
Merged
Merged
Conversation
Four launch-ready deltas in one push. 1) watch_price MCP tool + price_watches table (migration 0002). Persists a "ping me when X drops to $Y" request keyed to the calling agent's clientHash. Throws when a client has >50 active watches. The notify-cron worker lands in a follow-up; this PR ships the contract and storage so agents can already use it. The killer agent-only feature: browser extensions can't watch prices (they only fire when the user opens a page). 2) RateLimiter (src/lib/rate-limit.ts) — token-bucket, in-memory, per-client. Default: 20 capacity refilled at 0.5/sec. Wired into find_best_deal (2 SerpApi credits per call) and find_products (up to 4 credits per call). A looping or misbehaving agent now gets a clean InvalidRequest error with a retry hint instead of torching our monthly budget. 3) Landing footer: explicit affiliate disclosure + privacy note. We participate in Amazon Associates + Skimlinks; users earn no extra cost; we don't strip creator/community attribution; we don't store queries/identities. Required by Amazon Associates ToS and FTC, and preempts the #1 Twitter complaint. 4) CI regression test (test/mcp/no-google-urls.test.ts): walks the structured response of find_products and find_best_deal, asserts no string field contains a google.com/search or google.com/shopping URL — even when SerpApi's product_link is one. This is the regression that hit a real Claude Desktop user yesterday. Tool count: 9 → 10. Smoke test updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything we need to actually post this on Twitter without getting torched.
1.
watch_priceMCP toolPersists a "ping me when X drops to $Y" request to a new
price_watchestable (migration 0002). The killer agent-only feature — browser extensions can't watch prices because they only fire when the user opens a page. Per-client cap of 50 active watches; soft-deleted not hard. Notify-cron worker lands in a follow-up; this PR ships the contract + storage.2. Token-bucket rate limiter
SerpApi credits are real money. A misbehaving agent looping
find_best_dealcould burn our monthly budget in minutes.RateLimiter(in-memory, per-clientHash, 20 capacity refilled at 0.5/sec) wraps bothfind_best_dealandfind_products. Agents get a cleanInvalidRequestwith a retry-in-N-seconds hint instead of an opaque 5xx.3. Affiliate disclosure + privacy footer
Required by Amazon Associates ToS and FTC. Preempts the #1 Twitter pile-on ("where's the disclosure?"). Says: we participate in Amazon Associates + Skimlinks; we never strip creator/community attribution; we don't store queries or identities.
4. CI regression test: no google.com URLs leak
A real Claude Desktop user hit a
google.com/searchURL yesterday.test/mcp/no-google-urls.test.tswalks the structured response of bothfind_productsandfind_best_deal, asserts no string field contains a Google search/shopping URL even when SerpApi'sproduct_linkis one. Locks the fix in.Test plan
🤖 Generated with Claude Code