{"name":"harvv-mcp","version":"1.3.0","description":"Harvv behavioral analytics for AI assistants: friction findings, page behavior, traffic, conversion paths, speed and SEO for the sites on your account, plus pixel install and verification. Every tool is scoped to the Harvv account behind your bearer token. Most only read; the few that save a setting return a preview unless confirmed and are marked destructive.","homepage":"https://harvv.com/docs/mcp","scope":"unauthenticated (call with Bearer for scope-filtered tools)","tools":[{"name":"list_my_sites","description":"Lists every site on the Harvv account this connection is scoped to, with id, name, domain and pixel_key for each. Use for questions like 'which sites do I have', or to find the site_id the other tools take.","args":{}},{"name":"install_pixel","description":"Returns the Harvv pixel install snippet for a site on this account, with where to place it on each platform (Shopify, WordPress, Webflow, Squarespace, Wix, Google Tag Manager) and code framework. install_instructions are steps a coding agent can apply to the codebase; human_prompt is the same install written for a person pasting it by hand. Use for requests like 'add Harvv to my site' or 'how do I install the pixel'. check_install tests whether the pixel is firing on a public URL afterwards.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."}}},{"name":"check_install","description":"Checks whether the Harvv pixel is firing on a public URL by fetching the page. Takes the live URL (a custom domain or a published .lovable.app address). A Lovable preview URL (id-preview--*.lovable.app) serves Lovable's editor page rather than the project HTML, so it returns installed=false with an explanation. Use for questions like 'is Harvv installed' or 'is the pixel working'. It makes one HTTP GET of the public page given and reads its HTML for the Harvv pixel (https://harvv.com/docs/mcp#tools); `url` is a web page, not an API endpoint.","args":{"url":{"type":"string","required":true,"description":"Public URL of the published site, http or https only. Localhost, RFC1918 ranges, and cloud-metadata IPs are rejected. Lovable preview URLs (id-preview--*.lovable.app) will return installed=false with a tailored message because they serve Lovable chrome, not the project HTML."}}},{"name":"get_friction_findings","description":"Alias of get_issues, kept for older clients; get_issues returns the same findings plus fix text and a status filter. Returns up to the top 10 behavioral friction patterns (dead clicks, rage clicks, slow pages, broken forms) on a site over the last N days, ranked by sessions affected. days defaults to 7, max 30; an out-of-range value is clamped and the response says clamped=true with the window actually used.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, minimum 1, maximum 30. Values outside this range are clamped; the response will include clamped=true."}}},{"name":"get_dashboard_url","description":"Returns a link that opens a site in the Harvv dashboard, where the full behavioral data, session lists and charts are. Use when the person wants to look at the data in Harvv itself.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."}}},{"name":"get_traffic_summary","description":"Traffic overview for a site over the last N days (default 7, max 30): sessions, unique visitors, top pages, referrer and source split, bounce-equivalent, the device split (`devices`: mobile, desktop, tablet) and the browser split (`browsers`: safari, chrome, edge, firefox, other). Device and browser are recorded on every session, and `device`, `browser` and `segment` narrow every number; a Safari-only or iPhone-only failure can read as mild sitewide friction until split out. Use for questions like 'how much of my traffic is mobile' or 'which browsers do my visitors use'. Every figure here is a session count, never money: campaign, source, medium and click-ID fields say where a visit came from, not what it cost or earned. Ad cost, clicks and platform conversions are in get_ad_spend, which has data only when a Meta Ads or Google Ads account is connected (Settings > Integrations); `spend_data` says whether one is, and null means Harvv holds no spend for this site. Campaign names can repeat across platforms, so platform and campaign name together identify a campaign.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped).","minimum":1,"maximum":30,"default":7},"device":{"type":"string","required":false,"description":"(optional) Only sessions on this device type.","enum":["mobile","desktop","tablet"]},"browser":{"type":"string","required":false,"description":"(optional) Only sessions in this browser. The response's `browsers` split shows which browsers the site actually gets.","enum":["chrome","safari","firefox","edge","other"]},"segment":{"type":"string","required":false,"description":"(optional) Only sessions carrying this visitor segment (e.g. logged_in, anonymous, first_visit, has_cart). Unknown segments are refused with the observed list."}}},{"name":"get_audience_state","description":"Who a site's visitors are, as categories, over the last N days (default 7, max 30): signed-in vs anonymous vs unknown, whether logged-in CMS staff are in the numbers, whether visitors arrived with a cart, and first-time vs returning. Use for questions like 'are these real customers or my own team' or 'how many visitors are logged in'. Each bucket states how it was determined, and 'unknown' is reported separately from anonymous. Categorical counts only: Harvv holds no user ids, names, emails or roles.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped)."}}},{"name":"verify_finding","description":"Re-checks one finding against recent data (default the last 3 days, max 14) to answer 'is this still happening now?'. Stored findings describe the site when they were measured; this re-runs the finding's own measurement and compares. Returns still_happening, residual (a trickle, not a live problem), no_longer_observed, cannot_tell (too little recent traffic to judge) or not_reverifiable. Broken-file findings also get the HTTP status breakdown, the countries affected and when the failure was last seen. A finding is not reported as fixed from an absence of events alone, because no traffic and no defect look the same.","args":{"issue_id":{"type":"string","required":false,"description":"The issue_id from get_issues. Required in practice."},"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Recent window to re-check. Default 3, max 14. Short on purpose: the question is whether it is happening NOW."}}},{"name":"get_performance","description":"Core Web Vitals for a site from real visitor sessions over the last N days (default 7, max 30): LCP, INP, CLS and TTFB at p50 and p75, plus the slowest pages by LCP. Use for questions like 'is my site slow' or 'did speed change'. `comparison` holds the same-length window immediately before, measured directly, and `comparison.poor_share_changes` carries a two-proportion significance test on each poor share; a verdict of `noise` means the change is within sampling variation. Percentile shifts are shown for both windows without a test (there is no clean significance test for a percentile here), so a small p75 move on its own is not evidence of a regression. Aggregated percentiles only.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped).","minimum":1,"maximum":30,"default":7},"device":{"type":"string","required":false,"description":"(optional) Only sessions on this device type.","enum":["mobile","desktop","tablet"]},"segment":{"type":"string","required":false,"description":"(optional) Only sessions carrying this visitor segment. Unknown segments are refused with the observed list."}}},{"name":"get_page_friction","description":"Per-page friction for a site over the last N days (default 7, max 30): dead clicks, rage clicks and affected sessions per page, plus the most-stuck elements. Without `path` it lists the top elements on the worst page; with `path` (e.g. /cart) it lists them for that page in target_page_top_elements. Use for questions about one page, like 'why do people leave /cart' or 'should we add a back button to the cart'. Friction counts can only point at on-page elements. The result includes `differential_diagnosis`: causes outside the website that this data cannot see (for a store: inventory, price, promotions, fulfillment; for a lead or booking business: phone calls, referrals, walk-ins, listings, insurance and staffing; for any business: ad spend and seasonality), any of which can outrank an on-site element as the cause of a drop. Aggregated counts only. Reads the Harvv analytics API (https://harvv.com/docs/mcp#tools); `path` is a page of the site, not an API endpoint.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped)."},"path":{"type":"string","required":false,"description":"(optional) A page path (e.g. /merchants, or / for the homepage) or full URL. When set, returns the top dead/rage-clicked elements on that page in target_page_top_elements, so you can name which elements are stuck on a specific page (not just the worst page). Matched exactly by default; see path_match."},"path_match":{"type":"string","required":false,"enum":["exact","prefix","contains"],"description":"(optional) How `path` is matched. 'exact' (default): that one page only, so '/' is the homepage. 'prefix': the page and every page under it, e.g. path '/products' with 'prefix' covers all product pages. 'contains': any page whose path contains the text. The response's path_scope says how many pages were actually matched."},"device":{"type":"string","required":false,"description":"(optional) Narrow to one device class: 'mobile', 'desktop' or 'tablet'. Omit for all devices. Friction is usually device-specific -- a control that is fine on desktop and unreachable on mobile reads as mild sitewide friction until you split it."},"segment":{"type":"string","required":false,"description":"(optional) Narrow to one session segment, e.g. 'logged_in', 'anonymous', 'has_cart', or a merchant-declared value namespaced by its window.harvv.user key like 'tags:wholesale' or 'plan:pro'. A value never observed on the site is refused with the observed list, not silently ignored. The response carries segment_coverage: segments are derived from user-state events kept ~7 days, so sessions before the derivation first ran can never be segmented, and a filtered read over such a window says the data was not recorded rather than returning counts that read as 'this segment had no friction'."}}},{"name":"get_layout_changes","description":"Estimates when a site's layout or theme last changed, and which open findings that change may have invalidated, from the pixel alone (no CMS access or plugin needed). The set of element selectors a site's events carry works as a structural fingerprint, and a large redesign replaces most of it in one week. Returns week-by-week churn, the site's own normal range, and the weeks far above it (within_normal_range false). These are inferences from visitor interactions, not a record of site edits. Only complete weeks are recorded, so the current week is never included; `coverage` says where the record ends. findings_possibly_invalidated lists open findings whose selector is missing from the newest recorded week (null when the record cannot judge; findings first seen after that week are not checked; seo:*, ai:* and speed:* findings are layout-independent and excluded). recent_changes lists changes saved with annotate_change. Use for questions like 'when did the site change'.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days, like every other tool. Default 56 (8 weeks), max 182. Scanned in whole weeks.","minimum":7,"maximum":182,"default":56},"weeks":{"type":"number","required":false,"description":"(deprecated alias of days; whole weeks)"}}},{"name":"get_site_stack","description":"The site's technology stack as Harvv last observed it: platform, theme, and the active plugin list with the role of each. Use for questions like 'what is this site built on', 'which page builder do they use' or 'which plugins add page weight'. One page builder plus its paid extension and add-on libraries is reported as one builder; add-ons cost page weight rather than causing conflicts. Every plugin listed was observed active on this site. snapshot_age_hours gives the snapshot's age, and stale_warning appears when it is over 7 days old. Needs the Harvv WordPress plugin; without it the result explains what is missing.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"category":{"type":"string","required":false,"description":"(optional) Filter to one category, e.g. 'page_builder', 'seo', 'security', 'caching'."}}},{"name":"get_issues","description":"The UX issues Harvv has detected on a site, each with what is broken, sessions affected, status, and a plain-English recommended fix. Use for questions like 'what should I fix' or 'what is wrong with my site'. Order: findings first seen in the last 14 days that touch conversion (affects_conversion) come first, then the rest by sessions affected. Each finding carries age_days and age_label; an old finding describes the site as it was and is unlikely to explain a recent drop. `status` selects open (default), all, resolved or dismissed. The answer is compact by default (every ranked row with its counts and codes, plus a `detail` block naming what was left out); detail:'full' adds the evidence prose and is several times larger. Long lists page with `next_offset`. Aggregated; no raw events.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"status":{"type":"string","required":false,"description":"(optional) 'open' (default) = the active work pipeline; 'all' = everything not dismissed; 'resolved' = only resolved and fixed issues; 'dismissed' = what was closed without a fix (false positive, won't fix, noise), which no other mode returns. Any other value returns invalid_argument_value with this list."},"limit":{"type":"integer","required":false,"description":"(optional) Max issues to return. Default 15, max 50. The answer also stops at the size an AI assistant accepts from one call; when it does, or when more findings match, `next_offset` says where the rest start.","minimum":1,"maximum":50,"default":15},"offset":{"type":"integer","required":false,"description":"(optional) Skip this many findings in the ranked list, to page. Pass the `next_offset` from the previous answer. Default 0.","minimum":0,"default":0},"include_headline":{"type":"boolean","required":false,"description":"(optional) Also return a `headline` block: sessions, dead clicks and rage clicks over `days` (default 7) for the whole site. This is what get_friction_findings used to add.","default":false},"days":{"type":"integer","required":false,"description":"(optional) Window for the headline block only. Default 7, max 30. Findings are never filtered by it.","minimum":1,"maximum":30,"default":7},"detail":{"type":"string","required":false,"enum":["compact","full"],"default":"compact","description":"(optional) 'compact' (default): every ranked row with the fields an answer needs, plus a `detail` block saying what was left out; small and quick to read. 'full': adds the evidence prose and per-row detail; several times larger and slower to return. The compact answer already contains every finding, count and code."}}},{"name":"get_issue_sessions","description":"Lists the visitor sessions affected by one issue (the issue_id from get_issues), plus a dashboard link showing all of them. Use for questions like 'which sessions?' or 'show me the visitors affected'. When there are more sessions than fit in one response it returns a bounded sample and states the total and where to see the rest. Session ids only, never raw event payloads.","args":{"issue_id":{"type":"string","required":false,"description":"The issue_id from get_issues (or the id in a finding's dashboard_url). Required in practice: without it there is no finding to list sessions for."},"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"limit":{"type":"integer","required":false,"description":"(optional) Max session ids to return. Default 50, max 200."}}},{"name":"get_traffic_sources","description":"Where a site's traffic comes from over the last N days (default 7, max 30), classified into channels (Organic search, Paid search, Paid social, Social, Email, Referral, Direct) with sessions and share for each. Use for questions like 'is my SEO working' (the Organic search share) or 'how much of my traffic is paid'. A traffic drop can come from spend changes, budget caps or ad delivery as easily as from the site. The result includes `differential_diagnosis`: causes outside the website that this data cannot see (for a store: inventory, price, promotions, fulfillment; for a lead or booking business: phone calls, referrals, walk-ins, listings, insurance and staffing; for any business: ad spend and seasonality), any of which can outrank an on-site element as the cause of a drop. Every figure here is a session count, never money: campaign, source, medium and click-ID fields say where a visit came from, not what it cost or earned. Ad cost, clicks and platform conversions are in get_ad_spend, which has data only when a Meta Ads or Google Ads account is connected (Settings > Integrations); `spend_data` says whether one is, and null means Harvv holds no spend for this site. Campaign names can repeat across platforms, so platform and campaign name together identify a campaign.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped)."}}},{"name":"get_ai_recommendations","description":"Which pages AI assistants (ChatGPT, Perplexity, Gemini, Claude, Copilot) sent visitors to, and whether those pages carry structured data (JSON-LD) a model can quote. Works on every platform, Shopify included, because it reads the assistant's outbound link arriving on a page running the pixel, not a crawler log. Returns the assistant breakdown, a device split, the pages each assistant linked, and which of those have no JSON-LD. Counts are a floor: Google AI Overviews arrive without attribution, and on mobile only ChatGPT tags its links. Use for questions like 'is ChatGPT sending me traffic'.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 90, max 365. Needs ~90 days to clear the reporting floor on most sites."}}},{"name":"get_ai_bot_activity","description":"AI crawler activity on a site over the last N days (default 30, max 90), including robots.txt violations (requests for paths robots.txt disallows). Crawlers do not run JavaScript, so the pixel cannot see them; this data comes from the Harvv WordPress plugin's server-side detection, and the result explains what is missing when the plugin is not sending data. Returns totals, violation rate, a breakdown by AI operator (OpenAI, Anthropic, Perplexity and others), the top violating paths, and a `daily` array with one dated row per day. Use for questions like 'which AI bots crawl my site'.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 30, max 90 (clamped)."}}},{"name":"get_seo_insights","description":"Per-page SEO and GEO (AI-search readiness) findings for a site, built from what the pixel captured on the real rendered page of each URL visitors loaded, over the last N days (default 14, max 30). Flags noindex pages, missing titles or descriptions, missing or broken structured data, thin content, heading, canonical and Open Graph issues, and slow Core Web Vitals, graded A to F and prioritized by traffic. Use for questions about on-page or technical SEO. Keyword rankings are in get_search_console and get_keyword_opportunities, which need the site's Search Console connection (Settings > Integrations in Harvv). Harvv does not track backlinks. Compact by default with a `detail` block naming what was left out; detail:'full' adds the evidence prose.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 14, max 30 (clamped)."},"detail":{"type":"string","required":false,"enum":["compact","full"],"default":"compact","description":"(optional) 'compact' (default): every ranked row with the fields an answer needs, plus a `detail` block saying what was left out; small and quick to read. 'full': adds the evidence prose and per-row detail; several times larger and slower to return. The compact answer already contains every finding, count and code."}}},{"name":"get_keyword_opportunities","description":"Ranked SEO opportunities for a site: every search query Google Search Console reports the site appearing for, joined to Google Ads search volume, CPC and competition. Shows measured position, how much of the available demand the site captures (impression_share) and what those clicks would cost to buy (traffic_value_usd), with striking-distance queries (positions 4 to 20) first. Use for questions like 'which keywords should we target next' or 'what should we blog about'. Needs Search Console connected. Every number is measured; there is no modeled traffic projection. A null search_volume means Google Ads has no data for that query, not that it has zero searches.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"limit":{"type":"integer","required":false,"description":"(optional) How many opportunity rows to return. Default 25, max 100."},"min_volume":{"type":"integer","required":false,"description":"(optional) Ignore queries below this monthly search volume. Default 0 (keep everything)."}}},{"name":"get_keyword_volume","description":"Monthly Google search volume, CPC, competition and 12-month trend for keywords you name, for a country, state or city. Use for keywords the site has no Search Console history for (a new topic, a blog title, a product line, a campaign); keywords a site already ranks for are covered, at lower cost, by get_keyword_opportunities. `location` takes a place by name, and the answer's `location` block states which place the numbers are for. A null search_volume means Google Ads has no data for that keyword, not that it has zero searches; keywords with no data come back in `missing` with a reason rather than as zero.","args":{"keywords":{"type":"array","required":true,"items":{"type":"string"},"description":"Keywords to look up. Up to 150 per call (what one answer holds). Case and extra whitespace are normalised, duplicates collapse to one."},"location":{"type":"string","required":false,"description":"(optional) The market to research (not the user's own location), by name: \"Phoenix, Arizona\", \"Anthem, AZ\", \"Arizona\", \"Toronto, Canada\". Resolved to Google's own location (city, neighbourhood, county, state or country); an ambiguous name (\"Springfield\") returns the candidates to choose from instead of a guess, and a name Google does not have returns the nearest places it does. Resolved from the name, so no location code is needed."},"location_code":{"type":"integer","required":false,"description":"(optional) A Google location code, when one is already known. Default 2840 (United States). `location` resolves a place name to its code."},"language_code":{"type":"string","required":false,"description":"(optional) Language code. Default \"en\"."}}},{"name":"get_conversion_path","description":"Finds the interaction that gates conversion on a page and how many visitors never reach it, by grouping the page's sessions by what they clicked before converting and comparing each group with an engagement-matched control. Use for questions like 'why do people who reach this page not buy, book or get in touch'. It measures successful interactions, so it surfaces what visitors needed and never found. Groups are observational: matched_lift_x (among equally deep sessions) is the comparable figure, raw_lift_x is not. page_conversion_pct is null with insufficient_sample below 30 sessions or 3 conversions. A gate missed for an off-site reason (a sold-out size, a full calendar) looks the same as one that is hard to find. The result includes `differential_diagnosis`: causes outside the website that this data cannot see (for a store: inventory, price, promotions, fulfillment; for a lead or booking business: phone calls, referrals, walk-ins, listings, insurance and staffing; for any business: ad spend and seasonality), any of which can outrank an on-site element as the cause of a drop. Harvv sees on-site behaviour only: phone calls, offline bookings, patients, clients and revenue outside the website live in call tracking (such as CallRail), the booking or practice system, referral records, or the store admin and its marketplace or POS channels. Reads the Harvv analytics API (https://harvv.com/docs/mcp#tools); `path` is a page of the site, not an API endpoint.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"path":{"type":"string","required":false,"description":"(optional) Page path to analyse, e.g. /products/some-item, or / for the homepage. Matched exactly by default; path /products with path_match 'prefix' covers every product page. Omit to analyse the whole site. Same name as on every other page tool."},"page":{"type":"string","required":false,"description":"(deprecated alias of path; accepted for one release)"},"path_match":{"type":"string","required":false,"enum":["exact","prefix","contains"],"description":"(optional) How `path` is matched. 'exact' (default): that one page only, so '/' is the homepage. 'prefix': the page and every page under it, e.g. path '/products' with 'prefix' covers all product pages. 'contains': any page whose path contains the text. The response's path_scope says how many pages were actually matched."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped). Keep it small on high-traffic stores.","minimum":1,"maximum":30,"default":7},"conversion":{"type":"string","required":false,"description":"(optional) What counts as a conversion. Default 'auto' = a click on a buy/submit control (add to cart, buy now, checkout, place order). Or pass a substring to match a specific element, e.g. 'Add to Cart'."},"detail":{"type":"string","required":false,"enum":["compact","full"],"default":"compact","description":"(optional) 'compact' (default): every ranked row with the fields an answer needs, plus a `detail` block saying what was left out; small and quick to read. 'full': adds the evidence prose and per-row detail; several times larger and slower to return. The compact answer already contains every finding, count and code."}}},{"name":"get_commerce_funnel","description":"The site's commerce funnel (browsed collection, viewed product, added to cart, viewed cart, began checkout, purchased) plus form starts and submits, over the last N days (default 7, max 30), with session and event counts and cart value where tagged, built from the site's own GA4/dataLayer events joined to Harvv sessions. Use for questions like 'where do shoppers stop'. `instrumented: false` means the site sends no commerce events for Harvv to mirror: a measurement gap, not zero add-to-carts. `instrumented: null` with `measurement_failed: true` means the query failed, so the answer is unknown (not missing tagging). `by_device` shows how far each device got; `device` narrows every step. `daily` has one row per day. Cart numbers alone cannot tell cart friction from an off-site cause: a store whose top product oversold shows the same cart drop. The result includes `differential_diagnosis`: causes outside the website that this data cannot see (for a store: inventory, price, promotions, fulfillment; for a lead or booking business: phone calls, referrals, walk-ins, listings, insurance and staffing; for any business: ad spend and seasonality), any of which can outrank an on-site element as the cause of a drop.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped)."},"device":{"type":"string","required":false,"description":"(optional) Narrow every step to one device class. Omit for all devices; `by_device` compares them either way.","enum":["mobile","desktop","tablet"]}}},{"name":"get_conversions","description":"Conversions, and revenue where the site sells online, for questions like 'how are sales today' or 'are leads coming in'. Three sources, framed by `business_context.shape`: (1) `orders`: orders Harvv recorded itself (Shopify Web Pixel, WooCommerce plugin or conversion API) with count, revenue, average order value and a daily series; no GA4 needed. (2) `pixel_leads`: visits that reached a thank-you page or form confirmation. (3) GA4 conversion events, per event with this period, the prior one and last_seen; `stopped_events[].others_continued` says whether the others kept firing. GA4 `state` not_connected or connected_no_data is a measurement gap, not zero; no_conversion_events_configured means nothing is starred in GA4 (`unstarred_candidates` lists likely events). Landing-page rates credit the page a visit started on, null below the sample floor. `orders` uses whole days on the store's clock; `orders.today_vs_yesterday` compares today so far with yesterday up to the same time. Website only: draft, POS, phone and marketplace orders are excluded (`orders.coverage.statement`). `device` narrows Harvv's own orders and leads. Harvv sees on-site behaviour only: phone calls, offline bookings, patients, clients and revenue outside the website live in call tracking (such as CallRail), the booking or practice system, referral records, or the store admin and its marketplace or POS channels.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 30. Trends compare this window against the one immediately before it."},"device":{"type":"string","required":false,"description":"(optional) Narrow Harvv's own orders and leads to one device class. GA4 is not split by device, so its fields are left out when this is set.","enum":["mobile","desktop","tablet"]}}},{"name":"get_fleet_benchmark","description":"Compares a site with the Harvv fleet on five behaviour rates over the last 30 days: share of sessions on phones, sessions with a dead click, sessions with a rage click, sessions that never scroll past a quarter of the page, and dead clicks per 1,000 sessions. Use for questions like 'is this normal?' or 'how do we compare?'. The fleet figure is the median of the latest benchmark run across all sites Harvv measures; no other site is named. Both sides count human sessions only.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."}}},{"name":"get_dismissed_findings","description":"What this account has already dismissed on a site: findings marked dismissed with the reason given (false positive, noise, intentional, duplicate, fixed already, not useful) or auto-expired by housekeeping. Use for questions like 'what did we decide about X', or to see which findings the account has already ruled out. Dismissals with a human reason stay suppressed for 90 days; housekeeping dismissals can return if the behaviour grows.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"limit":{"type":"integer","required":false,"description":"(optional) Max findings, default 20, max 50.","minimum":1,"maximum":50,"default":20}}},{"name":"dismiss_issue","description":"Dismisses a finding on a site in this account, with a reason, the same as the dashboard's Dismiss button. This changes the account's records: the finding leaves the open list and is not re-raised for 90 days unless the behaviour grows. Without confirm=true it returns a preview and changes nothing. The finding can be re-opened in the Harvv dashboard. Idempotent. Use when the person asks to dismiss a finding.","args":{"issue_id":{"type":"string","required":true,"description":"The finding id from get_issues / get_friction_findings."},"reason":{"type":"string","required":true,"description":"Why it is being dismissed.","enum":["false_positive","noise","intentional","duplicate","fixed_already","not_useful"]},"confirm":{"type":"boolean","required":false,"description":"true dismisses the finding; omitted or false returns a preview and changes nothing.","default":false},"note":{"type":"string","required":false,"description":"(optional) A one-line note for the activity log."}}},{"name":"mark_issue_fixed","description":"Records that a finding has been fixed, the same as the dashboard's Mark fixed action. This changes the finding's status: it moves to verification, and Harvv measures before and after on the next detection pass (verify_finding gives the numbers after about 14 days). Without confirm=true it returns a preview and changes nothing. This connection has no tool that reverses it; the finding's status is managed in the Harvv dashboard. Idempotent. Use when the person says a fix has shipped.","args":{"issue_id":{"type":"string","required":true,"description":"The finding id."},"confirm":{"type":"boolean","required":false,"description":"true marks the finding fixed; omitted or false returns a preview and changes nothing.","default":false},"note":{"type":"string","required":false,"description":"(optional) What was changed, for the activity log."}}},{"name":"annotate_change","description":"Saves a dated note about a change made to the site (a deploy, a theme edit, a campaign launch, a price change), so later comparisons covering that date show it beside a rise or drop. This adds a record to the account; without confirm=true it returns a preview and saves nothing. The same date and note text is saved only once. Notes are read back by get_layout_changes (recent_changes) and the dashboard; this connection has no tool that deletes one. Use when the person describes a change they made.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"date":{"type":"string","required":true,"description":"When it happened, YYYY-MM-DD."},"note":{"type":"string","required":true,"description":"What changed, one or two sentences."},"confirm":{"type":"boolean","required":false,"description":"true saves; omitted or false returns a preview and saves nothing.","default":false}}},{"name":"set_conversion_goal","description":"Sets what counts as a conversion on a site: 'form' (a form submit, call or booking), 'add_to_cart' (a store's cart and checkout) or 'cv' (the site sends its own conversion event). Every tool's framing and the dashboard follow it; when unset, Harvv infers it from behaviour, and a saved goal overrides the inference. This changes the site's settings; without confirm=true it returns a preview and changes nothing. It can be changed again with another call or in the site's Settings screen.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"goal":{"type":"string","required":true,"description":"What a conversion is on this site.","enum":["form","add_to_cart","cv"]},"confirm":{"type":"boolean","required":false,"description":"true saves; omitted or false returns a preview and saves nothing.","default":false}}},{"name":"set_brand_names","description":"Saves the names a business is searched by besides its domain: a former name, a store name, or a domain it used to live on (e.g. 'Acme Outfitters', 'acme-old.com'). Search Console and keyword tools then count searches for those names as brand searches; get_search_console's brand_candidates lists searches that look like brand names. This replaces the saved list (an empty list clears it); without confirm=true it returns a preview and changes nothing. A later call with the previous list restores it.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"names":{"type":"array","required":true,"description":"The names (or old domains) the business goes by, 3 to 60 characters each, at most 20.","items":{"type":"string"}},"confirm":{"type":"boolean","required":false,"description":"true saves; omitted or false returns a preview and saves nothing.","default":false}}},{"name":"set_working_context","description":"Saves notes about who a person works with, so later answers are framed for the right reader. scope 'this_client' saves, for one site, who makes the final call (me, my_boss, my_team, client, other) and optional notes about that person (title, priorities, objections); it is kept for the person's own team on that site and never shown to a client they invited. scope 'me' saves a short note about the person (role, agency, way of working), used in answers for them on every site. The values come from what the person states; Harvv does not infer them. This changes the account's saved notes; without confirm=true it returns a preview and changes nothing. A new call replaces the note, and an empty notes string clears it.","args":{"scope":{"type":"string","required":true,"enum":["this_client","me"],"description":"'this_client': who decides on one site, plus notes about them. 'me': a note about the person themselves."},"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from. Used with scope 'this_client'."},"who_decides":{"type":"string","required":false,"enum":["me","my_boss","my_team","client","other"],"description":"(scope 'this_client') Who makes the final call on this site's findings."},"notes":{"type":"string","required":false,"description":"Up to 1000 characters, in the person's own terms. Replaces the saved note; an empty string clears it; omit to keep it."},"confirm":{"type":"boolean","required":false,"description":"true saves; omitted or false returns a preview and saves nothing.","default":false}}},{"name":"set_costs","description":"Saves what a store's products, shipping and payments cost, so get_profit can say whether a change made or lost money (revenue and profit can move in opposite directions). Every field is optional: a field left out keeps its saved value, null clears it back to unknown, and unknown is never treated as zero, so 0 means the cost really is zero. Product cost is default_cost_pct (a % of item price) and/or cost_by_type (per Shopify product type, cost_pct or cost_per_item). Amounts are in the store's order currency. The figures are kept for the person's own team on this site; the other side of a managed site keeps its own. The values come from what the person states. This changes saved settings; without confirm=true it returns a preview and changes nothing. A later call, or Settings > Site profile > Costs, changes or clears them.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"default_cost_pct":{"type":"number","required":false,"description":"Cost of goods as a % of item price, for any product without its own entry (0 to 100). null clears it.","minimum":0,"maximum":100},"cost_by_type":{"type":"array","required":false,"description":"Per product type: [{ product_type, cost_pct } or { product_type, cost_per_item }], at most 50. Replaces the saved list; an empty list or null clears it.","items":{"type":"object","properties":{"product_type":{"type":"string"},"cost_pct":{"type":"number"},"cost_per_item":{"type":"number"}},"required":["product_type"]}},"shipping_cost_per_order":{"type":"number","required":false,"description":"What the store pays to ship one order (0 if the customer pays all of it). null clears it.","minimum":0},"prices_include_tax":{"type":"boolean","required":false,"description":"true if shelf prices already include sales tax (common outside the US), false if tax is added at checkout."},"payment_fee_pct":{"type":"number","required":false,"description":"Card processing % of the order total (0 to 20), e.g. 2.9.","minimum":0,"maximum":20},"payment_fee_fixed":{"type":"number","required":false,"description":"Fixed card fee per order, e.g. 0.30.","minimum":0},"fixed_costs_per_day":{"type":"number","required":false,"description":"Costs that do not move with sales (rent, staff, apps), per day. 0 gives profit before overheads.","minimum":0},"confirm":{"type":"boolean","required":false,"description":"true saves; omitted or false returns a preview and saves nothing.","default":false}}},{"name":"get_profit","description":"Profit per day and for the window: revenue, product revenue net of tax and shipping, estimated cost of goods, shipping cost, payment fees, ad spend (Meta and Google, the same import get_ad_spend reads), fixed costs, and what is left. Use for questions like 'did that change make money', 'am I profitable on ads' or 'what is my break-even return'. Revenue and profit can move in opposite directions (a cheaper product mix, lower ad spend); split_date adds per-day averages before and after a change. Each component carries status measured, estimated or unknown. Costs come from set_costs (Settings > Site profile > Costs); a cost nobody set is unknown, not zero, and then profit.value is null and cost_settings.missing lists the figures needed. profit_before_unknown is not profit. Whole store-local days only, ending yesterday. contribution_margin.break_even_roas is the ad return at which ads break even.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Whole days ending yesterday. Default 30, max 90.","minimum":1,"maximum":90,"default":30},"start_date":{"type":"string","required":false,"description":"(optional) First day, YYYY-MM-DD in the store's time zone. Overrides days."},"end_date":{"type":"string","required":false,"description":"(optional) Last day, YYYY-MM-DD. Default yesterday."},"split_date":{"type":"string","required":false,"description":"(optional) A change date, YYYY-MM-DD: adds per-day averages before it and from it on."}}},{"name":"get_search_console","description":"Google Search Console data Harvv has synced for a site: totals, top queries by clicks, CTR leaks (queries ranking well that get fewer clicks than their position predicts), almost-ranking queries and pages (average position 11 to 20), and how visitors arriving from search behave on each landing page compared with everyone else there. Use for questions like 'which searches do we rank for', 'how did our rankings move', or a ranking report for a client. States how fresh the sync is. Google names the search behind only part of a site's impressions, so query rows do not add up to page totals. Without a Search Console connection (Settings > Integrations) it still returns the search-visitor behaviour, which comes from the pixel, and says what connecting would add. Read-only.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"limit":{"type":"integer","required":false,"description":"(optional) Max rows per list. Default 20, max 50.","minimum":1,"maximum":50,"default":20}}},{"name":"red_team","description":"Returns Harvv's checklist of the ways its own numbers have been wrong in production (impossible counts, missing denominators, stale figures quoted as current, causes asserted but never measured, measurement gaps reported as zero, raw-count rankings that only find the biggest site, coverage limits, and ad-platform numbers read as something they are not, such as all clicks as visitors), for checking an analysis before it is presented. Use for checking a ranking, a prioritized list, a cross-site comparison, a revenue claim, or a claim about ad clicks, costs or returns. With draft_conclusion, the checks come back ordered by how likely each is to apply to that draft. A run that finds nothing is a valid result. Returns a run_id that red_team_report takes.","args":{"draft_conclusion":{"type":"string","required":false,"description":"(optional) The conclusion about to be presented; the checks come back ordered by how likely each is to apply to it."},"site_id":{"type":"string","required":false,"description":"(optional) The site the draft is about: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, Harvv takes it from the tools called just before in this conversation."}}},{"name":"red_team_report","description":"Records the outcome of a Red Team pass (a red_team call whose checks were applied to a draft): one finding per claim checked, with the check_id that applied, the claim in a sentence, the verdict (survives, softened, retracted), why, and source_tool when known, plus revised_conclusion if the conclusion changed. When no claim changed, `checked` lists the check ids applied instead. Harvv uses these reports to find tool answers that misled an analysis. Takes only the run_id from a red_team call in the same conversation. Adds a record; uses no credits.","args":{"run_id":{"type":"integer","required":true,"description":"The run_id red_team returned."},"findings":{"type":"array","required":false,"description":"One per claim you checked, at most 30.","items":{"type":"object","properties":{"check_id":{"type":"string","enum":["denominator","cohort_size","staleness","asserted_cause","absence_as_zero","scale_artifact","coverage","metric_definition","falsifier","other"]},"claim":{"type":"string"},"verdict":{"type":"string","enum":["survives","softened","retracted"]},"why":{"type":"string"},"source_tool":{"type":"string"}},"required":["check_id","claim","verdict"]}},"checked":{"type":"array","required":false,"description":"The check ids you applied, when no claim changed.","items":{"type":"string"}},"revised_conclusion":{"type":"string","required":false,"description":"(optional) The conclusion after the checks, when it changed."}}},{"name":"check_slack_integration","description":"Checks whether this account's Slack integration is wired up correctly by making live calls to Slack, so it reports what Slack actually returns rather than what Harvv's database records. Use for questions like 'is Slack working', 'the Slack bot is not responding', 'why is Harvv not posting to my channel' or 'did my Slack setup work'. Returns an overall status, a `healthy` verdict and a list of checks; each check that is not a pass carries its fix, including the menu path, and a check marked unknown could not be verified.","args":{}},{"name":"get_setup_status","description":"Where this Harvv account's setup stands, from what Harvv has observed rather than what anyone reports: for every site, whether the pixel has ever fired (and whether it is quiet), whether a Shopify store's Customer Events checkout pixel is delivering checkout steps and orders, whether Google Analytics and Search Console are connected, whether a conversion goal is known, the names the business is searched by, and who signs off; for the account, Slack, ad accounts and the note about the person. Returns a checklist per site (done, todo, waiting, n/a), the next action for each gap, who can do it (the person, their developer, or a client who owns the account), the Studio link, and what_to_do_first across sites. A step is marked done only when Harvv has seen it happen, and a waiting step names the proof it is waiting for. Use for questions like 'is everything connected', 'what is missing', 'why is there no data' or 'did my Shopify checkout pixel work'. Uses no credits.","args":{"site_id":{"type":"string","required":false,"description":"(optional) One site to check: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the checklist covers every site on the account."}}},{"name":"get_connect_link","description":"Get a one-time link that connects Google Analytics (GA4) or Google Search Console to one site on this account. The person opens it, signs in to Harvv if asked, clicks Connect, approves read-only access in Google with their own Google login, and picks the property: one page, no menus. The link works only for the person signed in to this Harvv account (anyone else who opens it is told it is for a different account), lasts 7 days, and is used up once the connection is made; calling again before then returns the same link. Use it when someone is setting up Harvv, when get_setup_status shows ga4 or gsc as todo, or when an answer's next_steps says to connect one of them. Google Ads, Meta Ads and Slack have no one-time link: for those this returns the Settings > Integrations link and says so. Free; costs no credits. Give the person the url exactly as returned. If the Google property belongs to a client or someone else, do not forward the link to them: they cannot use it. Ask the property owner to add this person as a user on the property first (Viewer is enough for GA4, Restricted for Search Console), then this person connects. After they say it is done, call get_setup_status to confirm: Harvv's observation is the status, not their word.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"integration":{"type":"string","required":true,"enum":["ga4","search_console","google_ads","meta_ads","slack"],"description":"What to connect: 'ga4' (Google Analytics), 'search_console' (Google Search Console), or 'google_ads', 'meta_ads', 'slack' (no one-time link; returns the Settings link)."}}},{"name":"get_ad_traffic_quality","description":"How the traffic from each ad campaign behaves after it lands, compared with the site's own non-paid traffic: bounce, rage clicks, dead clicks, scroll depth and form interaction, over the last N days (default 30, max 90). Use for questions like 'which campaign sends bad traffic' or 'is my Meta traffic any good'. A session counts as paid only if it carries an ad-platform click id (fbclid, gclid, msclkid, ttclid, li_fat_id) or a paid utm_medium. This tool holds no spend, cost per click or ROAS; cost is in get_ad_spend, and `spend_data.available` says whether an ad account is connected (null means Harvv holds no spend for this site). Campaign names collide across platforms, so platform and campaign name together identify a campaign. Every comparison is significance-tested in `vs_unpaid`: a verdict of `noise` means the difference is within sampling variation however large the percentage, and `too_few_sessions_to_judge` means no verdict. A campaign whose sessions name more than one ad carries `by_ad` (the ad from utm_content, a Meta ad id resolved to its name, or the landing page as a labelled fallback) with the same metrics and test per ad, and `ad_attribution` says what share of each platform's paid sessions name an ad at all. The result includes `ad_learnings`: Harvv's measured rules for running ads.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 30, max 90. Wider than other tools on purpose: a campaign sending 30 sessions a day cannot be separated from noise in 7 days."},"campaign":{"type":"string","required":false,"description":"(optional) One campaign by name (exact, or a substring; case does not matter) to get its landing-page split: every judged page, plus the largest pages too small to judge, with the rest counted. The default answer lists every campaign and gives page rows to as many as fit in one answer, largest first; a campaign whose pages were left out says so and names this argument."}}},{"name":"get_ad_spend","description":"What a site's ads cost and what the ad platform reports they returned: spend, impressions, clicks, conversions and conversion value per campaign, a daily series, and for Google Ads the change history (who changed a budget, bid strategy, status, landing page, ad or keyword, and when). Use for questions like 'how much did I spend' or 'what changed in my Google Ads account'. Has data only when a Meta Ads or Google Ads account is connected (Settings > Integrations); `availability` says which. Money is in each account's own currency, never summed across currencies. Conversions are the platform's own count (Harvv's is get_conversions). Meta `clicks` include photo taps, likes and profile taps; landing_page_views counts visitors. conversions_breakdown separates website purchases from checkouts inside Facebook or Instagram; conversions_value and reported_roas are null until every day carries Meta's purchase value. Campaign names collide across platforms, so platform and campaign name together identify a campaign. Each Meta campaign and each Meta ad also carries the practitioner's first read: link click-through rate (link clicks over impressions, never all-clicks), CPM, cost per Meta purchase, the cost per purchase at the 2% link CTR norm when under it (the norm is a practitioner's, not Harvv's), age (Meta's created time, with a launch flag for days 1 to 3 and metrics by age bucket), reach and frequency with their trend and an audience_exhaustion finding, three returns side by side (Meta's claim, matched store revenue per ad dollar, margin-adjusted return with the break-even line) and a spend_response curve (what a purchase cost on low-spend and high-spend days, and the band it earned best at). `findings` on a campaign or ad lists what fired and the lever it points at. Compact by default (series over 14 days in weeks, newest 6 change rows, the series behind the read left to full); detail:'full' returns more. The result includes `ad_learnings`.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 30, max 90."},"detail":{"type":"string","required":false,"enum":["compact","full"],"default":"compact","description":"(optional) 'compact' (default): every ranked row with the fields an answer needs, plus a `detail` block saying what was left out; small and quick to read. 'full': adds the evidence prose and per-row detail; several times larger and slower to return. The compact answer already contains every finding, count and code."}}},{"name":"get_help","description":"Harvv product documentation and best practice, not site data. Answers how-to and capability questions about Harvv itself, like 'how do I disconnect Slack', 'how do I connect this to Claude', 'how do credits work', 'how do I install the pixel', 'why is my Harvv revenue lower than Shopify' or 'should I use MCP or the dashboard chat'. Returns the matching help entries with their steps and menu paths, or no match when the documentation does not cover the question.","args":{"question":{"type":"string","required":true,"description":"The user's question in their own words, e.g. 'how do I turn off the slack integration'."}}},{"name":"get_element_clicks","description":"Which elements get clicked most, ranked by click volume, optionally narrowed to a page path or an element-name filter (element_contains 'footer' answers 'what do people click in the footer?'). Each row is the element's handle (tag#id.class:\"visible text\"), its click count, and how many sessions hovered it. Clicks are interaction events (one visitor clicking twice counts twice); hover_sessions are distinct sessions. Data runs through the end of yesterday (UTC). Use for questions about where attention and engagement land; broken interactions (dead and rage clicks) are in get_page_friction. Reads the Harvv analytics API (https://harvv.com/docs/mcp#tools); `path` is a page of the site, not an API endpoint.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"path":{"type":"string","required":false,"description":"(optional) Page path to narrow to, e.g. /cart, or / for the homepage. Matched exactly by default; see path_match."},"path_match":{"type":"string","required":false,"enum":["exact","prefix","contains"],"description":"(optional) How `path` is matched. 'exact' (default): that one page only, so '/' is the homepage. 'prefix': the page and every page under it, e.g. path '/products' with 'prefix' covers all product pages. 'contains': any page whose path contains the text. The response's path_scope says how many pages were actually matched."},"element_contains":{"type":"string","required":false,"description":"(optional) Only elements whose handle contains this text, e.g. 'footer' or 'add-to-cart'."},"device":{"type":"string","required":false,"description":"(optional) Narrow to one device class: 'mobile', 'desktop' or 'tablet'. Omit for all devices. The response carries device_coverage: device was only recorded on this rollup from 2026-09-01, so a filtered read over an older window reports that the data was not recorded rather than returning an empty list that would read as 'nobody clicked'."},"segment":{"type":"string","required":false,"description":"(optional) Narrow to one session segment, e.g. 'logged_in', 'anonymous', 'has_cart', or a merchant-declared value namespaced by its window.harvv.user key like 'tags:wholesale' or 'plan:pro'. A value never observed on the site is refused with the observed list, not silently ignored. A session can be in several segments, so per-segment counts overlap and do not add up to a total. segment_coverage says when segment data was not recorded (it exists from 2026-09-01), which is different from nobody in the segment clicking."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped)."},"limit":{"type":"integer","required":false,"description":"(optional) Max elements returned. Default 12, max 25."}}},{"name":"get_js_errors","description":"JavaScript exceptions and failed network requests on a site, grouped by page and by file or URL, over the last N days (default 3, max 30). Three separate lists: `javascript_errors` (uncaught exceptions and unhandled promise rejections), `network_failures` (requests that errored, at any status and resource type) and `slow_but_succeeded` (requests that completed slowly, not counted as failures). Use for questions like 'what is broken on /checkout' or 'is a script failing'. The pixel records at most 3 JavaScript errors per page load, so error counts are a floor, not a total. Browsers report cross-origin script failures as the literal message \"Script error.\" with no file or line; those rows are real failures whose detail the browser withheld. Compact by default with a `detail` block naming what was left out; detail:'full' adds the evidence prose. Reads the Harvv analytics API (https://harvv.com/docs/mcp#tools); `path` is a page of the site, not an API endpoint.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"path":{"type":"string","required":false,"description":"(optional) Page path to narrow to, e.g. /checkout, or / for the homepage. Matched exactly by default; see path_match."},"path_match":{"type":"string","required":false,"enum":["exact","prefix","contains"],"description":"(optional) How `path` is matched. 'exact' (default): that one page only, so '/' is the homepage. 'prefix': the page and every page under it, e.g. path '/products' with 'prefix' covers all product pages. 'contains': any page whose path contains the text. The response's path_scope says how many pages were actually matched."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 3, max 30 (clamped). Measured on the busiest store: 1d=3.4s, 3d=7.3s, 7d exceeds the query budget. A longer window on a high-traffic site can come back degraded."},"limit":{"type":"integer","required":false,"description":"(optional) Max rows per list. Default 10, max 25."},"detail":{"type":"string","required":false,"enum":["compact","full"],"default":"compact","description":"(optional) 'compact' (default): every ranked row with the fields an answer needs, plus a `detail` block saying what was left out; small and quick to read. 'full': adds the evidence prose and per-row detail; several times larger and slower to return. The compact answer already contains every finding, count and code."},"device":{"type":"string","required":false,"description":"(optional) Only sessions on this device type.","enum":["mobile","desktop","tablet"]},"segment":{"type":"string","required":false,"description":"(optional) Only sessions carrying this visitor segment. Unknown segments are refused with the observed list."}}},{"name":"get_page_behavior","description":"How visitors behave on a page (or the whole site) beyond clicks: the share who left mid-visit to check something elsewhere (tab-away), who hesitated (with the median pause), who copied text such as a price or SKU, who held the site open in several tabs, who read the content versus skimmed or skipped it, and which elements people hovered but never clicked. `panels` covers each panel the page opens (filter drawer, menu, cart drawer, search, signup popup): how many opened it, what opened it and when, whether anything inside was used, and how it was closed or abandoned. Use for questions about hesitation, distraction, comparison shopping, whether content gets read, or what a popup does to a visit. Rates are aggregated over real sessions; `low_confidence: true` marks a small sample, and a signal reported as not measured had no data in the window, which is different from zero. Reads the Harvv analytics API (https://harvv.com/docs/mcp#tools); `path` is a page of the site, not an API endpoint.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"path":{"type":"string","required":false,"description":"(optional) Page path to narrow to, e.g. /cart, /products/some-item, or / for the homepage. Matched exactly by default; see path_match. Omit for site-wide. When set, the response carries path_scope (how many pages were matched) and open_findings_for_this_page (what Harvv already found there)."},"path_match":{"type":"string","required":false,"enum":["exact","prefix","contains"],"description":"(optional) How `path` is matched. 'exact' (default): that one page only, so '/' is the homepage. 'prefix': the page and every page under it, e.g. path '/products' with 'prefix' covers all product pages. 'contains': any page whose path contains the text. The response's path_scope says how many pages were actually matched."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped)."}}},{"name":"get_forms","description":"Every form visitors use on a site, one row per form: sessions that started it, progress through a multi-step form (sessions per step), what Continue/Submit clicks did (refused with errors, moved on, finished, left the page, closed the popup, or nothing visible for 10 s), the first field flagged on a refused click, the last field touched before leaving and its state, and structural problems (fields with no label, '*' fields not marked required, error messages not linked to their field, email or phone typed as plain text, missing autocomplete, fake div buttons, no real <form>). Use for questions like 'why don't people finish my signup form' or 'which step loses people'. Counts are sessions unless a field says clicks. Form tagging began with pixel v10 on 2026-09-29 and is counted from v11; `coverage` says how many field events were left out. Forms in open shadow-root widgets are measured from pixel v17, `widgets` says which third-party widgets Harvv can see inside, and forms in iframes or closed widgets are not measured. Typed text is never read, and whether a submission reached a CRM or inbox is outside Harvv's view. Reads the Harvv analytics API (https://harvv.com/docs/mcp#tools); `path` is a page of the site, not an API endpoint.","args":{"site_id":{"type":"string","required":false,"description":"(optional) The site to read: its domain (e.g. example.com), 16-character pixel key, or site UUID. When omitted, the account's only site is used if it has exactly one; an account with several sites returns reason site_required with the list of sites to choose from."},"path":{"type":"string","required":false,"description":"(optional) Page path the form is on, e.g. / or /contact. Matched exactly by default; see path_match. Omit for every page."},"path_match":{"type":"string","required":false,"enum":["exact","prefix","contains"],"description":"(optional) How `path` is matched. 'exact' (default): that one page only, so '/' is the homepage. 'prefix': the page and every page under it, e.g. path '/products' with 'prefix' covers all product pages. 'contains': any page whose path contains the text. The response's path_scope says how many pages were actually matched."},"form":{"type":"string","required":false,"description":"(optional) One form's key exactly as a previous get_forms call returned it in `form`, e.g. div#signup or form#contact."},"days":{"type":"integer","required":false,"description":"(optional) Window in days. Default 7, max 30 (clamped)."}}}],"auth":{"type":"bearer","header":"Authorization","format":"Bearer <token>","how_to_get_token":"Sign in at https://harvv.com/login, then POST /customer/mcp-token to generate a Lovable MCP token for your account."},"usage":"POST /mcp/lovable with body { tool: \"<name>\", args: {...} } and Authorization: Bearer <your customer MCP token>"}