# Suparanku MCP Server

suparanku v1.18.0 · MCP protocol 2025-11-25 · transport: Streamable HTTP (stateless).

> This reference is generated from the live tool registry — it never drifts from the server.

Setup guides for Claude, Claude Code, Cursor and Cline (plus llms-install.md): https://github.com/Apurichoumi/mcp-docs

## Endpoint

```
https://app.suparanku.com/api/mcp/v1
```

Pin the versioned URL above. `https://app.suparanku.com/api/mcp` (unversioned) is an alias to the
current stable major and may advance to a new major in the future.

## Versioning

The tool catalog is semver-versioned (current v1.18.0). Additive
changes (new tool, new optional parameter, new output field) ship as MINOR within
this endpoint — re-read `tools/list` to pick them up. Breaking changes ship as a
new `/api/mcp/vN` endpoint; the previous version keeps working until an announced
sunset date, and any tool being retired is marked **Deprecated (sunset …)** below
before removal. See the Changelog at the end of this document.

## Connect

Any MCP-compatible client/agent. Two ways to authenticate:

- **Personal Access Token (PAT)** — create one in the Suparanku app under
  Settings → MCP & API tokens, then pass it as a Bearer token. For clients that
  only speak stdio, bridge with: `npx mcp-remote https://app.suparanku.com/api/mcp/v1 --header "Authorization: Bearer <TOKEN>"` (pin mcp-remote >= 0.1.16).
- **OAuth 2.1** — interactive hosted clients discover the authorization server
  via `/.well-known/oauth-protected-resource` and run Authorization Code + PKCE.

## How it works

Suparanku measures how AI assistants (ChatGPT, Claude, Gemini, etc.) talk about a brand
across five axes — visibility, position, sentiment, competitors, and sources — and turns
that into prioritized recommendations and content briefs for the Japanese market.

WORKFLOW (call suparanku_how_it_works for the full end-to-end algorithm):
1. Call suparanku_list_companies FIRST to get a company_id. Every other tool needs one.
   (No company yet, or you need another one? suparanku_create_company makes one — see below.)
2. Pass company_id (and optionally brand_id, an own brand) to each tool.
3. Use suparanku_explain_term / suparanku_get_glossary to understand the metrics.
4. suparanku_get_metrics is the unified analytics tool — pick group_by and either a
   date_range (trend) or a scan_ref from suparanku_list_scans (one past scan, all cuts).
   Sentiment and competitor share are returned there, not as separate tools.

TENANCY & PERMISSIONS:
- A token can span multiple companies (call suparanku_list_companies to see which),
  but only those it is scoped to: your active memberships intersected with the token scope.
  It can never reach a company outside that set (no cross-tenant access).
- READ tools work whenever the company has MCP access (read or full).
- WRITE tools require the company to be mcp_access="full", your role to be member or higher,
  and a read+write token. DELETE tools additionally require an allow-destructive token and
  confirm:true, and some (delete_prompt, delete_topic) are irreversible / break comparability.
- A company owner/admin can revoke a specific credential's access to THEIR company at any
  time. If a company that previously worked starts returning NOT_FOUND, your access to it was
  revoked — other companies on the same token are unaffected. Re-run suparanku_list_companies.
- Creating a company (suparanku_create_company) additionally needs admin/owner on a company with
  full MCP access and a remaining company-creation allowance, and a token scoped to ALL companies
  (an explicit-list token cannot reach what it creates). It runs NO onboarding audit — follow up
  with suparanku_run_site_fast_audit, then suparanku_run_market_research, then
  suparanku_generate_prompts. Same for a new brand via suparanku_create_brand.

OUTPUT: results are paginated and size-capped; tables come back as CSV. Errors are returned
as tool errors with guidance on how to fix the call — read them and self-correct.

PAGING & LOOPS:
- List/source reads return { total, offset, limit, has_more }. To read a whole list, advance
  offset += limit while has_more is true.
- Marking up sources: call suparanku_list_source_categories once for the valid categories, then
  loop suparanku_get_sources(only_unmarked:true) → suparanku_set_source_category_bulk until
  unmarked_count reaches 0.
- Triaging possible competitors: add/dismiss REMOVE a candidate and re-rank the list, so a plain
  offset += limit walk skips candidates. Page through with READ-ONLY calls first to collect the
  full set (by canonicalKey), THEN add/dismiss.
- Fixing the site (site audit): run_site_fast_audit → poll get_latest_site_audit_run → 
  get_site_schema(only_problems:true) for the pages that need work → per page, page through
  list_technical_recommendations (summary cards; advance offset += limit while has_more) +
  get_page_outbound_links to triage links. A null-status link is NOT auto-broken: an external
  "no_response" is often a bot-block — resolve it with set_link_verdict (ok/bad), don't treat it as
  broken. For each rec, call get_recommendation for the full fix brief + artifacts, apply, deploy, re-run.
- Real demand from Google (the one MEASURED demand signal — everything else about demand is
  inferred from the brand site or from AI answers). Search Console queries that no Market Map
  cluster covers are grouped into at most 5 themes per weekly pass and wait for a verdict.
  Nothing is auto-accepted, because search demand is not AI demand. The loop:
  suparanku_get_real_demand → judge each theme → suparanku_set_real_demand_verdict(theme_keys,
  accept|dismiss) → suparanku_generate_prompts for what you accepted (accepted themes lead the
  coverage plan) → re-read suparanku_get_real_demand to confirm the new statuses. Accept only
  themes describing how a BUYER of this brand chooses; dismiss definition lookups, off-market
  languages and anything unrelated to the buying decision. When unsure, dismiss. The
  context.demand.unreviewed recommendation is this queue surfacing in the Growth engine — clearing
  it auto-resolves the recommendation on the next generation run.
- Growing prompt coverage: suparanku_get_prompt_generation_status (can_generate_now?) →
  suparanku_generate_prompts → poll the status tool with the batch_id until "done" (new prompts
  are topic-grouped and scanned automatically). status "researching_demand" = no candidate demand
  was ready, a market-research pass was queued — poll, then call generate again. Only write prompts
  yourself (suparanku_create_prompts_bulk) when you were asked to track specific queries.
- Reports: suparanku_generate_report → poll suparanku_get_report until status "done" →
  suparanku_get_report_download_url (the URL expires in ~15 minutes — re-mint, never store it).
- Rate limit is per-company weighted cost-units per minute (Starter 60). Long write loops can hit
  TOO_MANY_REQUESTS — expect it, back off, and retry; do not hammer at full speed.

## Tools — read (56)

### `suparanku_help`

Read this first. Explains what Suparanku is, how to use this MCP (workflow + tenancy + read vs write), and lists every available tool. Optionally pass `topic` to filter the tool list.

- kind: read-only, idempotent
- input: topic
- since: v1.0.0

### `suparanku_how_it_works`

Read this to understand the WHOLE Suparanku workflow end-to-end (the Context → Analytics → Growth → Action loop, steps 0–9), with the tools for each step. Complements suparanku_help (which just lists the tools).

- kind: read-only, idempotent
- input: (no input)
- since: v1.0.0

### `suparanku_explain_term`

Explain a Suparanku / AI-visibility (GEO) domain term so you can interpret the data correctly — e.g. "visibility", "position", "sentiment", "sample", "content brief", "source", "markup version". Pass the term in `term`.

- kind: read-only, idempotent
- input: term
- since: v1.0.0

### `suparanku_get_glossary`

Return the full Suparanku / AI-visibility (GEO) glossary so you understand every metric and concept before working with the data. No input.

- kind: read-only, idempotent
- input: (no input)
- since: v1.0.0

### `suparanku_list_source_categories`

List every source (cited-domain) category Suparanku uses — id, label, meaning, and whether you can assign it — so you can mark up "unknown" (needs-markup) domains. Call this before suparanku_set_source_category(_bulk). No input.

- kind: read-only, idempotent
- input: (no input)
- since: v1.1.0

### `suparanku_list_companies`

List the companies this token can access, with each company_id, name, plan tier, your role, and its MCP access level (none/read/full). ALWAYS call this FIRST — every other tool needs a company_id from here. free_audit_running:true means that company’s free audit is still building its profile, market map, topics and prompts: reads work, write tools are refused until it lands — track it with suparanku_get_free_audit_progress.

- kind: read-only, idempotent
- input: (no input)
- since: v1.0.0

### `suparanku_list_brands`

List a company’s own tracked brands (id, name, language, domain). Use a brand id as brand_id in other tools.

- kind: read-only, idempotent
- input: company_id
- since: v1.0.0

### `suparanku_list_topics`

List a brand’s topics (theme groupings of prompts) with prompt counts.

- kind: read-only, idempotent
- input: company_id, brand_id
- since: v1.0.0

### `suparanku_list_competitors`

List the competitors already MARKED/tracked for a brand. For not-yet-marked suggestions use suparanku_list_possible_competitors.

- kind: read-only, idempotent
- input: company_id, brand_id
- since: v1.0.0

### `suparanku_list_possible_competitors`

The FULL potential-competitor inbox — every discovery source merged and deduped against already-tracked and dismissed competitors: live AI answers (source ai_answers), market research (research_player / research_proposal / research_watchlist) and earlier audit candidates (wave_candidate). `confirmed:true` = a research candidate corroborated by current live AI answers (highest trust, sorted first); `seenInAnswers:true` also includes mentions-derived earlier audit candidates, while false rows remain research hypotheses. Rows with ai_answers carry `canonicalKey`/`promptIds` (pass canonicalKey to suparanku_get_possible_competitor_evidence for the exact prompts/answers); every row carries `dismissAliases` — to act, mark it with suparanku_add_competitor or dismiss it with suparanku_dismiss_possible_competitor (canonicalKey + displayName + dismissAliases as its aliases). Paginated: `total` is the full inbox count, follow `has_more`. NOTE: add/dismiss REMOVE a row from this list and re-rank it, so a plain `offset += limit` walk skips rows — page through with READ-ONLY calls first to collect the full set, THEN act.

- kind: read-only, idempotent
- input: company_id, brand_id, limit, offset
- since: v1.0.0

### `suparanku_get_possible_competitor_evidence`

For ONE possible competitor (pass its `canonical_key` from suparanku_list_possible_competitors): the actual prompts/AI answers where it was spotted — prompt text, which LLM, the matched phrase and sentiment. Use it to decide whether to mark it as a competitor (suparanku_add_competitor) or dismiss it. Returns up to `limit` most-recent mentions from the last 90 days (no offset paging).

- kind: read-only, idempotent
- input: company_id, brand_id, canonical_key, scope, prompt_id, limit
- since: v1.0.0

### `suparanku_get_competitor_mentions`

For ONE tracked competitor (pass its `competitor_brand_id` from suparanku_list_competitors): the prompts/AI answers in the LATEST scan where it appeared — prompt text, which LLM, the matched snippet, sentiment and position. `answers_count` = number of answers it appeared in; `prompts_count` = number of distinct prompts. Use it to verify the competitor’s aliases are catching the right answers. Paginated: page with `limit`/`offset`, follow `has_more`.

- kind: read-only, idempotent
- input: company_id, brand_id, competitor_brand_id, topic_id, include_branded, limit, offset
- since: v1.0.0

### `suparanku_get_competitor_topic_gaps`

Where competitors beat you, per topic: your share of AI answers vs EVERY tracked competitor’s share over the window (share = answers mentioning the brand / total answers sampled for the topic). Rows sort by the deficit (best competitor − you, descending); `gap_pp` is that deficit in percentage points and `beaten:true` flags a deficit of 30pp or more. The branded topic and the «Other» buffer topic are excluded (neither is a competitive comparison), as are topics where nobody appears. `total_runs` = answers sampled for the topic; `low_data:true` (fewer than 30 runs) means the shares are directional, not statistical — still shown, never hidden. Matches the dashboard’s “Where they beat you” view.

- kind: read-only, idempotent
- input: company_id, brand_id, date_range
- since: v1.8.0

### `suparanku_get_competitor_source_gap`

For ONE tracked competitor (pass its `competitor_brand_id` from suparanku_list_competitors): the cited domains whose pages verifiably mention that competitor while NO cited page on the domain mentions your brand — i.e. the publications to target next. Up to 30 domains ordered by citation volume, each with its source category. `unverified_count` = cited URLs in the window that could not yet be checked for this competitor (excluded from the list, not evidence either way). Branded prompts (text naming a tracked brand) are excluded — the gap measures open-market answers only.

- kind: read-only, idempotent
- input: company_id, brand_id, competitor_brand_id, date_range
- since: v1.8.0

### `suparanku_list_scans`

List a brand’s scan events (most recent first): id, started_at, trigger, status, markup_version (comparability marker). Use a scan id as scan_ref in suparanku_get_metrics to read that point-in-time.

- kind: read-only, idempotent
- input: company_id, brand_id, limit
- since: v1.0.0

### `suparanku_get_metrics`

The unified analytics tool: visibility, position, sentiment, sources and competitor share. Choose group_by (overall/topic/provider/competitor/prompt) and EITHER a date_range (trend) OR a scan_ref from suparanku_list_scans (one past scan, all cuts). sentiment & competitor share are returned here — there are no separate tools for them. For movement scan over scan (including competitors inside a topic, and a marker for scans whose prompt set changed) use suparanku_get_visibility_dynamics and suparanku_get_dynamics_summary.

- kind: read-only, idempotent
- input: company_id, brand_id, group_by, date_range, scan_ref
- since: v1.0.0

### `suparanku_get_visibility_dynamics`

How visibility MOVES, scan over scan — the trend, not the snapshot. Pick a metric (visibility / position / sentiment) and a cut: brands = you against every tracked competitor, topics = your topics against each other, providers = per AI assistant. topic_id narrows the brands and providers cuts to one topic. Every point is one scan, identified by scan_id and stamped with scan_at — two scans on the same day are two points, not one. Each point also carries published_since_prev_scan — how many recommendations were marked published between that scan and the one before it, which is what lets you say whether a move followed our own work or the market’s. And runs, the number of measurements behind the value: a jump built on a handful of runs is noise. Each point carries measurement_changed: when true, the prompt set was edited between that scan and the one before it (added / removed / moved counts say how), so the step is NOT a clean before/after — say so in any report instead of presenting it as a result. comparable_with_prev=false means the methodology itself changed. metric="sentiment" works in every cut, competitors included: each phrase in an answer is scored for the brand it names, so two brands in one comparison answer no longer inherit each other’s verdict. It is the same number every other Suparanku surface reports.

- kind: read-only, idempotent
- input: company_id, brand_id, metric, breakdown, topic_id, limit
- since: v1.16.0

### `suparanku_get_dynamics_summary`

The whole "did it work" answer in one call: the four headline numbers with their move since the previous scan (visibility, average position, sentiment, gap to the strongest competitor), every topic ranked with its change and who leads it, and every tracked brand with its change. Start a progress report here, then drill with suparanku_get_visibility_dynamics. Topics whose prompt set was edited between the two scans carry measurement_changed — their delta mixes a real move with a changed measurement, so do not report it as a clean result.

- kind: read-only, idempotent
- input: company_id, brand_id, limit
- since: v1.16.0

### `suparanku_get_sources_dynamics`

What the assistants read, scan over scan: how many distinct domains they pulled from, what share of the citations was your own site and what share was a tracked competitor’s — as a total and split per assistant, since different assistants read different corners of the web. Pass topic_id to restrict it to the sources cited for one topic. Also returns the domains themselves with their per-scan citation counts and a status of new (first cited in the latest scan) or gone (cited before, not any more) — the two facts worth acting on. Filter by ownership with filter, page the domain list with domain_limit / offset.

- kind: read-only, idempotent
- input: company_id, brand_id, filter, topic_id, limit, domain_limit, offset
- since: v1.16.0

### `suparanku_get_sources`

The websites AI answers cite for a brand (latest scan). level="domain" (default) returns a PAGE of domains with mention counts + effective category + a needs-markup marker; level="url" returns up to 50 URLs for a specific domain (pass `domain`). Counts in the domain response: `total` = number of domains AFTER filters (use it for paging — compare against `offset`); `total_sources` = total citation volume (Σ counts), NOT a domain count; `unmarked_count` + `category_counts` are over the FULL set (unaffected by filters), for planning and completion. Filters (domain level): `only_unmarked:true` returns only domains still needing a category — loop with it until `unmarked_count` reaches 0 to mark everything; `category` returns only domains of one effective category (see `category_counts` for the distribution). Page with `limit`/`offset` and follow `has_more`.

- kind: read-only, idempotent
- input: company_id, brand_id, level, topic_id, domain, only_unmarked, include_branded, category, limit, offset
- since: v1.0.0

### `suparanku_list_prompts`

List a brand’s tracked prompts (id, text, topic, per-provider stats). Use a prompt id with the prompt-scan tools. Paginated: `total` is the full count, page with `limit`/`offset` and follow `has_more` (rows are heavy, so large brands need paging).

- kind: read-only, idempotent
- input: company_id, brand_id, topic_id, limit, offset
- since: v1.0.0

### `suparanku_get_prompt_generation_status`

The state of PROMPT GENERATION for the brand: whether a generation (or a market-demand research pass) is running right now, how much room the brand still has for new prompts, how many generations are left today, and — when `batch_id` is passed — the outcome of that batch (generating|done|failed|undone + how many prompts it added). Call it BEFORE suparanku_generate_prompts to see whether generating is possible, and AFTER it to poll until the batch is done.

- kind: read-only, idempotent
- input: company_id, brand_id, batch_id
- since: v1.11.0

### `suparanku_get_prompt_scan_meta`

For one prompt: which AI providers were sampled, how many samples, and per-prompt aggregates — WITHOUT the full answer texts. Then call suparanku_get_verbatim_answer for a specific provider+sample.

- kind: read-only, idempotent
- input: company_id, prompt_id, scan_ref
- since: v1.0.0

### `suparanku_get_verbatim_answer`

The full verbatim AI answer for a prompt — heavy, so fetch only the provider+sample you need (read suparanku_get_prompt_scan_meta first). Includes the brand’s position, citations and sentiment.

- kind: read-only, idempotent
- input: company_id, prompt_id, provider_id, sample_index, scan_ref
- since: v1.0.0

### `suparanku_list_recommendations`

ALL of the brand’s current recommendations in one call as a FLAT, paginated list of summary cards (each item carries `category` = context | technical | content, plus status, impact/effort and — for content — brief_status). This is NOT the full detail: for one item’s steps/evidence/brief use suparanku_get_recommendation. `counts` gives the per-category active totals (matches the dashboard badges). Optionally filter by `status` and/or `category`: open/in_progress = the Actual worklist; done = closed THIS iteration; dismissed = every Skip ("won't fix") whose signal is still detected, however old. A skip whose signal stopped firing ages out of the list entirely. Paginate with limit/offset until has_more is false.

- kind: read-only, idempotent
- input: company_id, brand_id, locale, status, category, limit, offset
- since: v1.1.0

### `suparanku_list_context_recommendations`

List CONTEXT recommendations (improve the data Suparanku tracks) as paginated summary cards (id, status, impact/effort) — NOT the full detail. Prioritised with impact/effort. Includes manual VISUAL verifications from the site audit (signal_key `context.site.*`, marked `manual:true`) — e.g. the og:image review, whose card carries `imageUrl`: SHOW that image to the user in the chat, ask whether it looks right (crop, blur, readable text, branding), and only then mark it via suparanku_set_recommendation_status (done records the verified-OK verdict). These never affect the SRPS score. Paginate with limit/offset until has_more is false; for one item’s detail use suparanku_get_recommendation.

- kind: read-only, idempotent
- input: company_id, brand_id, status, limit, offset, locale
- since: v1.0.0

### `suparanku_list_technical_recommendations`

List TECHNICAL recommendations (site/markup changes for AI crawlers, from the site audit) as paginated summary cards (id, status, impact/effort, scope, affected_count + a small URL sample) — NOT the inline fix brief. Prioritised with impact/effort. Optionally narrow with audit_scope: "site" or "page:<url>". Paginate with limit/offset until has_more is false; for one item’s full fix brief (plain-language explanation + ready-to-paste artifacts + machineReadable.fix/verify) call suparanku_get_recommendation. AUTONOMOUS FIX LOOP: run_site_fast_audit → poll get_latest_site_audit_run until done → get_site_schema(only_problems:true) to find the pages that need work → for each, page through this list + get_page_outbound_links, and triage links site-wide with suparanku_get_unverified_links + suparanku_get_broken_links (mark a valid one ok with set_link_verdict, fix a really dead one on the site) → for each rec, suparanku_get_recommendation for the full brief, apply its artifacts per its steps → deploy → run_site_fast_audit again; fixed checks auto-resolve.

- kind: read-only, idempotent
- input: company_id, brand_id, status, limit, offset, locale, audit_scope
- since: v1.0.0

### `suparanku_list_content_recommendations`

List CONTENT recommendations (one card per THEME; each closes via publications — Theme × [Format & Platform] rows) as paginated summary cards: id, status, impact/effort, brief_status, publications counters (publications_total/published/skipped, briefs_ready) and stale_since when the latest scan no longer confirms the signal. NOT the full checklist — for a card’s `publications` rows, reasons and briefs use suparanku_get_recommendation / suparanku_get_content_brief. Paginate with limit/offset until has_more is false.

- kind: read-only, idempotent
- input: company_id, brand_id, status, limit, offset, locale
- since: v1.0.0

### `suparanku_get_recommendation`

Full detail of one recommendation (description, evidence with per-signal `reasons`, status, published URLs). For a CONTENT card the `publications` map is the working checklist — Theme × [Format & Platform] rows keyed by "own" (the anchor piece on the brand’s site) and placement domains, each with status (planned/skipped/published), briefStatus, rationale and url. Agent loop per row: generate_content_brief(publication_key) → get_content_brief → publish the piece → mark_recommendation_published(publication_key, url); skip pairs you won’t do via set_publication_status. For a TECHNICAL recommendation the `brief` field carries the complete deterministic fix inline (problem/why/desired/how, ready-to-paste artifacts, acceptance criteria, machineReadable.fix/verify; `brief.copyText` = the same fix copy-ready in English). Apply the artifacts, then re-run the audit to auto-resolve it.

- kind: read-only, idempotent
- input: company_id, brand_id, recommendation_id, locale
- since: v1.0.0

### `suparanku_get_content_brief`

Return the brief for one publication row of a content card. `publication_key`: omit or "own" for the anchor (the original brief); a domain key for an external pair’s adaptation brief. Response is { brief_status, brief?, hint }: ready→the brief; none→no brief yet + how to generate one; generating→poll later; failed→reason. Never an opaque error.

- kind: read-only, idempotent
- input: company_id, brand_id, recommendation_id, publication_key
- since: v1.0.0

### `suparanku_get_brief_rewrite`

Return the rewrite-brief content for a recommendation (rewrites of existing URLs), distinct from the main content brief.

- kind: read-only, idempotent
- input: company_id, brand_id, recommendation_id
- since: v1.0.0

### `suparanku_get_company_usage`

This company’s MCP-relevant quotas and remaining budget: manual refreshes, content briefs and deep page audits (used / quota / reset date), scope limits (max brands/prompts/topics/competitors), and mcp_access. No prices.

- kind: read-only, idempotent
- input: company_id
- since: v1.0.0

### `suparanku_get_report`

Latest PDF report metadata (id, status, locale, data window, is_stale) + the last 10 completed reports (history). Does NOT trigger generation (use suparanku_generate_report) and does NOT return the file — pass a report id to suparanku_get_report_download_url for a time-limited download link.

- kind: read-only, idempotent
- input: company_id, brand_id
- since: v1.0.0

### `suparanku_get_report_download_url`

Mint a fresh, time-limited download URL (valid ~15 minutes) for a COMPLETED PDF report. Pass a report id from suparanku_get_report (latest.id or a history row id). The URL expires — do not store it; re-call this tool whenever you need to download again. Only reports with status "done" have a file.

- kind: read-only, idempotent
- input: company_id, brand_id, report_id
- since: v1.8.0

### `suparanku_get_scan_status`

Poll the status of a manual scan started with suparanku_run_manual_scan (queued/running/completed, per provider).

- kind: read-only, idempotent
- input: company_id, brand_id, prompt_ids
- since: v1.0.0

### `suparanku_get_site_audit`

The brand’s domain-level site audit for its primary domain: the SRPS / SRPS+ scores, the grouped domain checks (accessibility, AI-agent access, entity/LLMO, base metrics, social card) with their verdicts, page/locale counts, and the manual-check state. For one page’s detail use suparanku_get_page_audit; for the page-by-page tree use suparanku_get_site_schema.

- kind: read-only, idempotent
- input: company_id, brand_id
- since: v1.2.0

### `suparanku_get_site_schema`

The brand’s crawled pages with per-page status: URL, locale, section, HTTP status, verdict, SRPS (fast) / SRPS+ (deep) scores, Lighthouse perf_score, AI-cited count, issue counts, flags, and the redirect / client_redirect markers (a "200" that redirects client-side is a fake-200 shell — `clientRedirect:true`; a server-side 3xx to a different page is a `redirect` node). Pass `only_problems:true` to return only pages that need attention. Use a page URL with suparanku_get_page_audit (per-page detail) or suparanku_get_page_outbound_links / suparanku_get_page_inbound_links (its link graph). Paginated: page with limit/offset and follow has_more (large sites have many pages).

- kind: read-only, idempotent
- input: company_id, brand_id, only_problems, limit, offset
- since: v1.2.0

### `suparanku_get_page_audit`

The full audit for ONE page (pass its `url`): fast + deep check categories with verdicts, the SRPS / SRPS+ scores, the on-page links block (status + role), canonical/noindex flags and AI citations. For the link graph with status classification + the no-response verdict use suparanku_get_page_outbound_links / suparanku_get_page_inbound_links. List pages first with suparanku_get_site_schema.

- kind: read-only, idempotent
- input: company_id, brand_id, url
- since: v1.2.0

### `suparanku_get_free_audit_progress`

Progress of the company’s free onboarding audit as 5 ordered steps (site crawl → business profile → market research → first measurement → competitor selection), each waiting|running|done with a short human-readable detail, plus progress_percent. While free_audit_running is true every write tool for this company is refused, so THIS is the one tool to poll (~30s cadence; the audit takes minutes to tens of minutes by site size). ready:true = the audit landed (or the company never ran one — free_audit:"skip" companies are ready immediately): stop polling and read suparanku_get_business_profile / suparanku_get_market_map / suparanku_list_prompts / suparanku_get_metrics.

- kind: read-only, idempotent
- input: company_id, brand_id
- since: v1.18.0

### `suparanku_get_latest_site_audit_run`

Status of the most recent FAST-family site-audit run (full crawl or fast page re-audit) for the brand: status (queued/running/done/partial/failed/blocked) and pages_done/pages_total progress. Poll this after suparanku_run_site_fast_audit until status is done/partial, THEN read suparanku_list_technical_recommendations. The parallel homepage deep run every full audit kicks is not reported here — deep results appear in suparanku_get_page_audit.

- kind: read-only, idempotent
- input: company_id, brand_id
- since: v1.4.0

### `suparanku_get_page_inbound_links`

Internal links pointing TO this page (the inbound side of the link graph): which crawled pages link here, with the anchor text, the link group, and the source page’s HTTP status. Answers "how was this page found / what links here" and explains an orphan or a 404 in the tree. Paginated. Pass a page URL from suparanku_get_site_schema.

- kind: read-only, idempotent
- input: company_id, brand_id, url, limit, offset
- since: v1.5.0

### `suparanku_get_page_outbound_links`

The on-page (outbound) links for ONE page, each classified for triage: status_class (ok / redirect / broken / no_response / not_checked / app_scheme), the no-response manual verdict (pending / ok / bad) and the `effective` status after that verdict. A null status is NEVER a confirmed break — an EXTERNAL no_response is often a bot-block (x.com, instagram, Cloudflare), an INTERNAL null is "not_checked" (target outside the audited set). Triage off `effective` (what SRPS / link health reflect), not raw `status_class`. Filter with all | errors (effective broken) | no_response (all unreachable externals) | external. Paginated. Pass a page URL from suparanku_get_site_schema; resolve a no-response link with suparanku_set_link_verdict.

- kind: read-only, idempotent
- input: company_id, brand_id, url, filter, limit, offset
- since: v1.5.0

### `suparanku_get_unverified_links`

Every EXTERNAL outbound link across the WHOLE site whose probe was a no-response (status null) or a bot-block (401/403/429, or LinkedIn’s 999) — neither is a confirmed dead link, so each needs a verdict (big hosts like openai.com behind Cloudflare reject the crawler while the URL opens fine in a browser). Deduped by href, with the site-wide verdict (pending / ok / bad), the probe status, and how many pages it appears on. The one-stop triage queue: list here, then resolve each with suparanku_set_link_verdict (ok = works, bad = really dead, null = back to pending). Pass only_pending:true for just the actionable set. Paginated. Its sibling suparanku_get_broken_links holds the links that are already CONFIRMED dead — between the two you have every external link that needs a decision.

- kind: read-only, idempotent
- input: company_id, brand_id, only_pending, limit, offset
- since: v1.5.0

### `suparanku_get_broken_links`

Every EXTERNAL outbound link across the WHOLE site that is a CONFIRMED dead link: a concrete 4xx/5xx probe status, plus any link you verdicted "bad" in the review queue. Bot-blocks (401/403/429, LinkedIn’s 999) and no-responses are NOT here — they are unproven and live in suparanku_get_unverified_links. Deduped by href, widest reach first, with the probe status, the manual verdict and how many pages carry the link. verdict null = the probe result stands, nothing to decide; "bad" = a review confirmed it; "ok" = reviewed as a false positive (the probe lied) — such a link stops counting as broken and lowers nothing, but stays listed in the reviewed tail so an accidental dismissal is visible and reversible. Pass only_active:true to drop that tail. Fix a real one on the site, or call suparanku_set_link_verdict with "ok" when the URL genuinely opens (null puts it back). Paginated.

- kind: read-only, idempotent
- input: company_id, brand_id, only_active, limit, offset
- since: v1.17.0

### `suparanku_get_indexing_status`

Google index status per own-site page, straight from Search Console URL Inspection: verdict and coverage reason, plus the full diagnosis — robots_txt_state, indexing_state, page_fetch_state, the Google vs declared canonical pair, last crawl, and the date the page was first seen indexed. Filter published (published from a recommendation) | cited (AI-cited) | problems (anything not PASS) | all. Empty with connected:false when Search Console is not connected. Only inspected pages carry detail — inspection runs on a daily budget, so a fresh page can legitimately read NOT_INSPECTED.

- kind: read-only, idempotent
- input: company_id, brand_id, filter, limit, offset
- since: v1.7.0

### `suparanku_get_search_performance`

Per-page Google Search clicks/impressions/CTR/position for the selected window (7d|28d|90d) with the previous window for every metric, plus the page’s index verdict and its url_hash (the handle for suparanku_get_page_traffic_detail). Position is impressions-weighted, CTR is clicks/impressions. From the customer’s Search Console. For the QUERIES behind these pages use suparanku_get_search_queries. Empty with connected:false when not connected.

- kind: read-only, idempotent
- input: company_id, brand_id, period, limit, offset
- since: v1.7.0

### `suparanku_get_real_demand`

The real-demand review queue: themes grouped from REAL Google Search queries (90d impressions, from the brand’s own Search Console) that no Market Map cluster covers. This is the only measured demand channel into the Market Map — every other demand signal is inferred from the brand site or from AI answers. Each row carries theme_key (the handle for a verdict), the buying situation, the query type and funnel stage, 90d impressions, up to 5 example queries, and status: "suggested" = still awaiting a verdict, "accepted" = already queued into the coverage plan. Dismissed themes are not returned. Themes are NEVER accepted automatically — search demand is not AI demand, so a human or agent decides: pass the theme_keys to suparanku_set_real_demand_verdict (that tool’s description explains how to judge a theme). An empty list means one of three things and does not distinguish them: Search Console is not connected, there are no impressions yet, or the map already covers everything people search for. Re-read after a verdict to confirm the new status.

- kind: read-only, idempotent
- input: company_id, brand_id
- since: v1.7.0

### `suparanku_get_ai_traffic`

Real visits from AI assistants per own-site page, from the customer’s GA4 — classified by our referrer registry. FLOOR, not ceiling: many AI-assistant visits arrive without a referrer. Returns sessions, AI sessions, engaged sessions and key events with previous-window values, plus url_hash for the per-page drill (suparanku_get_page_traffic_detail). For the split BY assistant and by non-AI channel use suparanku_get_traffic_channels. Empty with connected:false when GA4 is not connected.

- kind: read-only, idempotent
- input: company_id, brand_id, period, limit, offset
- since: v1.7.0

### `suparanku_get_citation_traffic_insights`

The citation×traffic join per page: cited_with_traffic (working — reinforce), cited_no_traffic (citation without clicks), ai_visits_not_cited (engines send visitors to pages never seen cited — hidden demand), not_indexed (Google has not indexed the page, so nothing downstream can work). Requires GA4; lists are empty when not connected.

- kind: read-only, idempotent
- input: company_id, brand_id, period
- since: v1.7.0

### `suparanku_get_search_queries`

The actual searches people typed on Google before reaching the brand’s site, from the customer’s Search Console: query, clicks, impressions, CTR, average position, each with the previous window. This is the only MEASURED demand signal we hold — everything else about demand is inferred from the site or from AI answers. Use min_impressions to cut the long tail and max_position for weak-position triage (e.g. max_position 10 = queries the brand shows up for but never on page one). Search Console reports queries site-wide, not per page, so there is no url here. For themed, uncovered demand use suparanku_get_real_demand. Empty with connected:false when Search Console is not connected.

- kind: read-only, idempotent
- input: company_id, brand_id, period, min_impressions, max_position, contains, sort, limit, offset
- since: v1.15.0

### `suparanku_get_traffic_timeseries`

Day-by-day series for the window (7d|28d|90d): Search Console impressions and clicks, GA4 sessions / AI-assistant sessions / key events / key events from AI assistants, and how many own-site pages were seen cited by AI that day. Use it to see WHEN something moved; the per-page cut is suparanku_get_search_performance / suparanku_get_ai_traffic. Each block is present only for a connected integration — read `connected` before concluding "no traffic".

- kind: read-only, idempotent
- input: company_id, brand_id, period
- since: v1.15.0

### `suparanku_get_traffic_channels`

GA4 sessions for the window split by acquisition bucket: one bucket per AI assistant (ai_engine names the assistant) plus google_organic, other_search, direct, referral, paid and other. Carries engaged sessions, key events, total engagement seconds and the previous window per bucket. This is where you see WHICH assistant sends visitors, and how AI traffic compares with search and direct. AI referral counts are a FLOOR — assistant visits often arrive without a referrer. Empty with connected:false when GA4 is not connected.

- kind: read-only, idempotent
- input: company_id, brand_id, period
- since: v1.15.0

### `suparanku_get_page_traffic_detail`

Everything we hold for ONE own-site page, in publication order: the proof loop (published by us → first indexed → first impressions → first seen cited, with the assistants that cited it), the 28-day Search Console series, the daily channel mix (ai / search / direct / other), totals since publication, and — when the signal is strong enough — an estimate of the direct-traffic lift around the first citation (the "dark traffic" AI sends without a referrer). Pass the page url; url_hash from suparanku_get_search_performance or suparanku_get_site_schema also works. Use it to answer "did publishing this page actually do anything".

- kind: read-only, idempotent
- input: company_id, brand_id, url, url_hash
- since: v1.15.0

### `suparanku_get_google_integration_status`

Whether the brand’s Google Search Console and GA4 are connected, which property each points at, how far the imported data reaches (data_through), whether the historical backfill is still running, and the error code if a connection broke (e.g. the customer revoked access). Call this FIRST when a Google tool returns connected:false or an empty window — it separates "not connected" from "connected but no data yet" from "connection is broken". Note: on the free plan Google data is imported ONCE at connection time and never refreshed, so data_through stops moving there.

- kind: read-only, idempotent
- input: company_id, brand_id
- since: v1.15.0

### `suparanku_get_business_profile`

The full Business Profile — the audited business-understanding document behind prompt generation and recommendations: up to 9 prose sections (identity, role, offers, situations, buyer, self_label, proofs, works_with, from_client) with source-linked facts, plus version + changelog. Read it FIRST when you need business context for planning content or competitive work. NOT the brand card (name/aliases/domains — that is suparanku_list_brands / suparanku_update_brand_profile). brand_id may be the own brand OR a tracked competitor’s brand id (from suparanku_list_competitors) — a competitor’s profile is competitive context. Defaults to the active own brand. `sections` restricts the output; format:"markdown" returns the whole document as prose. Returns profile:null with building:true while the profile is still being built — retry later, don’t error.

- kind: read-only, idempotent
- input: company_id, brand_id, sections, format
- since: v1.10.0

### `suparanku_get_market_map`

The brand’s Market Map — the audited market model: buyer job + category (overview), buyer-language demand clusters, the buyer lexicon (dominant term + synonyms), market vocabulary (lexicon), segments, buying criteria, players, environment, sources. Clusters and lexicon terms carry `monthly_volume` — average monthly search volume in the measurement market (market demand). Semantics: field/`volume_fetched_at` absent = volume was never measured for this snapshot; monthly_volume null WITH a fetched_at = measured, below the reporting threshold (~10/mo); volumes are never zero. `demand.available` says whether the snapshot has any measured volume at all. Own brands only (competitors have no map of their own — for competitor context use suparanku_get_business_profile). `include` picks sections (default overview + buyer_lexicon + clusters + segments; "all" is large — prefer the default unless you need everything). `version` pins a past snapshot from `versions[]`. The text channel is the clusters CSV; format:"markdown" returns the whole map as prose instead.

- kind: read-only, idempotent
- input: company_id, brand_id, version, include, format
- since: v1.10.0


## Tools — write (40)

### `suparanku_create_company`

Create a NEW company (workspace) with its first brand. `free_audit` is REQUIRED and decides what happens after the company exists — the two paths spend different things and are not interchangeable afterwards, so choose deliberately. "run" = the standard free audit, the same one a self-serve signup gets: the whole primary-domain site is crawled, then the business profile, the market map and the starter topics + prompts are built and the ONE free measurement the free plan includes is taken. It is async and takes a while — poll suparanku_get_latest_site_audit_run, then read suparanku_get_business_profile / suparanku_get_market_map / suparanku_list_prompts as each stage lands, and do not write topics or prompts yourself while it runs. "skip" = create the company and STOP: nothing is crawled, nothing is built, the free measurement stays unspent, and you set everything up by hand. Pick "run" for a measured starting point to work from (a demo, an audit for a prospect, a client you want the standard picture of); pick "skip" when you already know the business and want to decide yourself what runs and when. Requires a read+write token whose owner is admin (or owner) on a company with full MCP access, and a remaining company-creation allowance — that allowance is granted per user and is 1 by default. To add a brand to an EXISTING company use suparanku_create_brand instead. Either way the new company starts on the FREE plan, which decides what you can do next: every read tool works, and so do the zero-cost writes that shape the measured set — brand profile, aliases, domains, topics, prompts, competitors, source markup. On-demand cost-bearing work (further site audits, manual scans, content briefs, market research) is not on the free plan; ask Suparanku to raise this company’s plan first, then run suparanku_run_site_fast_audit → suparanku_run_market_research → suparanku_generate_prompts.

- kind: write
- input: free_audit, company_name, brand_name, aliases, domain, description, industry, target_audience, primary_language
- since: v1.15.0

### `suparanku_create_brand`

Create a NEW brand inside an existing company (subject to the company’s max_brands limit — see suparanku_get_company_usage). Does NOT create a new company — use suparanku_create_company for that. Requires admin role. Provide aliases (the only match strings), a domain, and a short business description. No free onboarding audit runs for the new brand: the site is crawled, but the business profile, market map and starter prompts are NOT built. Follow up with suparanku_run_site_fast_audit, suparanku_run_market_research and suparanku_generate_prompts as you need them.

- kind: write
- input: company_id, brand_name, aliases, domain, description, industry, target_audience, primary_language
- since: v1.0.0

### `suparanku_update_brand_profile`

Update a brand’s profile fields (name, description, industry, target_audience, domain, primary_language). Changing primary_language is a METHODOLOGY EVENT: it re-keys matching and bumps the markup version, so metrics before/after are not directly comparable.

- kind: write, idempotent
- input: company_id, brand_id, name, description, industry, target_audience, domain, primary_language
- since: v1.0.0

### `suparanku_update_brand_domains`

Set a brand’s primary domain + secondary domains (these define the "owned" source category).

- kind: write, idempotent
- input: company_id, brand_id, primary, secondary
- since: v1.0.0

### `suparanku_update_brand_aliases`

Replace a brand’s aliases (the match strings). Methodology event — re-keys matching + bumps markup version. Applied to existing answers automatically (async, a few minutes) — no manual scan needed; do NOT spend scan quota on this.

- kind: write, idempotent
- input: company_id, brand_id, aliases
- since: v1.0.0

### `suparanku_create_topic`

Create a topic (theme grouping for prompts) on the active brand.

- kind: write
- input: company_id, brand_id, name, description, color
- since: v1.0.0

### `suparanku_create_topics_bulk`

Create several topics on the active brand in ONE call — use this instead of looping suparanku_create_topic. De-dupes names within the request and SKIPS names that already exist (rather than failing the whole batch). Returns { created, skipped } so you can see which names were new.

- kind: write
- input: company_id, brand_id, items
- since: v1.3.0

### `suparanku_set_topic_color`

Set or clear a topic’s swatch color. Takes a palette id (indigo, teal, coral, amber, violet, emerald, rose, sky, lime, fuchsia, ochre, slate) — NOT a hex/CSS value, which the dashboard cannot resolve and renders gray. Pass null to clear and let the UI auto-pick.

- kind: write, idempotent
- input: company_id, topic_id, color
- since: v1.0.0

### `suparanku_rename_topic`

Rename a topic.

- kind: write, idempotent
- input: company_id, topic_id, name
- since: v1.0.0

### `suparanku_set_topic_metadata`

Set or clear a topic’s description.

- kind: write, idempotent
- input: company_id, topic_id, description
- since: v1.0.0

### `suparanku_delete_topic` (destructive)

Delete a topic. METHODOLOGY EVENT + irreversible markup change: it orphans the topic’s prompts and bumps the markup version, breaking timeline comparability across the change. Requires confirm:true and an allow-destructive token.

- kind: write, destructive, idempotent
- input: company_id, topic_id, confirm
- since: v1.0.0

### `suparanku_create_prompt`

Add a tracked prompt to the active brand (optionally assigned to a topic).

- kind: write
- input: company_id, brand_id, text, topic_id
- since: v1.0.0

### `suparanku_create_prompts_bulk`

Add several tracked prompts to the active brand in ONE call (each optionally assigned to a topic via topic_id) — use this instead of looping suparanku_create_prompt. Does NOT start a scan: run suparanku_run_manual_scan afterwards (spends manual-refresh quota) or wait for the next scheduled scan. Returns the created prompt ids.

- kind: write
- input: company_id, brand_id, items
- since: v1.3.0

### `suparanku_update_prompt`

Update a prompt’s text or topic assignment. To stop tracking a prompt, delete it with suparanku_delete_prompt — pausing (active:false) is not available: it hides the prompt without freeing it transparently.

- kind: write, idempotent
- input: company_id, prompt_id, text, topic_id, active
- since: v1.0.0

### `suparanku_delete_prompt` (destructive)

Delete a prompt. IRREVERSIBLE HARD DELETE: it permanently removes the prompt AND its captured AI answers / scan history (the metrics derived from it disappear). This is the only way to stop tracking a prompt and free its plan slot. Requires confirm:true and an allow-destructive token.

- kind: write, destructive, idempotent
- input: company_id, prompt_id, confirm
- since: v1.0.0

### `suparanku_generate_prompts`

Generate the next batch of tracked prompts for the brand from the demand its Market Map has NOT covered yet — the same one-click generation the app offers on the Prompts page (you do not write the prompts; they are derived from the brand’s market, grouped into topics, and scanned automatically once created). Use this instead of inventing prompts yourself; use suparanku_create_prompts_bulk only for prompts you were explicitly asked to track. ASYNC — returns a batch_id, then poll suparanku_get_prompt_generation_status until the batch is "done". Bounded: one click = one chunk (not the whole gap), a few generations per brand per 24h, and never past the plan’s prompt limit. If no candidate demand is ready, the call queues fresh market research instead (status "researching_demand") — poll, then call again. Check suparanku_get_prompt_generation_status first to see whether generating is possible right now.

- kind: write
- input: company_id, brand_id, count
- since: v1.11.0

### `suparanku_undo_prompt_batch` (destructive)

Revert one batch created by suparanku_generate_prompts: it deletes exactly that batch’s prompts (and their captured AI answers), puts their demand back in the candidate pool, and drops topics the batch created that are left empty. Only a batch still in the review window (status "done") can be undone — poll suparanku_get_prompt_generation_status for the status. IRREVERSIBLE: requires confirm:true and an allow-destructive token. To remove a single prompt instead of the whole batch, use suparanku_delete_prompt.

- kind: write, destructive, idempotent
- input: company_id, brand_id, batch_id, confirm
- since: v1.11.0

### `suparanku_add_competitor`

Mark/track a competitor for the active brand. Provide its name + aliases (match strings). A competitor can have several official sites — pass them in `sites` (first = primary, the only one profiled). `domain` remains accepted for one site. Existing answers are re-processed automatically (async, a few minutes) — the competitor’s past mentions appear on their own; do NOT run suparanku_run_manual_scan for this, it wastes quota.

- kind: write
- input: company_id, brand_id, name, aliases, domain, sites
- since: v1.0.0

### `suparanku_update_competitor`

Update a tracked competitor’s name, aliases, or sites. Pass the full desired site list in `sites` (first = primary, the only one profiled); omit or empty to clear. `domain` remains accepted for a single site. Edits are re-applied to existing answers automatically (async, a few minutes) — no manual scan needed; do NOT spend scan quota on this.

- kind: write, idempotent
- input: company_id, competitor_brand_id, name, aliases, domain, sites
- since: v1.0.0

### `suparanku_delete_competitor` (destructive)

Stop tracking a competitor. This is a SOFT delete (reversible). Requires confirm:true and an allow-destructive token. Metrics update automatically (async, a few minutes) — no manual scan needed.

- kind: write, destructive, idempotent
- input: company_id, competitor_brand_id, confirm
- since: v1.0.0

### `suparanku_add_competitor_alias`

Add one alias (match string) to a tracked competitor. Applied to existing answers automatically (async, a few minutes) — no manual scan needed.

- kind: write, idempotent
- input: company_id, competitor_brand_id, alias
- since: v1.0.0

### `suparanku_dismiss_possible_competitor`

Dismiss an AI-suggested (possible) competitor so it stops being suggested. Pass the canonical_key + display_name from suparanku_list_possible_competitors. Returns `dismissed_id` — keep it to restore the candidate later with suparanku_undismiss_competitor.

- kind: write, idempotent
- input: company_id, brand_id, canonical_key, display_name, aliases, reason
- since: v1.0.0

### `suparanku_undismiss_competitor`

Reverse a dismissal so the brand is suggested as a possible competitor again.

- kind: write, idempotent
- input: company_id, dismissed_id
- since: v1.0.0

### `suparanku_set_source_category`

Override the category of a cited domain (e.g. owned / competitor / media / review). Call suparanku_list_source_categories for the valid `source_type` values and what each means.

- kind: write, idempotent
- input: company_id, brand_id, domain, source_type
- since: v1.0.0

### `suparanku_set_source_category_bulk`

Override categories for several domains at once (the mark-up-everything loop). Call suparanku_list_source_categories for the valid `source_type` values and what each means.

- kind: write, idempotent
- input: company_id, brand_id, items
- since: v1.0.0

### `suparanku_clear_source_override`

Remove a manual category override for a domain (revert to the auto classification).

- kind: write, idempotent
- input: company_id, brand_id, domain
- since: v1.0.0

### `suparanku_track_source`

Pin a specific URL (e.g. a piece you published) so future scans report whether AI cites it. Returns the pin’s `tracked_source_id` — keep it to remove the pin later with suparanku_untrack_source. Pinning an already-pinned URL is safe: the existing pin is returned with `created:false`.

- kind: write
- input: company_id, brand_id, url, label
- since: v1.0.0

### `suparanku_untrack_source`

Remove a pinned tracked-source URL. `tracked_source_id` comes from suparanku_track_source — including for a pin made in the dashboard: call suparanku_track_source with the same URL and it returns the existing pin’s id with `created:false`.

- kind: write, idempotent
- input: company_id, brand_id, tracked_source_id
- since: v1.0.0

### `suparanku_generate_content_brief`

Generate a brief for ONE publication row of a CONTENT card (spends 1 of the company’s monthly content-brief quota — see suparanku_get_company_usage; a failed retry is free). `publication_key`: omit or "own" for the ANCHOR (the own-site piece — always generate it first), or a domain key from the card’s `publications` for an external pair (its brief adapts the ready anchor to that platform+format; gated on the anchor being ready). Then read it with suparanku_get_content_brief (same publication_key). Poll suparanku_get_recommendation for per-row briefStatus.

- kind: write
- input: company_id, brand_id, recommendation_id, publication_key
- since: v1.0.0

### `suparanku_generate_brief_rewrite`

Generate a rewrite section of an existing content brief, for one placement of that brief (spends content-brief quota). `placement_key` is "r" + the placement’s index in the brief’s `placements` — read them with suparanku_get_content_brief and count from 0. Only a "rewrite" or "crosslink" placement can be generated: the one whose role is "original" is the anchor, which has no rewrite and is rejected (generate it with suparanku_generate_content_brief instead). In practice the anchor is first, so "r0" is normally the rejected key and rewrites start at "r1" — check the roles rather than assuming the order.

- kind: write
- input: company_id, brand_id, recommendation_id, placement_key
- since: v1.0.0

### `suparanku_set_recommendation_status`

Move a recommendation through its lifecycle: in_progress / done / dismissed / reopen. For a manual site-audit VERIFY rec (evidence.manual=true — Google/Bing indexing, the og:image visual review), done/reopen also records/clears the underlying manual-check verdict, keeping the Site Audit page and the rec in sync. For the og:image review, first SHOW the image (the rec’s imageUrl) to the user in the chat and get their confirmation that it looks right — only the human can judge the visual; never mark it done unseen.

- kind: write, idempotent
- input: company_id, brand_id, recommendation_id, action
- since: v1.0.0

### `suparanku_mark_recommendation_published`

Attach a published URL for tracking. PER-PUBLICATION mode (preferred): pass `publication_key` (from the card’s `publications`) + `url` — the row flips to published and the URL enters indexing/citation tracking; repeat per row as pieces go live. Reversible without consequences: call again with `url: null` to replace or remove the link (tracking stops, the brief stays). WHOLE-CARD mode (legacy): pass `urls[]` — marks the card done. Card-level closing stays a human decision.

- kind: write, idempotent
- input: company_id, brand_id, recommendation_id, urls, publication_key, url
- since: v1.0.0

### `suparanku_set_publication_status`

Consciously skip ONE publication row of a content card ("won’t publish this pair" — e.g. the YouTube video) or return it to the plan. Skipped rows leave the progress counter and offer no brief; the decision survives re-scans and is fully reversible (status "planned" restores the row with its brief intact). A published row cannot be skipped — remove its URL first via suparanku_mark_recommendation_published with url: null.

- kind: write, idempotent
- input: company_id, brand_id, recommendation_id, publication_key, status
- since: v1.0.0

### `suparanku_run_manual_scan`

Trigger a manual re-scan of specific prompts to fetch FRESH AI answers (spends manual-refresh quota — see suparanku_get_company_usage). Do NOT call this after competitor / alias / brand-markup edits: those edits are re-applied to the already-collected answers automatically within minutes, so re-scanning for them only wastes quota. A scan is only needed when you want genuinely new answers — e.g. newly created prompts that have never run, or measuring change after publishing content. Returns enqueued info; poll progress with suparanku_get_scan_status using the same prompt ids.

- kind: write
- input: company_id, brand_id, prompt_ids
- since: v1.0.0

### `suparanku_generate_report`

Request a PDF visibility report for the brand’s current scan data. Idempotent per data snapshot: if a report for the SAME current data + locale already exists it is returned immediately (already_generated:true) without re-generating. Otherwise generation is enqueued (async) — poll suparanku_get_report until status is "done", then fetch the file with suparanku_get_report_download_url. No quota cost. `locale` sets the report language (defaults to the requesting user’s language).

- kind: write, idempotent
- input: company_id, brand_id, locale
- since: v1.8.0

### `suparanku_run_site_fast_audit`

Trigger a FAST audit of the brand’s whole primary-domain site (no quota cost — soft-throttled). Async — poll suparanku_get_latest_site_audit_run until status is done/partial, then read results via suparanku_get_site_audit / suparanku_get_site_schema and the fixes via suparanku_list_technical_recommendations. AUTONOMOUS FIX LOOP: run this → poll → list technical recs → apply each rec’s inline brief.artifacts → deploy → run this again; fixed checks auto-resolve. For a deeper rendered-page audit of specific pages use suparanku_run_page_deep_audit.

- kind: write
- input: company_id, brand_id
- since: v1.2.0

### `suparanku_run_market_research`

Rebuild the brand’s Market Map — the buyer-language research (category, buyer job, demand clusters with search volumes, segments, criteria, players) that prompt generation and the report read from. Spends 1 of the monthly market-research allowance (see suparanku_get_company_usage); the free plan has none, so it answers UPGRADE_REQUIRED there. Async — poll suparanku_get_market_map for the new version. Run it after a site audit on a company you just created, before suparanku_generate_prompts.

- kind: write
- input: company_id, brand_id
- since: v1.15.0

### `suparanku_run_page_deep_audit`

Trigger a DEEP (rendered-page) audit of specific page URLs. Spends 1 of the monthly deep-page-audit quota PER url (see suparanku_get_company_usage) — the call is rejected if the remaining quota is smaller than the number of urls. Async: results appear in suparanku_get_page_audit once it finishes.

- kind: write
- input: company_id, brand_id, urls
- since: v1.2.0

### `suparanku_set_link_verdict`

Triage a single EXTERNAL outbound link, from either site-wide queue: suparanku_get_unverified_links (no-response / bot-blocked — unproven) or suparanku_get_broken_links (confirmed 4xx/5xx). Three states, and all three are reachable at any time: "ok" = the URL is valid, the probe was wrong (e.g. x.com bot-blocks the crawler, or a site that 404s bots) → it stops counting as broken everywhere, staying visible in that queue’s reviewed tail; "bad" = confirmed dead → folds into link_health and lowers SRPS; null = undecided, wipe the verdict and put the link back to the raw probe result (pending in the review queue). Same call for a per-page link out of suparanku_get_page_outbound_links. Internal links take no verdict. The verdict is a property of the URL, so it applies to EVERY page that links to that href and re-scores them all in the moment. Re-pull suparanku_get_page_outbound_links / suparanku_get_page_audit afterwards to see the recomputed SRPS.

- kind: write, idempotent
- input: company_id, brand_id, href, verdict
- since: v1.5.0

### `suparanku_set_real_demand_verdict`

Triage the §7.1 real-demand candidates from suparanku_get_real_demand — the query themes real Google searches show demand for that no Market Map cluster covers. WHY THIS EXISTS: every other demand signal in Suparanku is inferred (from the brand site, from AI answers); this one is measured, and it is the only channel by which real search behaviour enters the Market Map. WHAT A VERDICT DOES: "accept" puts one probe for that theme at the HEAD of the coverage plan, ahead of everything else, so the next generated prompts measure what AI assistants answer for demand the brand demonstrably already has; "dismiss" retires the theme permanently — it is never proposed again, and the weekly pass stops spending one of its five slots on it. HOW TO DECIDE: accept a theme only when it describes how a BUYER of this brand actually chooses. Search demand is not AI demand — a page that ranks for a definition lookup ("what is X"), for a language or country the brand does not sell to, or for a term unrelated to the buying decision, is traffic, not demand. Those are dismissals. When unsure, dismiss: an accepted junk theme costs a prompt slot at the front of the queue and pollutes the metrics it mints; a dismissed good theme costs nothing but a future re-proposal. Verdicts are final and only apply to themes still awaiting review (status "suggested"). AFTER ACCEPTING: nothing is minted instantly — call suparanku_generate_prompts to turn the accepted themes into tracked prompts (they lead the queue), then re-read suparanku_get_real_demand to confirm the themes now read status "accepted".

- kind: write, idempotent
- input: company_id, brand_id, theme_keys, verdict
- since: v1.12.0


## Glossary

- **visibility**: How often a brand appears in AI assistants’ answers for the tracked prompts, expressed as a percentage (brand mentions ÷ total answers sampled). Higher is better.
- **position**: Where the brand appears within an AI answer when it is mentioned (1 = first). Lower (earlier) is better. Reported as an average over the answers where the brand was mentioned.
- **sentiment**: The tone of how an AI answer talks about a brand, scored from -2 (very negative) to +2 (very positive). Scored per brand: in an answer that compares several products, each brand is judged by the phrases about it, not by the tone of the answer as a whole. Averaged over the answers where the brand was mentioned; 0 means mentioned with nothing evaluative said.
- **sample**: One captured AI answer to a tracked prompt for a given assistant. Several samples per prompt are averaged so the reported numbers are stable rather than reflecting a single run’s randomness.
- **scan**: A measurement event: the tracked prompts are asked across the selected AI assistants and the answers are analysed. Scans run on a schedule and can also be triggered manually; each scan becomes a comparable point on the timeline.
- **prompt**: A question or query that real users might ask an AI assistant, tracked so you can see how the brand shows up in the answers.
- **topic**: A grouping of related prompts (e.g. a product category or use-case) so metrics can be broken down by theme.
- **competitor**: Another brand tracked for comparison. Suparanku reports how often competitors appear in AI answers alongside (or instead of) your brand.
- **possible competitor**: A brand that AI answers surfaced organically and that has not yet been confirmed/marked as a competitor. Review the suggestions and mark the real ones to track them.
- **source**: A website/domain or URL that an AI answer cited. Sources are categorised (e.g. your own site, a competitor’s, media, review/aggregator, etc.) so you can see what AI relies on.
- **content brief**: An actionable specification for a piece of content that addresses a visibility gap — what to write, in what format, against which criteria, and where to publish it. Generated on demand from a content recommendation.
- **recommendation**: A prioritised, concrete action to improve AI visibility. Three kinds: context (improve the data Suparanku tracks), technical (site/markup changes for AI crawlers), and content (what to write).
- **markup version**: A counter that increases whenever you change what is measured (aliases, competitors, prompts, topics, brand language). Metrics from before and after a change are flagged as not directly comparable on the timeline.
- **measurement changed**: A marker between two scans meaning the set of tracked prompts changed in between (prompts added, removed, or moved to another topic). The two readings still stand on their own, but the difference between them mixes a real change with a changed measurement, so it is not a clean before/after.
- **dynamics**: How a metric moves from scan to scan, as opposed to what the latest scan says. Every analytics view reports the latest scan; the dynamics views answer whether a number is rising or falling and whether it moved after work was published.
- **GEO**: Generative Engine Optimization — improving how a brand is represented in the answers of AI assistants (the AI-era analogue of SEO). Sometimes called LLMO (LLM Optimization).

## Changelog

### v1.19.0 — 2026-08-25

- suparanku_track_source and suparanku_dismiss_possible_competitor now return the row they created — `tracked_source_id` and `dismissed_id` respectively. Their undo counterparts (suparanku_untrack_source, suparanku_undismiss_competitor) require those ids, and nothing on the MCP surface had ever returned one, so an agent could pin a URL or dismiss a candidate and then had no way to reverse it. Re-calling either tool for the same target is safe and returns the existing row (track_source reports `created:false`), which is also how to recover the id of a pin or dismissal made in the dashboard. track_source’s output is now flat — `{ok, created, tracked_source_id}` instead of nesting under `created`.
- suparanku_get_prompt_scan_meta no longer returns empty timestamps. Every date in its payload (createdAt, runAt, latestRunAt, and the per-sample dates) serialized as `{}` instead of an ISO string, so an agent could not tell when a run happened; suparanku_get_verbatim_answer was already correct and the two now agree.
- suparanku_generate_brief_rewrite documented an example placement_key of "r0", which is the anchor placement in every observed brief and is always rejected. Both the tool and the field description now explain that the key is the placement’s index, that only rewrite/crosslink placements can be generated, and that the roles should be read rather than the order assumed.
- suparanku_undo_prompt_batch distinguishes "not yet" from "no longer": a batch that is still generating now says to keep polling until it is done, instead of reporting that it is no longer undoable. The done-only contract is unchanged.

### v1.18.0 — 2026-08-24

- suparanku_create_company can now run the free audit, and it makes you say which you want. The new required free_audit field takes "run" or "skip" and has no default: "run" starts the same free audit a self-serve signup gets — the whole primary-domain site is crawled, then the business profile, the market map and the starter topics + prompts are built and the one free measurement the free plan includes is taken — while "skip" is the previous behaviour, create and stop. Until now creating a company over MCP could only skip the audit, so an agent asked to "create this company and run the free audit" had to send the customer through the website instead. The result says which path ran and what to poll for it.
- New suparanku_get_free_audit_progress: the free audit as 5 ordered steps — site crawl, business profile, market research, first measurement, competitor selection — each waiting|running|done with a short detail ("214/508 pages", the research stage, answers per AI assistant), plus progress_percent, free_audit_running and ready. It is the one tool to poll while an audit runs, whether you started it with create_company free_audit:"run" or the customer started it from the website.
- While a free audit is running, write tools for that company are refused. The audit is writing the brand profile, the market map, the topics and the prompts itself, and it fills the plan’s prompt allowance — an edit made alongside it either loses to it or eats the allowance it needs, and there is nothing to merge afterwards. The refusal names what to poll and says plainly that the answer is to wait; every read keeps working throughout, which is how you wait. suparanku_list_companies marks such a company with free_audit_running:true, so you can see it before you try. This applies to any company mid-audit, including one whose audit was started from the website.

### v1.17.1 — 2026-08-23

- Markup-edit tools now say when a scan is NOT needed. suparanku_add_competitor, suparanku_update_competitor, suparanku_add_competitor_alias, suparanku_delete_competitor and suparanku_update_brand_aliases state that their edits are re-applied to the already-collected answers automatically within minutes, and suparanku_run_manual_scan states the inverse: it exists to fetch FRESH answers (new prompts, post-publication measurement), not to "refresh" after markup edits. Behaviour is unchanged — agents were re-scanning after competitor edits and burning manual-refresh quota on measurements that would have appeared on their own.

### v1.17.0 — 2026-08-23

- New suparanku_get_broken_links: the site-wide list of external links that are CONFIRMED dead — a real 4xx/5xx, or a link a review marked bad — deduped by href, widest reach first, with the probe status, the manual verdict and how many pages carry it. Until now an agent could read the unproven queue (suparanku_get_unverified_links) but never the confirmed one, so half the link triage was dashboard-only. Together the two tools cover every external link that needs a decision.
- suparanku_set_link_verdict now says plainly that it takes all three states from either queue: "ok" = the URL is valid and the probe was wrong, "bad" = confirmed dead, null = undecided (wipes the verdict and restores the raw probe result). Behaviour is unchanged; the tool had only ever described the no-response case.
- suparanku_get_site_schema with only_problems:true no longer counts a server-side 3xx redirect node as a problem. A redirect carries no checks, and on a site with a long legacy redirect tail it crowded out the pages that actually fail. Redirect nodes are still in the unfiltered list, marked redirect:true.

### v1.16.0 — 2026-08-21

- Dynamics is now readable over MCP, not just in the dashboard. suparanku_get_visibility_dynamics returns visibility, average position or sentiment scan over scan — you against every tracked competitor, your topics against each other, or per AI assistant, optionally inside a single topic. Until now an agent could read the latest scan and a rough date-window trend, but never "how did this topic move from scan to scan, and who moved with it".
- Every dynamics point says whether the measurement itself changed. When prompts were added, removed or moved between topics between two scans, the point carries measurement_changed with the counts, and a methodology change sets comparable_with_prev=false. A rise across such a step is not a clean result and should not be reported as one — the flag is there so a report can say which steps are trustworthy.
- New suparanku_get_dynamics_summary is the whole "did it work" answer in one call: the four headline numbers with their move since the previous scan, every topic ranked with its change and who leads it, and every tracked brand with its change. It is the natural first call for a progress report.
- New suparanku_get_sources_dynamics: how many distinct domains the assistants pulled from at each scan, how much of that was your own site and how much a competitor’s, split per assistant when you ask for it — plus the domains themselves, flagged new when first cited in the latest scan and gone when they stopped being cited.

### v1.15.0 — 2026-08-21

- You can now create a COMPANY over MCP, not just a brand inside one: suparanku_create_company makes a new workspace with its first brand and stops there — nothing is crawled, no business profile, market map or starter prompts are built. It is the hands-on path: you decide what runs and when. It needs a read+write token whose owner is admin or owner on a company with full MCP access, plus a remaining company-creation allowance (1 per user by default; ask Suparanku to raise it). A token scoped to an explicit company list cannot reach the company it just created — the result says so; use a token scoped to all companies. The new company starts on the free plan with MCP switched on: every read tool works, and so do the zero-cost writes that shape the measured set — brand profile, aliases, domains, topics, prompts, competitors, source markup. Cost-bearing work (site audit, scans, content briefs, market research) stays off until the plan is raised, and the tool says so in its result instead of letting you discover it one failed call later.
- suparanku_run_market_research rebuilds the Market Map — the buyer-language research (category, buyer job, demand clusters with search volumes, segments, criteria, players) that prompt generation and the report read from. Previously this was a dashboard-only button, so an agent that had just created a brand had no way to produce the research its own prompt generation depends on. It spends one of the monthly market-research allowance and is async: poll suparanku_get_market_map for the new version.
- suparanku_create_brand now says plainly what it does NOT do: the new brand gets a site crawl, but no business profile, market map or starter prompts — those are yours to trigger. The tool behaves exactly as before; only the description was wrong about it.
- Google Search Console data is no longer page-only. New suparanku_get_search_queries returns the actual searches people typed — clicks, impressions, CTR, average position, each against the previous window — with min_impressions to cut the tail and max_position for weak-position triage. Until now the query level existed in the product but never left the server: an agent could see which PAGES got impressions, never which QUESTIONS earned them.
- New suparanku_get_traffic_timeseries (day-by-day impressions, clicks, sessions, AI-assistant sessions, key events and cited pages) and suparanku_get_traffic_channels (sessions split per AI assistant and per non-AI channel, with engaged sessions and key events). The per-assistant split previously existed only inside the PDF report.
- New suparanku_get_page_traffic_detail: one page end to end — published → first indexed → first impressions → first cited (and by which assistants), its 28-day search series, its daily channel mix, totals since publication, and the direct-traffic lift estimate around the first citation. This is the "did publishing this actually do anything" answer.
- New suparanku_get_google_integration_status: whether Search Console and GA4 are connected, which property, how far the data reaches, whether the backfill is still running and the error code if a connection broke. Call it when a Google tool answers connected:false or an empty window — it separates "not connected" from "no data yet" from "the connection broke". It also reports whether data refreshes daily: on the free plan Google data is imported once at connect time and never again.
- suparanku_get_indexing_status now returns the full URL Inspection diagnosis (robots state, indexing state, fetch state, the Google vs declared canonical pair, last crawl, first-indexed date) and takes filter:"problems". suparanku_get_search_performance adds CTR, the previous-window position and the index verdict; suparanku_get_ai_traffic adds engaged sessions and previous-window values; both now return url_hash, the handle for the per-page drill. suparanku_get_citation_traffic_insights takes a period and returns the fourth bucket it had always computed and thrown away: not_indexed.

### v1.14.0 — 2026-07-30

- suparanku_create_prompts_bulk no longer starts a scan. It never advertised one, but prompts added this way were in fact scanned a few seconds after creation — so a batch you added to shape the measured set silently produced a round of measurements nobody asked for. Creating prompts now only adds them; they are measured by the next scheduled scan, or immediately if you call suparanku_run_manual_scan (which spends manual-refresh quota and says so). Prompts minted by suparanku_generate_prompts are unaffected — those are still scanned automatically once created, as documented.
- Topic colors take a PALETTE ID, not a hex value. suparanku_create_topic, suparanku_create_topics_bulk and suparanku_set_topic_color previously advertised hex (e.g. "#4F46E5") and accepted anything; the dashboard only resolves the 12 named ids, so every hex-colored topic rendered gray. The field is now one of indigo, teal, coral, amber, violet, emerald, rose, sky, lime, fuchsia, ochre, slate — a wrong value is rejected with the list instead of being stored and shown gray. Omitting the color is still the good default: an unset color gets an auto-picked swatch.

### v1.13.0 — 2026-07-29

- Prompts are now the customer’s data end to end: the nightly sweep that paused “dead” generated probes and replaced them from the coverage plan is GONE, and nothing else changes a prompt, a topic assignment or a prompt’s active flag without an explicit action from the customer or their agent. The two remaining exceptions are onboarding (which builds the starter set the wizard promises) and a downgrade (which pauses prompts over the new tier limit) — both now leave an audit trail. Practical effect for an agent: the prompt set you read is the prompt set the customer chose, and metrics stay comparable unless someone acted.
- The three overlapping prompt-coverage recommendations became ONE ladder — at most a single card is open at a time. prompts.fill_gap (generate N from the market map) while slots and candidates both exist; prompts.plan_upsell (raise the limit) only once the current tier is genuinely exhausted, so it no longer appears to brands that still have free slots; and a new prompts.pool_dry (deepen the market map) when slots are free but no demand cluster is left uncovered. context.topics.few_prompts is retired — it duplicated fill_gap and its quota variant asked for prompts to be switched off with nothing to judge that by.
- New context recommendation context.topics.oversliced: N topics measured on a single prompt each, which makes visibility and share-of-voice unstable for those topics. Its suggested_tools are suparanku_update_prompt (move the prompt) + suparanku_delete_topic (drop the emptied topic) — merging costs no prompt slots.
- suparanku_get_recommendation / suparanku_list_recommendations now carry coverage counters in `evidence` (active, limit, target, reserve, free, pool, thin) on every card of the coverage family, so an agent can decide without a second round trip.

### v1.12.0 — 2026-07-29

- Real-demand themes can now be TRIAGED over MCP, not just read — previously the accept/dismiss buttons were UI-only. New write tool suparanku_set_real_demand_verdict: pass theme_keys from suparanku_get_real_demand plus accept or dismiss. Accepting puts a probe for that theme at the head of the coverage plan (call suparanku_generate_prompts to mint it); dismissing retires the theme permanently and frees a slot in the weekly pass. The whole batch is validated before anything is written, so a wrong theme_key never leaves half the batch decided.
- suparanku_get_real_demand’s description now explains what the queue IS (the only demand channel measured from real Google searches rather than inferred), what each status means, and that an empty list does not distinguish “Search Console not connected” from “nothing uncovered”.
- New context recommendation context.demand.unreviewed — real-demand themes waiting for a verdict now surface in the Growth engine like unmarked sources and untriaged competitors do, with suparanku_get_real_demand + suparanku_set_real_demand_verdict + suparanku_generate_prompts as its suggested_tools. It auto-resolves once the queue is empty.

### v1.11.0 — 2026-07-27

- Prompt GENERATION is now available over MCP — the same one-click flow the Prompts page offers, previously UI-only. New write tool suparanku_generate_prompts: generates the next batch of prompts from the demand the brand’s Market Map has not covered yet (async → batch_id; the new prompts are grouped into topics and queued for scanning automatically). Bounded exactly like the button: one call = one plan-scaled chunk, a few generations per brand per 24h, never past the plan’s prompt limit; with no candidate demand ready it queues fresh market research instead (status "researching_demand") rather than failing.
- New read tool suparanku_get_prompt_generation_status: is a generation (or a market-research pass) running, how much room is left under the prompt limit, how many generations remain today, and — with batch_id — that batch’s outcome (generating|done|failed|undone + inserted count). New write tool suparanku_undo_prompt_batch (destructive, confirm:true): reverts one generated batch — deletes its prompts and their captured answers, returns their demand to the candidate pool, drops the now-empty topics it created.
- The generation gates’ machine codes are translated into self-correcting tool errors (prompt limit reached / daily generation cap / a generation already in flight) instead of surfacing raw RESOURCE_EXHAUSTED text.

### v1.10.0 — 2026-07-27

- New read tool suparanku_get_business_profile: the full Business Profile artifact — up to 9 prose sections (identity, role, offers, situations, buyer, self_label, proofs, works_with, from_client) with source-linked facts, version and changelog, as structured JSON + a markdown rendering. Works for the own brand or a tracked competitor brand id (competitive context). Distinct from the brand-profile resource / suparanku_update_brand_profile, which are the brand CARD (name/aliases/domains).
- New read tool suparanku_get_market_map: the brand’s Market Map — overview (buyer job, category + maturity, how they buy, measurement market), buyer-language demand clusters and the buyer lexicon WITH average monthly search volumes (market demand), market lexicon, segments, criteria, players, environment, sources. Section selection via include, past snapshots via version, clusters as CSV in the text channel, or the whole map as markdown.

### v1.9.0 — 2026-07-26

- suparanku_list_recommendations status semantics: `dismissed` now returns every Skip (“won’t fix”) whose signal is STILL DETECTED by the latest audit/scan — however long ago it was skipped (previously a skip older than one iteration became invisible while still suppressing the recommendation). `done` still means “closed this iteration”; a re-detected done reopens to open at emission time, so it never lingers.
- HTTP 999 (LinkedIn’s anti-bot code) is now a bot-block status alongside 401/403/429: such links land in suparanku_get_unverified_links as verdictable instead of being auto-counted broken.
- suparanku_set_link_verdict: an “ok” verdict is now honored on an EXTERNAL link with a CONFIRMED 4xx/5xx probe status too (a false-positive probe — e.g. a site that 404s bots). The link stops counting as broken in link_health / SRPS everywhere; suparanku_get_page_outbound_links reflects it as effective:"ok".
- Click depth (page.graph_depth / domain.linking_depth) is now measured from each LOCALE home (/{locale}/ = depth 0) as well as the domain root, so non-default-locale pages no longer carry a systematic +1 click. The site-wide domain.linking_depth recommendation is no longer emitted while the page-level issue aggregate is open (it duplicated the same finding); recommendation copy now spells out that click depth is about internal links, not URL nesting.
- Recommendation list tools + suparanku_get_recommendation now render the LOCALIZED card copy for context/content recommendations (render_kind i18n rows previously surfaced the raw signal key as title and a null summary). All list tools accept `locale`; get additionally returns `card_text` {title, summary, detail} and `suggested_tools` — the MCP tools that act on that recommendation (e.g. sources.unmarked → suparanku_get_sources + suparanku_set_source_category).
- suparanku_set_source_category now REJECTS locked domains (the brand’s own domains and tracked-competitor domains) with a clear error instead of storing a silent no-op override — those categories are derived automatically and always win.
- The standalone context.competitors.rising recommendation is retired (it always duplicated context.competitors.unmarked — same candidate pool, same triage surface); accelerating candidates now ride the unmarked card (per-candidate rising flags in evidence.candidates). AI assistants/answer engines (ChatGPT, Claude, Gemini, NotebookLM…) are excluded from competitor candidates everywhere.

### v1.8.0 — 2026-07-17

- New write tool suparanku_generate_report: request a PDF visibility report for the brand’s current scan data — idempotent per data snapshot (an existing report for the same data is returned instead of re-generated; otherwise generation is enqueued, poll suparanku_get_report). New read tool suparanku_get_report_download_url: mint a fresh ~15-minute download URL for a completed report. suparanku_get_report’s description was corrected — it returns metadata + history only, never a download URL.
- New read tool suparanku_get_competitor_topic_gaps: per-topic share-of-voice vs every tracked competitor, with the deficit in percentage points and a beaten flag (≥30pp) — the dashboard’s “Where they beat you” view. Topics where every prompt names the own brand are excluded.
- New read tool suparanku_get_competitor_source_gap: for one tracked competitor, the cited domains verifiably mentioning it while never mentioning you — publication targets, with citation counts and an unverified footnote count.
- suparanku_list_possible_competitors UPGRADED to the FULL potential-competitor inbox (single source of truth): live AI-answers suggestions merged with market-research players/proposal/watchlist and earlier audit candidates, deduped against tracked/dismissed, with sources[] provenance and a confirmed-by-live-answers flag. AI-answers fields (canonicalKey, mentionCount, promptIds, …) keep their previous names; research-only rows carry a canonicalKey fallback and no answer metrics.

### v1.7.0 — 2026-07-12

- Five new read tools over the Google integrations (Search Console + GA4): suparanku_get_indexing_status (per-page Google index verdict + coverage reason), suparanku_get_search_performance (per-page clicks/impressions/position with previous-window values), suparanku_get_real_demand (candidate demand clusters synthesized from real queries the Market Map does not cover), suparanku_get_ai_traffic (per-page visits from AI assistants via the customer’s GA4 — an explicit floor, not a ceiling) and suparanku_get_citation_traffic_insights (the citation×traffic join: cited-with-traffic / cited-no-traffic / AI-visits-not-cited). All five return an empty result with connected:false when the integration is not connected — never an error.

### v1.6.0 — 2026-06-29

- The technical-recommendation `brief` (suparanku_get_recommendation) is now a plain-language explanation (problem, why it matters, desired state, how to fix) plus a concrete developer brief (ordered steps, ready-to-paste artifacts, acceptance criteria, an optional risk note, machineReadable.fix/verify). Internal-only fields are no longer returned anywhere on the MCP surface.
- All recommendation list tools (suparanku_list_recommendations, suparanku_list_context_recommendations / _technical_ / _content_) are now paginated summary lists: they accept limit/offset and return { recommendations, total, offset, limit, has_more } so an agent can page through the whole set without the response getting truncated. Each item is a slim card (id, status, impact/effort, and for a technical issue aggregate scope + affected_count + a small URL sample) — NOT the full payload.
- suparanku_list_technical_recommendations no longer inlines the fix brief (it did since 1.4.0). The full fix brief and the complete evidence.affected[] list now come from suparanku_get_recommendation.
- suparanku_list_recommendations changed shape from a grouped object { context, technical, content } to a flat paginated array with `category` on each item; per-category active totals stay available as `counts`.

### v1.5.0 — 2026-06-28

- New read tools suparanku_get_page_inbound_links and suparanku_get_page_outbound_links: the link graph to/from one page. Outbound links carry status_class (ok / redirect / broken / no_response / not_checked / app_scheme), the no-response manual verdict, and the effective status after that verdict, plus an all | errors | no_response | external filter.
- New write tool suparanku_set_link_verdict: resolve an external no-response link as ok (verified false-positive) or bad (confirmed dead). The verdict is site-wide (applies to every page linking to that href) and re-scores SRPS in the moment.
- New read tool suparanku_get_unverified_links: the site-wide queue of external links that came back no-response or bot-blocked (401/403/429), deduped with their verdict + page count — list them, then resolve each with suparanku_set_link_verdict. External bot-block statuses are now treated like a no-response (verdictable), not auto-counted as broken.
- suparanku_get_site_schema now accepts only_problems:true (return only pages with issues / a non-2xx status / a client-side fake-200 redirect) and documents the perf_score, redirect and client_redirect fields it already returns.

### v1.4.0 — 2026-06-24

- New read tool suparanku_get_latest_site_audit_run: poll the latest site-audit run’s status + page progress (drives the autonomous fix loop after suparanku_run_site_fast_audit).
- suparanku_list_technical_recommendations and suparanku_get_recommendation now return an inline deterministic 7-part fix brief for technical findings (ready-to-paste artifacts + machineReadable fix/verify) and accept an optional locale; content-deep checks return brief.status="needs_generation".

### v1.3.0 — 2026-06-24

- New write tool suparanku_create_topics_bulk: create up to 150 topics on the active brand in one call (de-dupes within the request and skips names that already exist; returns created + skipped).
- New write tool suparanku_create_prompts_bulk: add up to 150 tracked prompts in one call (each optionally assigned to a topic via topic_id) — replaces looping suparanku_create_prompt. Does not start a scan.

### v1.2.0 — 2026-06-23

- New read tools suparanku_get_site_audit, suparanku_get_site_schema, suparanku_get_page_audit: the domain-level site audit (SRPS / SRPS+ + grouped checks), the crawled page tree, and one page’s full audit detail.
- New write tools suparanku_run_site_fast_audit (fast full-site audit, no quota) and suparanku_run_page_deep_audit (deep rendered-page audit of specific URLs, spends the monthly deep-page-audit quota per url).
- suparanku_list_technical_recommendations now accepts an optional audit_scope filter ("site" for domain-level findings or "page:<url>" for one page).

### v1.1.0 — 2026-06-21

- New read tool suparanku_list_source_categories: the full source-category reference (id, label, meaning, settable flag) so agents can mark up unknown domains.
- suparanku_set_source_category(_bulk) now accept the full settable category set (added ecommerce, aggregator, industry_assoc, academic, job) — matching the dashboard.
- New read tool suparanku_list_recommendations: ALL current recommendations (context + technical + content) in one call, grouped by kind.
- Removed the weekly_visibility_report and competitor_gap_analysis workflow prompts (low value; the tools cover the same flows directly).

### v1.0.0 — 2026-06-20

- Initial stable MCP surface — read + write tools ship together.
- List/source reads accept limit/offset and return total + has_more; get_sources adds only_unmarked + category filters and unmarked_count/category_counts.
- New reads get_possible_competitor_evidence + get_competitor_mentions: the prompts/AI answers where a possible or tracked competitor was mentioned.
