MCP Server
Last updated: October 2, 2026
BetterReviews is an MCP server (Model Context Protocol, Streamable HTTP transport, protocol version 2025-06-18). Instead of writing curl calls, an AI client gets a small set of tools over your store: search the review corpus, read a review, pull the best reviews for a product, record and manage marketing claims, link supporting reviews, ask a reviewer for permission to feature their review, and run the whole product-page optimization loop — regenerate, edit, fact-check, approve and publish.
The server is stateless and plain JSON: every POST carries one JSON-RPC 2.0 message and gets one JSON response; a notification gets 202 with no body. There are no sessions, no server-sent events and no batching.
Two ways to connect
| Door | URL | Auth | Tools |
|---|---|---|---|
| Header | https://api.betterreviews.app/api/v1/mcp | X-API-Key header with a store API key | All sixteen (ten need Read & Write) |
| Connector URL | https://api.betterreviews.app/api/v1/mcp/c/<token> | The URL itself (a 90-day connector token) | Read-only six |
Claude Code
claude mcp add --transport http betterreviews https://api.betterreviews.app/api/v1/mcp \
--header "X-API-Key: YOUR_API_KEY" Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"betterreviews": {
"type": "http",
"url": "https://api.betterreviews.app/api/v1/mcp",
"headers": { "X-API-Key": "YOUR_API_KEY" }
}
}
} claude.ai custom connector
claude.ai connectors cannot send headers, so you use a connector URL instead. Mint one with a Read & Write API key (201):
curl -X POST https://api.betterreviews.app/api/v1/mcp/connector-tokens \
-H "X-API-Key: YOUR_API_KEY" {
"id": "…",
"name": "mcp-connector",
"prefix": "ppo_AbCd",
"expires_at": "2026-12-12T10:00:00Z",
"url": "https://api.betterreviews.app/api/v1/mcp/c/ppo_…"
} Paste url as the connector URL in claude.ai. It is shown once. Then:
- Treat the URL as a password. Anyone who has it can read your reviews and claims until it expires or you revoke it.
- It is read-only: only
search_reviews,get_review,top_reviews,list_claims,permission_previewandget_optimizationare available. Mutating tools are not listed and returnread_only_tokenif called. - It expires after 90 days; at most two are active per store (
422 too_many_connector_tokens). - It only works at its own URL. Presented as a header, Bearer token or query parameter anywhere on the REST API it is refused with
401. - Revoke it from Settings → Developer in the BetterReviews app (it appears as
mcp-connector) or withDELETE /api/v1/mcp/connector-tokens/:id. Revocation is immediate.
Managing connector tokens requires a Read & Write API key: POST mints, GET /api/v1/mcp/connector-tokens lists the active ones as {"data": [...]} (id, prefix, last used, expiry — never the URL), DELETE /:id revokes.
Tools
| Tool | Auth | Arguments | Returns |
|---|---|---|---|
search_reviews | either | query (required, ≤500 chars), product_id, min_rating 1–5, limit 1–50 | reviews[], total, has_more. Natural-language search over the corpus. Requires an active paid subscription — the Free plan gets subscription_required. |
get_review | either | review_id | Rating, title, content, display name, status, stage, rights_status, tags, media count. Never the reviewer's email. |
top_reviews | either | product_id (required, ≤64 chars), objective (marketing | helpful, default marketing), limit 1–20 (default 3), min_rating 1–5, exclude_ids (≤50 review IDs) | {reviews[], tier, coverage, objective}. The best reviews for one product, ranked in SQL — no model call, no AI cost. Each row carries id, product_id, rating, title, content, reviewer_display_name, is_verified_buyer, rights_status, word_count, quality_score, review_created_at and media_count — the same key names as get_review, not the REST endpoint's created_at/media[] shape. There is no require_media argument (REST only), and limit defaults to 3 here versus 5 on REST. tier is A (some reviews AI-analyzed) or C (none yet, analysis-free ranking); coverage is {total, analyzed}. Product-scoped: Review Group siblings are never pooled in. Capped at 60 calls/minute per store on top of the shared limit, in a bucket shared with GET /reviews/top. |
list_claims | either | product_id, status, collect_enabled, include_archived, limit, offset | Same shape as GET /claims. |
permission_preview | either | review_id | {reachable, masked_email} — the full address never leaves the server. |
upsert_claim | header + write | Create: product_id, claim_text, topic, source (+ optional ask_phrasings, status, collect_enabled). Update: claim_id + fields. | {claim}. Same validation as the REST API (invalid_prompt_text, product_not_found, another_claim_active). |
set_claim_collection | header + write | claim_id, enabled | {claim}. One collect-enabled claim per product. |
link_reviews_to_claim | header + write | claim_id, reviews[] of {review_id, evidence_quote?, confidence?} (≤50) | {claim_id, reviews[], links_total} — the links written by this call plus the claim's total link count. |
request_review_permission | header + write | review_id, scope (text | photo | both, default both) | {permission_request_id, status}. Consent rules apply: unreachable, opted-out or suppressed reviewers are refused. |
get_optimization | either | product_id | The latest product-page optimization: the five widgets, status, version and the fact-check report. Same body as GET /products/:id/optimization. |
regenerate_optimization | header + write | product_id, blocks[] (≤5), feedback (≤1000 chars), featured_review_id | The new draft's summary with accepted: true — generation runs in the background. Costs credits past the free regenerations per block; needs an active subscription. featured_review_id requires top_review to be among the blocks (invalid_featured_review_id otherwise); a review that is not eligible leaves the widget empty with a reason in the report rather than failing the call. |
edit_optimization_widget | header + write | product_id, widget_key, content | The updated draft. Edited text is re-fact-checked before it can be published; a byte-identical resubmit changes nothing. |
recheck_optimization | header + write | product_id | The draft with a fresh report over the widgets that had not passed. Widgets that already passed are untouched. |
approve_optimization | header + write | product_id | The draft, now approved. Publishing is a separate call. |
publish_optimization | header + write | product_id | The published optimization. Spends credits and writes the live Shopify metafields; refused with unverified_content while any widget has not passed the fact check. |
flag_optimization_widget | header + write | product_id, widget, reason, note | {flagged, refunded} plus the regeneration counters. Always recorded; refunds only when the fact-check report agrees. |
The ten header + write tools also require the key's Read & Write scope — a Read-only key sees them in tools/list but calling one returns insufficient_scope.
Claim, permission and optimization tools are entitlement-gated (claim_collection, rights_management, product_pages) exactly like the equivalent REST operations; without the entitlement the tool returns entitlement_required. Better and Studio include them. On legacy plans (no longer sold), they are available on Growth and above, and on legacy Pro and Enterprise; stores on the legacy Starter plan do not have them. The Free plan does not include them, and neither does a cancelled store (cancelling returns the store to Free).
Errors
Two layers, matching the MCP specification:
- JSON-RPC errors for protocol problems:
-32601unknown method,-32602unknown tool or invalid arguments (wrong type, over-long string, out-of-range number, too many items — checked before any tool runs),-32600for batch arrays or malformed requests,-32700for an empty body. - Tool results with
isError: truefor domain outcomes. The text content andstructuredContentcarry{"error": "snake_case_code"}using the same codes as the REST API:review_not_found,claim_not_found,claim_archived,product_not_found,another_claim_active,invalid_prompt_text,validation_error,missing_fields(with afieldsarray —upsert_claimcreate needsproduct_id,claim_text,topicandsource),invalid_search,invalid_product_id(top_reviews— not numeric after stripping ashopify-prefix or a product GID),entitlement_required,subscription_required,read_only_token,insufficient_scope(mutating tool with a Read-only key),immutable_fields(product_id/sourcecannot change on update),rate_limited,search_unavailable, and the permission-request outcomes (no_reachable_email,consent_blocked,suppressed,already_requested). The optimization tools call the same implementation as their REST endpoints, so they answer the same codes:optimization_not_found,optimization_in_progress,optimization_changed,insufficient_credits,credit_limit_reached,unverified_content,invalid_blocks,invalid_featured_review_id,invalid_widget_key,invalid_content,legacy_draft_regenerateandunpublished_optimization_limit.
HTTP-level: 401 bad/expired/revoked key or token, 403 credential without a store, 413 body over 256 KB, 429 rate limited (120 requests/minute per store on each door; top_reviews adds a 60/minute per-store bucket shared with GET /reviews/top). Some tools also draw on per-store hourly buckets shared with the equivalent REST endpoints, so one quota covers both doors: optimization_generate 30/h (regenerate_optimization), optimization_verify 60/h (recheck_optimization and a non-empty edit_optimization_widget), optimization_feedback 20/h (flag_optimization_widget) and optimization_publish 30/h (publish_optimization); request_review_permission is capped at 10/minute and 200/day per store. Exceeding any of these returns rate_limited as a tool result. GET returns 405; DELETE returns 204.
Workflow: does our corpus support this claim?
With the header door connected in Claude Code, a typical exchange is:
search_reviews— "keeps drinks hot for hours", filtered to the product.get_reviewon the promising hits to read the full text.upsert_claim— record the claim with a neutraltopicand exampleask_phrasings.link_reviews_to_claim— attach the supporting reviews with the quoted sentence and a confidence.- Not enough evidence?
set_claim_collectionso the AI review chat can ask happy customers about the topic. Enough?permission_preview+request_review_permissionbefore quoting a review in ads.
See the Claims API for field semantics and the API keys guide for creating and revoking keys.
Book a demo