# DomainGuard MCP — Operating Guide Endpoint: `https://nhmohio.com/api/mcp` Transport: Streamable HTTP (JSON-RPC 2.0 over `POST`) Auth: `Authorization: Bearer nhm_...` (a DomainGuard API key) Server name: `domainguard` · 262 tools (253 a customer key can be offered: 138 read, 106 write, 9 destructive; plus 9 operator-only `admin` tools) · 9 prompts · 6 resources This file is written for an AI assistant. Point Claude at this URL, paste it into a project's `CLAUDE.md`, or read it through the server itself as the resource `domainguard://guide` — and it will know how to use the server well instead of guessing. The same text the server sends as `instructions` on connect is a condensed version of §3, §4 and §5 below. --- ## 1. What this server is for DomainGuard watches domains and reports on them: DNS, registration, SSL, mail authentication and the DMARC report stream, HTTP security headers, blacklists, uptime, Hijack Guard change detection, lookalike domains, vulnerabilities and open ports, software CVE/KEV alerts, dark-web breach data, on-page and whole-site SEO, audit-grade accessibility, local rankings and the Google Business Profile, reviews, citations, backlinks, AI search visibility, third-party provider status, compliance evidence and incident records. The MCP server hands every part of that to an assistant so it can answer questions about *these* domains rather than websites in general, add and set up a domain by asking the owner a few questions, and — with a write-capable key — run the scans and lookups that produce new data. **Every feature of the product is reachable here.** The only things deliberately left out are credentials, money and sessions: API keys, sign-in, billing, in-app purchase and push-device registration stay dashboard actions. **The one boundary that matters:** DomainGuard monitors these domains, it does not host them. Every tool reports, records, or measures. Nothing here edits a website or publishes a DNS record. The deliverable of a session is a diagnosis plus the exact change to make, executed by the user at their registrar, DNS provider, host, CMS, or Google Business Profile. --- ## 2. Connect ### Claude Code ```bash claude mcp add --transport http domainguard https://nhmohio.com/api/mcp \ --header "Authorization: Bearer nhm_your_key_here" ``` The nine prompts then appear as slash commands: `/mcp__domainguard__audit_domain`, `/mcp__domainguard__onboard_domain`, `/mcp__domainguard__set_up_local_seo`, `/mcp__domainguard__improve_local_seo`, `/mcp__domainguard__review_domain`, `/mcp__domainguard__verify_findings`, `/mcp__domainguard__portfolio_triage`, `/mcp__domainguard__fix_email_deliverability`, `/mcp__domainguard__build_wisp`. ### Claude Desktop Works in every version, through the `mcp-remote` bridge (needs Node.js). In `claude_desktop_config.json` (Settings → Developer → Edit Config): ```json { "mcpServers": { "domainguard": { "command": "npx", "args": ["-y", "mcp-remote", "https://nhmohio.com/api/mcp", "--header", "Authorization:${DG_AUTH}"], "env": { "DG_AUTH": "Bearer nhm_your_key_here" } } } } ``` The header is written without a space after the colon and the key goes in `env` on purpose: some platforms split arguments on spaces. Restart Claude Desktop after saving. ### Cursor `~/.cursor/mcp.json` (every project) or `.cursor/mcp.json` (one project): ```json { "mcpServers": { "domainguard": { "url": "https://nhmohio.com/api/mcp", "headers": { "Authorization": "Bearer nhm_your_key_here" } } } } ``` ### VS Code (Copilot agent mode) `.vscode/mcp.json`. The key is asked for once and kept in VS Code's secret storage, so it never lands in the repository: ```json { "inputs": [ { "type": "promptString", "id": "domainguard-key", "description": "DomainGuard API key (nhm_...)", "password": true } ], "servers": { "domainguard": { "type": "http", "url": "https://nhmohio.com/api/mcp", "headers": { "Authorization": "Bearer ${input:domainguard-key}" } } } } ``` ### OpenAI Codex CLI `~/.codex/config.toml`, with the key in the environment variable it names: ```toml [mcp_servers.domainguard] url = "https://nhmohio.com/api/mcp" bearer_token_env_var = "DOMAINGUARD_API_KEY" ``` ### Anything else Any client that can call a remote MCP server over Streamable HTTP with a custom header works: the URL, and `Authorization: Bearer nhm_...`. A client that only runs local (stdio) servers can use the same `mcp-remote` bridge as Claude Desktop. There is no OAuth flow and no event stream — `GET` is refused by design. The key is created in the DomainGuard dashboard under **Settings → API Keys** (or **Connect an AI agent**, which makes one for you) and is shown exactly once. ### Keep the tool list small: scope the key to the job Every tool a key can reach is described to the model before the first call, and that text sits in its context for the whole session. A key with every scope and `write` is offered 253 tools, about 213 KB of descriptions (roughly 55,000 tokens). Give each assistant only what its job needs: | Key's scopes | Tools offered | Size | |---|---|---| | `seo` (read) | 53 | 44 KB | | `seo` + `write` | 94 | 85 KB | | `domains` (read) | 36 | 29 KB | | `domains` + `write` | 62 | 57 KB | | `vulnscan` + `write` | 51 | 35 KB | | `scans` + `write` | 19 | 16 KB | | every scope, read only | 138 | 106 KB | | every scope + `write` | 253 | 213 KB | A reporting assistant needs no `write`. A local-SEO assistant needs `seo` and `domains` (for `list_domains`). Clients that load tools on demand (Claude Code's tool search) pay less for a large list, but a smaller one still makes the right tool easier to pick. Tool results are compact JSON. ### Verify the connection by hand ```bash curl -s https://nhmohio.com/api/mcp \ -H "Authorization: Bearer nhm_your_key_here" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` The tool list that comes back is the real answer to "what can this key do" — it is already filtered to the key's scopes and the account's plan. Trust it over this document if the two ever disagree. `prompts/list` and `resources/list` return the nine prompts and six resources; `prompts/get` renders one; `resources/read` returns the text. --- ## 3. Ground rules Follow these in every session. They are what separates a useful session from an expensive one. 1. **Start with `list_domains`.** Almost every other tool takes a numeric `domain_id`, and that is where the ids come from. Never guess an id. 2. **Read stored results before running anything.** `get_checkup_history`, `get_seo_results`, `get_vuln_scan_latest`, `get_local_rank_history`, `get_geogrid_scans`, `get_domain_history` and `get_domain_setup_guide` return data already collected — free, instant, and usually enough. Scans take minutes and consume budget. 3. **Know which tools spend.** Anything named `run_*`, `start_*`, `check_*`, `verify_*`, `scan_*`, `fetch_*`, `discover_*` or `assess_*` does real work: it sends traffic to a site, calls a paid data source, or spends credits. So do two innocuous-looking ones — `get_domain_email_security` and `get_scan_ownership_verification` perform live lookups and are classified as writes for that reason. 4. **Quote before you spend.** `get_local_seo_credits` for the balance, `get_local_seo_catalog` for what each action costs and requires, `quote_local_ranks`, `quote_geogrid_scan`, `quote_local_seo_endpoint`, `quote_local_seo_report`, `quote_accessibility_audit` and the `pricing` block of `get_local_seo_quickstart` for the number. Tell the user the cost and get agreement before running a paid action for the first time in a session. 5. **Always send a fresh `request_key` for a paid action.** Reuse it only to retry the exact same request after a timeout. Format: 8–100 characters of letters, digits, `.`, `_`, `-`. Avoid colons — some endpoints reject them. A good pattern: `dg-ranks-example-com-20260829-a7f3`. 6. **After a timeout, recover — do not re-run.** `get_local_seo_operation` with the same `request_key` returns the saved or in-flight result. Running again with a new key charges twice. 7. **Poll, don't wait.** `start_vuln_scan`, `start_site_audit`, `start_deep_seo_job` and `start_accessibility_audit` return an id immediately and finish in the background. Poll `get_vuln_scan_results`, `get_site_audit_status` / `get_site_audit_results`, `get_deep_seo_job`, and `get_accessibility_audit`. Leave real time between polls — a site crawl is minutes, not seconds. 8. **Confirm destructive calls in words first.** `delete_domain` erases a domain's entire history and cannot be undone. Every other `delete_*`, `remove_*`, `untrack_*` and `unsubscribe_*` is also one-way. Name the exact thing and get a yes. 9. **Report findings, then the fix, then who applies it.** The user changes DNS at their registrar, page content in their CMS, and business details in Google Business Profile. Say which one. 10. **Never invent a number — or a name.** Scores, positions, volumes, and credit prices all come from tool output. If a tool did not return it, say it is not measured rather than estimating. The same goes for a business name, a city, a phrase or an email address during onboarding: they are the user's. 11. **Add a domain by interview, not cold.** `plan_domain_onboarding` first, then the questions, then `onboard_domain` once. §4 has the whole protocol. 12. **Verify what you can, and report what the scanner got wrong.** You are the scanner's reviewer. §5 has the whole protocol. 13. **Website content follows Google's rules on AI content.** Google's guidance (https://developers.google.com/search/docs/fundamentals/using-gen-ai-content) and its spam policies set four limits, and they apply to you: - **Never author copy for the user to publish.** Describe the edit ("shorten the title so the service and town come first"); do not write the title, meta description, heading, alt text, page text or structured data value yourself. A `suggested_value` with `suggested_is_draft` true is a draft built from the page: show it as a draft, and say every fact in it must be checked before it goes live. When there is no draft, a person writes it. - **Never mass-produce pages.** Do not suggest or write a page per town, per suburb or per phrase. Many pages made to rank without adding value are scaled content abuse; near-identical city pages are doorway pages. Point the user at the nearest existing page, a service-area page and the Google Business Profile, and suggest a new page only for something the business can fill with its own facts (work done there, photos, reviews, details that differ). A `Templated Pages` finding means pages to merge or rewrite, never more of them. - **Structured data comes from the visible page.** Only facts shown on the page and true of the business; never add a rating, review, price or FAQ the page does not show. A `Structured Data Accuracy` finding is markup that breaks this. Google shows no FAQ or HowTo rich results any more, and no review stars for a business's reviews of itself; do not promise them. - **No SEO myths.** Meta keywords (Google ignores them), keyword density targets, word-count minimums and "exactly one H1" are not Google rules; title and description lengths are guides to avoid truncation, not limits. --- ## 4. Onboarding a domain — the interview This is the protocol behind the `onboard_domain` prompt and the `domainguard://onboarding` resource. The goal: the user names a domain, you ask a few plain questions, and the domain ends up monitored and configured the way they meant — without them learning the product's vocabulary. ``` 1. plan_domain_onboarding { domain } → already_monitored? · account.plan · scan_cleared · local_business.saved · credits · pricing · questions[] (ask, why, known, default, spends, maps_to) · then[] · rules[] 2. Ask the questions, in order, one or two at a time, in your own words. · confirm anything marked `known`; skip what the user already told you · if they don't know the local-business answers: discover_local_seo_details { domain_id } (needs the domain to exist; for a new domain, onboard first with no local_business, then discover, then call onboard_domain again with it) 3. onboard_domain { domain, ...answers } ← once → steps[] (done / skipped / failed, with why) · setup (the guide) · created 4. Tell the user what happened (steps) and what is left (setup.steps), in words. 5. If a local business was saved and the plan allows it: get_local_seo_quickstart { domain_id } → quote pricing → yes → run_local_seo_quickstart { domain_id, business_name, location, keywords, request_key } 6. Later: get_status_subscription_suggestions → update_status_subscriptions; get_domain_setup_guide again once the first scan finishes. ``` The questions, and what each one becomes: | Question | Argument | Default if unanswered | |---|---|---| | Does this domain send email? | `sends_email` | yes — SPF/DKIM/DMARC are checked | | Impostor check, subdomain discovery, one exposure scan at add time? | `lookalikes` `subdomains` `vulnerabilities` | all on | | Queue a WCAG 2.2 AA audit with the add? (5 credits) | `accessibility` | off | | Keep scanning for exposure after the first scan? | `exposure_monitoring` | on, where the plan schedules scans | | Is it a local business, and where? Name, city, storefront or service area, radius, phone, up to 5 phrases | `local_business { business_name, location, is_service_business, radius_miles, street_address, phone, targeting, keywords[] }` | skipped | | An address at the domain for the one-click ownership link | `ownership_email` | passive checks only until a proof exists | | Addresses at the domain to watch in breach data | `dark_web_emails[]` | none | | A group or client to file it under, and notes | `group` `notes` `scan_client_id` | ungrouped | `onboard_domain` spends nothing. It adds the domain (or finds it if already here), starts the add-time checks, patches the per-domain settings, saves the business profile and phrases, sends the ownership link, watches the addresses, and returns the setup guide. Each step reports separately, so a step the plan refused is a sentence the user sees, not a gap the guide reveals later. It is safe to call again with more answers; it only changes what you pass. `get_domain_setup_guide` is the source of truth for what is left. It is computed from real state on every read. Never tell the user a step is done unless the guide says so, and never tick one yourself. `skip_domain_setup_step` records "not for this domain" for an optional step the user has ruled out. Ownership proof is **per domain** (since 2026-10-02): proving a domain clears active scanning (deep and extreme vulnerability profiles, port scans) for that domain only, and a verified sign-in address at a domain clears it too. `get_scan_clearance_status` says which domains are already cleared. The email link (`request_ownership_email_link`, or `ownership_email` during onboarding) is the easiest proof; the DNS/file challenge (`get_scan_ownership_verification` → `verify_scan_ownership`) is the alternative; `request_scan_authorization` is for a client's domain the account cannot prove. `request_ownership_instructions_email` emails the owner all three ways for one domain, to do later from their own desk. ### Setting up local search **Prefer the autopilot.** It reads the whole website (not only the homepage), finds the business's own Google listing by its website or phone, and hands back a full report in one paid call: ``` preview_local_autopilot { domain_id } → free: what the website says (name, phone, address, hours, services, towns, Google link), the saved values, conflicts between them, proposed phrases, exact price confirm the business + phrases with the user; quote proposed total → yes run_local_autopilot { domain_id, business_name, location, keywords, approved_credits, request_key, frequency?, report_emails?, grid_size?, grid_keyword? } → report: Maps top-20 / 3-pack / organic per phrase, competitors, the listing checked field by field against the website (with the value to paste), next steps; emailed ``` If the report has `listing_candidates` and no `listing_name`, the listing could not be confirmed: show the candidates, and `set_local_listing` the one the user picks. After setup: `quote_local_scan` → `run_local_scan` for a new reading, `update_local_scan_settings` to schedule weekly or monthly scans (each emailed), `list_local_scans` / `get_local_scan` for history, `get_local_competitors` for the competitor set. The older quick start below still works, and is the path behind the `set_up_local_seo` prompt and `domainguard://local-seo`. ``` get_local_seo_access → is the suite on this plan? get_local_seo_quickstart { domain_id } → saved business, phrases, homepage suggestions, latest positions, last profile read, balance, pricing discover_local_seo_details { domain_id }→ name, city, address, phone, service-area?, phrases, each with source + confidence — for the user to confirm verify_local_seo_location / search_locations → when the city is unusual or not found quote: pricing.per_phrase × N + pricing.profile → get a yes run_local_seo_quickstart { ..., request_key } → local-pack AND organic position per phrase, plus the Google Business Profile read add_local_keyword_target → more phrases (25 per domain) get_local_seo_catalog → run_local_seo_action → profile_health, citations, competitor_gap, reviews, geogrid … quote each first ``` `save_local_seo_profile` and `add_local_keyword_target` are free. Every measurement spends; say the number first. On `location_not_found`, offer the suggestions the response carries and call again. On a timeout, `get_local_seo_operation` with the same key — never re-run blind. --- ## 5. Verify, then report — you are the scanner's reviewer This is the protocol behind the `review_domain` prompt and the `domainguard://review` resource. DomainGuard's results are claims about the live world, and you can check many of them yourself. **Start with the verification tasks.** DomainGuard tests itself through you: ``` get_verification_tasks { domain_id } ← omit domain_id for the per-domain overview → tasks[]: { finding_key, kind: component | exposure | check, host, claim, claimed_value, how_we_tested, how_to_verify[{ step, command }], compare, last_verification } … run each step yourself: DNS lookups, HTTPS GET/HEAD, a TLS handshake, a service greeting. Read the output; never assume it matched … submit_verification_results { domain_id, agent: "your name and version", results: [{ finding_key, verdict: confirmed | incorrect | could_not_verify, observed_value, method, evidence }], missed: [{ title, detail, evidence }] ← real problems DomainGuard did not report } ``` Send confirmations as well as disputes; they are how DomainGuard measures its own accuracy per test method. An `incorrect` verdict needs `observed_value` or `evidence`. It files the scanner report and queues the finding for re-check, so don't also call `report_scanner_issue` for it. It never changes the result by itself. Tasks you verified in the last 7 days drop off while the claim stays the same (`include_verified: true` brings them back). 60 results and 5 missed per call, 400 a day. What decides a verdict: - **A version**: what the site itself states (a plugin's changelog or the `?ver=` on its own files, a theme's `style.css`, a service greeting, a device's `/status`). A bundled library prints its own version first (`2.7.0-wc.8.4.0`). - **A CVE**: the version inside the published affected range. Read both bounds: "8.8.0 to 8.9.2" does not cover 8.4.0 even when a feed shortens it to "< 8.9.3". A distribution build (`Ubuntu-4ubuntu0.11`) back-ports fixes, so check the distro tracker first, or answer `could_not_verify`. - **A DNS, mail, header or certificate finding**: the record, header or certificate as served, and whether the rating follows from it. Rules of engagement: only this account's domains and the hosts the tasks name. Read-only and passive. Never log in, submit forms, brute-force, fuzz or try an exploit; a version plus the published range is the whole proof. The full method, sources and licences: `domainguard://verification` or https://nhmohio.com/mcp/verify.md. The prompt `verify_findings` runs the whole loop. **Other mistakes** (not on the task list: an SEO count, a wrong date in a history row, a check that threw) go to `report_scanner_issue`: ``` report_scanner_issue { domain_id, category: false_positive | false_negative | wrong_value | error | other, severity: low | medium | high (how much the wrong answer misleads), check_name: "DMARC Record" (the scanner's own words), title: one line — what was wrong, detail: what it reported versus what is actually true, evidence: the record you resolved, the header you saw, the count you made, suggested_fix: what DomainGuard should change } … keep reading the domain, keep reporting … submit_scanner_review { domain_id } ← once, when you finish the domain ``` Reports for one domain collect into a review; `submit_scanner_review` sends the operators one message. Rules: report only what you verified, never a hunch. Plan limits, 402s and missing scopes are not scanner mistakes. Do not soften or hide a finding because you reported it — tell the user what the scanner said **and** what you found. Pro and Enterprise; 30 reports a day, 10 per domain, 10 per review; a repeated title inside a week is counted, not re-sent. If the tool answers 402 or 403, the account cannot report, so carry on without it. Reports and verdicts are accepted from the account's own API keys and nothing else (a dashboard session cannot file one). A read-only key is enough: they are records about DomainGuard's scanner and change none of the account's data. That is by design: the channel pages a human, so it stays tied to a key the owner minted and can revoke. --- ## 6. Tool map — by the job you are doing ### Audit a domain — everything, interpreted, in one call `audit_domain { domain_id }` reads every stored result (registration, DNS, SPF/DKIM/DMARC and the report stream, certificates on every host and TLS port, the security checkup, the vulnerability scan, software with CVE and CISA-KEV matches, open ports, uptime, Hijack Guard events, lookalikes, breached addresses, on-page and whole-site SEO, the WCAG audit) and returns a `verdict`, `findings` ranked by real risk across every area (`priority` 1 first; 25 by default, `limit` for more) — each with the measured `evidence`, `where` (the exact DNS name and record type, header and URL, host:port, page and CSS selector; `where.hosts` when one finding covers several hosts), a concrete `fix` and `fix_owner` — plus `not_measured` and `summary_markdown`. The same problem seen by two scanners, or on five hosts, is one finding, so do not re-sort or re-merge them. Email fixes are the record to publish for the step the domain is on: DMARC p=none with the account's report address first, p=quarantine only after two weeks of reports at 95% passing. Free; starts no scan. Show the markdown to a person; act on the JSON. Anything under `not_measured` is unknown, not clean: offer the tool it names. Every finding's `source.tool` is the tool with the full data. The prompt `audit_domain` drives it. Also: `get_domain_certificates` · `get_subdomain_sightings` · `get_domain_vuln_state` · `get_exploited_software` · `list_on_demand_scans` · `get_on_demand_scan` · `get_verification_summary`. ### Scan any domain, and share the report (the app's Scan tab) `run_on_demand_scan` (quick by default; full is one an hour) → `get_on_demand_scan` → `share_on_demand_scan` for a public link the user can send their IT provider (`unshare_on_demand_scan` revokes it) · `list_on_demand_scans` · `delete_on_demand_scan`. Works on domains the account does not monitor; on a monitored one a full scan is `run_domain_scan`. Tell the user a shared link is public until they stop sharing it. ### Add and set up a domain `plan_domain_onboarding` → `onboard_domain` → `get_domain_setup_guide` · `skip_domain_setup_step` · `add_domain` · `add_domains_bulk` · `discover_local_seo_details` · `get_local_seo_quickstart` → `run_local_seo_quickstart` ### When things run `get_domain_schedule { domain_id }` lists every automatic check (uptime every 5 minutes, the security and DNS check on the account's scan frequency, certificates daily, on-page SEO weekly on Pro and Enterprise and monthly on Starter, the vulnerability scan when "keep scanning" is on, impostor domains, the local search report, the SEO email) with its next run. Without `domain_id` it describes a domain about to be added. Say times in the user's zone. ### How fast and how well the website works `run_site_check { domain_id }` loads the homepage (three clean loads, the middle one reported) and two more pages in a real browser as a mid-range phone on 4G, then `list_site_checks` (poll until `completed`; two to four minutes) and `get_site_check { check_id }`. The speed score is the homepage's, on Lighthouse's mobile curves (FCP, Speed Index, LCP, TBT, CLS); it is close to PageSpeed Insights but not the same number, because we apply the throttling to a real load where PageSpeed Insights simulates it. Findings come in six sections (speed, mobile, seo, security, accessibility, best_practices), each with `detail`, `cause`, `fix`, `evidence` and the pages it was seen on. Lead with high severity and the biggest `savings_ms`/`savings_bytes`, quote the cause in plain words, and give the fix as steps. `get_site_check` returns the worst 30 findings; pass `detail: true` for all of them. `list_site_checks` is the trend: say "got slower since " only when two or more checks agree. A `status` of `skipped` (the domain redirects to a different website) or `unreachable` (the site did not load) is a result about the site, not an error: say so, and do not offer to retry. Only `failed` means the tester gave up. Scheduled checks run weekly on Starter and daily on Pro and Enterprise. ### Find out what is on the account `list_domains` · `get_domain_history` · `get_dns_change_events` · `get_domain_events` · `get_domain_hijack_events` · `get_domain_event` · `get_uptime_snapshots` · `get_downtime_events` · `list_alerts` · `get_alert_activity` · `list_domain_groups` · `list_scan_clients` ### Find out what is wrong with a site `get_checkup_history` (richest single source) · `get_seo_results` · `get_vuln_scan_latest` · `get_open_ports` · `get_observed_components` · `list_software_vendors` · `get_software_vendor` · `list_software_alerts` · `list_lookalikes` · `get_lookalike_events` · `get_vulnerability_assets` ### Search performance — on-page and site-wide `get_seo_results` · `get_seo_history` · `run_seo_scan` · `start_site_audit` → `get_site_audit_status` → `get_site_audit_results` · `get_seo_intent_conflicts` · `fetch_sitemap` → `start_deep_seo_job` → `get_deep_seo_job` → `get_deep_seo_scans` → `get_deep_seo_scan_pages` · `check_page_seo` · `assess_website` · `score_page_performance` · `score_page_accessibility` · `get_seo_policy` · `email_seo_report` · `get_report_branding` · `save_report_branding` ### Keyword gap — what competitors rank for `quote_keyword_gap` → `run_keyword_gap` → `get_keyword_gap` · `list_keyword_gap_runs` · `get_keyword_gap_competitors` · `add_keyword_gap_competitor` · `set_keyword_gap_competitor` · `remove_keyword_gap_competitor` · `update_keyword_gap_settings` Use this when someone asks what keywords their competitors rank for, what pages to write next, or what to bid on in Google Ads. Read `get_keyword_gap` first; if `run` is null, quote, get a yes, and run. Each row's `bucket` is where the site stands (`gap`, `striking`, `weak_page`, `competing`, `page_one`, `winning`, `brand`, `off_topic`) and `action` is the step; `target_url` is the page that should rank, or the suggested path of a new page when `target_is_new`. Read the summary's `notes` before trusting positions: without Search Console they come only from the shortlisted search pages. Rows with `ad_candidate` are grouped by `ad_group` (the landing page) in `summary.ad_groups`. ### Google's own numbers (Search Console) `list_search_console_sites` → `get_search_console_performance` · `get_search_console_opportunities` · `inspect_url_index_status` These read the owner's Google Search Console through a read-only Google connection the owner makes in the dashboard or the app; you cannot connect it for them. A 409 `not_connected` means they have not connected it yet, and `reconnect_required` means Google withdrew the grant: tell them to connect again from the SEO page. A 404 `no_property` means none of their Search Console properties covers the domain. Google's data runs two to three days behind, so the default window ends three days ago; say so when someone asks about yesterday. CTR is a fraction (0.034 is 3.4%), and position is an average where 1 is the top. URL inspection is capped at 2,000 a day per property by Google, so inspect the pages the question is about, not a whole sitemap. ### Tell search engines a page changed (IndexNow) `get_indexnow_status` → `configure_indexnow` → (owner uploads the key file) → `verify_indexnow_key` → `submit_urls_to_indexnow` or `sync_sitemap_to_indexnow` → `list_indexnow_urls` · `list_indexnow_submissions` IndexNow tells Bing, Yandex, Seznam, Naver, Yep, the Internet Archive and Amazon that a URL changed. Google does not take part, so do not tell the user it reaches Google. The site has to serve a key file first: `get_indexnow_status` names it (`key_file.url`, holding exactly `key_file.content`); nothing is sent for a host whose file does not check out. Each engine gets its own request, and every URL row keeps each engine's last answer, so "was this sent to Bing, and what did it say?" is `list_indexnow_urls` with `engine: "bing"`. A 202 means received, key not checked yet; it is not a failure. Send only pages that changed; for a whole site use `sync_sitemap_to_indexnow`, which sends only what is new or has a newer ``. Each sitemap read also sends pages again to any engine that refused them an hour or more earlier (`retried`, `retry_pending`). Bing refuses Cloudflare's shared network, so the server sends to Bing from its own relay; a `rate_limited` from Bing is retried on the next read, so say so rather than calling it an error. ### Can search engines and AI assistants read the site? (crawler access) `get_search_visibility_overview` → `get_crawler_access` → `check_crawler_access` → `list_crawler_access_history` ChatGPT, Claude and Perplexity take no URL submissions and no IndexNow. They find pages with their own crawlers (OAI-SearchBot, Claude-SearchBot, PerplexityBot), fetch a page live when someone asks (ChatGPT-User, Claude-User), and lean on search indexes (Bing for ChatGPT). So "get my site into ChatGPT" means: the crawlers can get in, and the search engines have the pages. Start with `get_search_visibility_overview`, which lists every domain's IndexNow setup, last send and crawler access, with `attention` saying what is wrong. `get_crawler_access` gives, per crawler, the robots.txt rule that decided and what the site answered our request made with that crawler's user agent. That request comes from our address, not the crawler's, so `edge: ok` is a good sign, not proof; `edge: unknown` means the site did not answer a browser either. Blocking the training crawlers (GPTBot, ClaudeBot, Google-Extended, Applebot-Extended) does not hide a site from search or AI answers; do not report it as a problem unless the owner wants every bot allowed. Domains with IndexNow on are checked daily, and a search or AI crawler that loses access goes into the morning summary. `get_seo_intent_conflicts` reads the newest crawl and says which pages compete for one search — same subject, same intent, same place — and what to do about each pair: which page to keep, and whether to redirect, merge, differentiate or split the other. It is free and instant; it fetches nothing. **Every finding says where it is.** `get_seo_results`, `run_seo_scan` and `get_site_audit_results` return `action_items`: one item per thing to change, worst first. Each has `page`, `selector` (CSS) and `xpath` for the element, `line` and `column` in the served HTML, `snippet` (the markup there now), `found` versus `expected`, `fix` (the corrected markup to paste, or the `robots.txt` line to add), `cms_hint` (where the setting lives in WordPress, Shopify, Wix, Squarespace, Webflow or Ghost, when the page says which one built it) and `priority` (1 = fix first). The same fields sit on each check as `locations[]`, `found`, `expected`, `fix`, `cmsHint` and `priority`, and on each whole-site audit issue as `evidence`. All of it is read from the HTML the scanner fetched, before JavaScript runs: a `null` means not measured, never "nothing to fix". Quote the selector and line when you tell the user what to change; do not invent a location for an item that has none. Rows stored before 2026-09-26 have no locations; `run_seo_scan` or a new audit fills them in. **`check_page_seo` returns findings, not HTML.** It fetches the one page and runs the same engine as a domain's stored scan, plus robots.txt, the sitemap and the http-to-https redirect: `scores`, `counts`, `findings` (problems worst first, each with `how_to_fix`, `suggested`, `cms_hint` and up to three sample `locations`), `passed_checks` (names only) and a capped `action_items` list. `assess_website`, `score_page_accessibility`, `score_page_performance`, `get_seo_results`, `get_domain_history`, `get_uptime_snapshots`, `list_lookalikes`, `list_alerts`, `list_software_alerts`, `get_software_overview`, `list_software_vendors` and `search_software_catalog` are compact the same way (problems or top rows first, totals counted, nothing dropped silently) and take `detail: true` for the full body; the list tools also take `limit`. **Fixes are written for the page.** `how_to_fix` quotes what is on the page now, says what to change, and ends with the admin screen when `cms_hint` names one. When the scan could build a value from the page it is in `suggested_value` (a title, a meta description, an og:image address, a label for an unnamed button or link, a JSON-LD block), with `suggested_basis` saying what it came from and `suggested_is_draft` true when it was composed from the page's own text: show a draft to the user as a draft and let them edit it before it goes live. A `cms_hint.paste` holds the same value for the setting it names. When the page gave nothing to build from, `suggested_value` is null and `how_to_fix` says a person has to write it; do not fill the gap yourself. Items arrive ordered by real impact (noindex, robots.txt, HTTPS and canonical problems first), and stored rows for retired checks (Color Contrast, Inline Styles) are not listed. ### Local search — being found in a place `get_local_seo_brief` (start here) · `autofill_local_seo` / `get_local_autopilot` · `preview_local_autopilot` → `run_local_autopilot` · `quote_local_scan` → `run_local_scan` · `list_local_scans` · `get_local_scan` · `email_local_scan` · `get_local_competitors` · `add_local_competitor` · `remove_local_competitor` · `update_local_competitor` · `update_local_scan_settings` · `set_local_listing` · `get_local_seo_access` · `get_local_seo_quickstart` · `run_local_seo_quickstart` · `discover_local_seo_details` · `verify_local_seo_location` · `search_locations` · `list_covered_locations` · `get_local_seo_profile` · `save_local_seo_profile` · `get_local_keyword_targets` · `add_local_keyword_target` · `delete_local_keyword_target` · `get_local_keyword_ideas` · `get_local_rank_history` · `get_rank_tracker` · `set_rank_tracker` · `quote_local_ranks` · `check_local_ranks` · `get_geogrid_scans` · `get_geogrid_entitlement` · `quote_geogrid_scan` · `run_geogrid_scan` · `get_backlink_snapshots` · `get_local_seo_audit_history` · `list_local_seo_reports` · `quote_local_seo_report` ### Everything else local — 33 catalogued actions `get_local_seo_catalog` → `quote_local_seo_endpoint` → `run_local_seo_action` → `get_local_seo_operation` · `get_local_seo_credits` Covers reviews, reputation, citations, keyword research, competitor intelligence, backlinks, brand mentions, AI search visibility, and full audits. See §7. ### Email that actually reaches inboxes `get_domain_email_security` · `get_dmarc_setup_status` · `get_dmarc_summary` · `get_dmarc_diagnostics` · `get_dmarc_senders` · `list_dmarc_reports` · `get_dmarc_inbox` · `rotate_dmarc_inbox` · `get_managed_email_auth` · `enable_managed_email_auth` · `check_managed_email_auth` · `set_managed_dmarc_policy` · `disable_managed_email_auth` ### Security and trust `run_domain_checkup` · `get_checkup_history` · `start_vuln_scan` · `get_vuln_scan_results` · `get_vuln_scan_history` · `discover_domain_subdomains` · `get_scan_clearance_status` · `get_scan_ownership_verification` · `verify_scan_ownership` · `verify_scan_ownership_bulk` · `request_ownership_email_link` · `request_ownership_instructions_email` · `request_scan_authorization` · `get_scan_authorization` · `list_scan_authorizations` · `list_vuln_dismissals` · `dismiss_vuln_finding` · `restore_vuln_dismissal` · `get_unreadable_versions` · `declare_software_version` · `remove_declared_software_version` · `recheck_host_software` · `list_ip_targets` · `add_ip_target` · `update_ip_target` · `delete_ip_target` · `list_ip_target_scans` · `scan_ip_target` ### Software vulnerabilities (KEV / CVE) `get_software_alert_summary` · `list_software_alerts` · `update_software_alert` · `list_tracked_software` · `search_software_catalog` · `get_software_catalog` · `get_software_catalog_categories` · `track_software` · `update_tracked_software` · `untrack_software` · `watch_all_detected_software` · `get_software_overview` · `get_software_advisory_history` · `get_vulnerability_feed` ### Lookalike domains `list_lookalikes` · `get_lookalike_events` · `run_lookalike_scan` · `set_lookalike_verdict` · `set_lookalike_watch` · `get_lookalike_evidence` · `act_on_lookalike_event` · `get_lookalike_alert_settings` · `update_lookalike_alert_settings` ### Hijack Guard — change events `get_domain_events` · `get_domain_hijack_events` · `get_domain_event` · `act_on_domain_event` (`authorize` = ours, promotes the baseline; `deny` = not ours, stays flagged; `acknowledge` / `investigate` / `resolve` move the state) ### Dark web `get_dark_web_status` · `get_dark_web_breach` · `run_dark_web_scan` · `add_dark_web_email` · `remove_dark_web_email` ### Accessibility (audit grade) `quote_accessibility_audit` · `start_accessibility_audit` · `get_accessibility_audit` · `list_accessibility_audits` · `record_accessibility_manual_review` · `complete_accessibility_audit` · `export_accessibility_audit` ### Uptime and dependencies `get_status_services` · `get_status_incidents` · `get_status_history` · `list_status_subscriptions` · `get_status_subscription_suggestions` · `update_status_subscriptions` · `subscribe_all_status_services` · `unsubscribe_all_status_services` · `suggest_status_service` ### Compliance evidence `get_compliance_records` · `update_compliance_item` · `initialize_compliance_records` · `bulk_update_compliance` ### Written information security plan (WISP) — build it by interview `list_wisps` · `start_wisp` · `get_wisp_interview` · `answer_wisp_questions` · `build_wisp` · `get_wisp_download_link` · `delete_wisp` When the user asks for a WISP, a security plan, or help with the FTC Safeguards Rule or IRS Publication 4557/5708: `list_wisps` first and continue a draft rather than start another; otherwise `start_wisp`. DomainGuard holds the questionnaire. Ask the `next_questions` it returns in order, one or two at a time, confirming any `suggested` value; save each reply with `answer_wisp_questions` (it returns what was refused and why, plus the next questions); repeat until `progress.complete`. Then `build_wisp` returns `download_url`, a Word document the user can open for 7 days without signing in, and `open_items`: controls the plan records as missing, each with its fix and due date. Read those to the user. Never invent an answer; a skipped optional question is saved as `null`. Prompt `build_wisp`; resource `domainguard://wisp`. ### Account plumbing `get_account_settings` · `update_account_settings` · `mark_alerts_read` · `list_domain_groups` · `create_domain_group` · `assign_domains_to_group` · `delete_domain_group` · `rename_domain_group` · `list_scan_clients` · `create_scan_client` · `delete_scan_client` · `update_domain` · `delete_domain` · `run_domain_scan` · `list_support_conversations` · `get_support_conversation` · `reply_to_support_conversation` · `update_support_conversation` To file a domain under a client, pass `scan_client_id` to `update_domain` (or `null` to take it off). Groups and clients do different jobs: a group is the category (for example Clients, Prospects, Personal, Business), a client is the customer the domain belongs to, so one client can hold several domains. ### Suggest a feature `suggest_feature` · `list_my_feature_suggestions` When the user wants DomainGuard to do something it can't, offer to send it to us with `suggest_feature`: confirm the one-line `title` with them, and put why it matters in `details`. Call `list_my_feature_suggestions` first so the same idea is not sent twice. The user sees in Alerts when we plan it, start it, ship it or decline it. A read-only key can send one; 10 a day. `mark_alerts_read` with no arguments marks every open alert on the account as read; with `ids` only those. Read alerts cannot be reopened — the History log keeps the record — so ask before clearing an inbox you did not create. ### Check our work and tell us when we are wrong `get_verification_tasks` · `submit_verification_results` · `report_scanner_issue` · `submit_scanner_review` — §5. ### Operator reads — administrator only `list_error_events` · `get_self_check_status` · `list_scan_failures` · `list_scanner_reports` · `get_verification_stats` · `list_feature_requests` · `update_feature_request` · `domainguard_waitlist_launch_email` · `monthly_report_email` These need a key carrying the `admin` scope, which only the administrator's own signed-in session can mint and which reaches nothing else. They are what the nightly fix routine reads: the Worker's error log grouped by fingerprint, every self-check invariant's last verdict, the scans that did not finish, and the scanner mistakes customers' assistants reported. A customer key never sees them in `tools/list`. --- ## 7. The Local SEO catalog `get_local_seo_catalog` returns the server's authoritative list of 33 actions, each with its exact required inputs, prerequisites, price basis, and whether a saved result can be reopened for free. **Call it before `run_local_seo_action`.** The catalog is the contract; this document is a summary that can go stale. ``` run_local_seo_action { action_id: "competitor_gap", // from the catalog request_key: "dg-compgap-20260829-x1", // fresh, 8-100 chars, [A-Za-z0-9._-] input: { domain_id: 12 } // exactly the catalog's documented fields } ``` Available `action_id` values, by area: | Area | Actions | |---|---| | Profile & reputation | `profile_health` `business_profile` `reviews` `review_velocity` `multi_platform_reviews` `reputation_audit` | | Rankings & maps | `rank_check` `geogrid` `serp_local_finder` `serp_maps` | | Keywords | `keyword_search_volume` `keyword_opportunities` `keywords_for_site` `keywords_related` `keyword_trends` | | AI search | `ai_visibility` `serp_ai_mode` `ai_mentions` `ai_top_pages` `ai_top_sources` `ai_compare` `ai_keyword_data` `ai_llm_response` `ai_scraper` | | Authority & links | `backlinks_summary` `backlinks_gap` `local_authority` `brand_mentions` | | Competition | `competitor_gap` `brand_mentions` | | Audits | `local_audit` `citation_audit` `citations` `page_audit` | Some actions cache: reopening a saved result is free, refreshing buys a new one. The catalog says which, per action; `list_local_seo_reports` and `quote_local_seo_report` show what is already saved. Prefer a saved result unless the user specifically wants current data. ### `rank_check` deserves a note `check_local_ranks` is the quick path and measures the **local pack only**. `run_local_seo_quickstart` measures the local pack **and** organic for up to five phrases and reads the profile in one go. The catalogued action measures whatever you ask and costs accordingly — **one credit per phrase per surface**: ``` run_local_seo_action { action_id: "rank_check", request_key: "dg-ranks-20260829-b2", input: { domain_id: 12, surfaces: ["local_pack", "organic"], // both, or just one keywords: ["emergency plumber akron"] // omit to check every saved phrase } } ``` Omitting `keywords` checks every saved phrase, so on a domain with 20 tracked phrases and both surfaces that is 40 credits — `quote_local_ranks` first, and narrow `keywords` when the user asked about one phrase. --- ## 8. Playbooks Each playbook is a sequence that produces something the user can act on. Run the free reads first in every one of them. The prompts the server offers are these playbooks packaged: `audit_domain` (§6), `onboard_domain` (§4), `set_up_local_seo` (§4), `improve_local_seo` (§4), `review_domain` (§5), `verify_findings` (§5), `portfolio_triage` (8.1), `fix_email_deliverability` (8.10), `build_wisp` (6, Compliance evidence). ### 8.1 First session — triage the whole portfolio ``` list_domains → for each domain: audit_domain { limit: 8 } (what is wrong, already ranked and de-duplicated, with the fix, the owner and what is not measured) get_domain_history (is this getting worse or has it always been so?) get_domain_events (anything hijack-shaped across the account) get_downtime_events (was anything actually down) list_alerts · get_software_alert_summary · get_dark_web_status ``` Output: one ranked list across the portfolio — a critical or actively exploited finding first, then how many domains share the finding, then severity. Fixing one misconfiguration that appears on nine domains beats fixing nine unrelated things. Name the domains whose setup guide still has required steps open. Spend nothing in this playbook. Everything above is a stored read. ### 8.2 Make a page rank — the technical loop ``` 1. get_seo_results → title, meta description, H1/heading structure, accessibility violations that also hurt crawling 2. get_checkup_history → HTTPS, redirects, response headers, exposed files 3. get_seo_history → has the score been moving, and in which direction 4. → hand the user a specific edit list 5. (after they publish) run_seo_scan 6. get_seo_results again → confirm the finding cleared 7. get_seo_history → confirm the number moved ``` Read the SEO result for what it names, not for its score. A missing meta description, a duplicated title across templates, an empty H1, or a canonical naming the wrong page is a concrete edit with an owner. "Score is 71" is not. Describe each text edit; do not write the new title or description yourself (§3 rule 13). ### 8.3 Whole site, not one page ``` start_site_audit { domain_id, max_pages } → returns audit_id get_site_audit_status { audit_id } → poll, minutes not seconds get_site_audit_results { audit_id } → page-level and site-wide findings ``` Or, for a chosen set of pages: `fetch_sitemap` → `start_deep_seo_job` → `get_deep_seo_job` → `get_deep_seo_scans` → `get_deep_seo_scan_pages`. Then cluster the findings before reporting them. A crawl of 300 pages does not produce 300 problems — it produces perhaps six template defects repeated 300 times. Report the templates. Sort by pages affected × severity, and name the template file or page type where you can infer it. A deep-SEO crawl also compares pages with each other when it finishes. A `Templated Pages` finding on a page lists the group whose main text is mostly the same as its own (the city page copied per town). Report it first among the content findings: the fix is to rewrite each page around its own facts or merge them into one page with 301 redirects, never to add more pages. ### 8.4 Get into the local map pack ``` Setup (once per domain) → §4 "Setting up local search" Measure list_local_scans / get_local_scan → free; the last full report and its trend quote_local_scan → run_local_scan → Maps top 20, 3-pack, organic, listing, competitors get_local_rank_history → free; is there recent data? quote_local_ranks / get_local_seo_credits check_local_ranks { request_key } → local pack, every tracked phrase quote_geogrid_scan → run_geogrid_scan { keyword, grid_size, request_key } Diagnose run_local_seo_action profile_health → what the business profile is missing run_local_seo_action citations → name/address/phone consistency run_local_seo_action competitor_gap → who is beating you and on what run_local_seo_action reviews → recent reviews and unanswered ones ``` How to read a geogrid: a business ranks best at its own pin and decays outward. A grid that is strong at the centre and dead three miles out is mostly proximity, which Google weighs and no page can change. What helps is a complete profile with the service area set, real photos, reviews from customers in those areas and consistent citations; not more keywords, and not a page per town unless the business has real, specific material for one (§3 rule 13). A grid that is weak *everywhere including at the pin* is a profile or trust problem — go to `profile_health` and `citations` first. ### 8.5 Find phrases worth targeting ``` get_local_keyword_ideas → what earlier research already found (free) run_local_seo_action keyword_opportunities → local searches this business should consider run_local_seo_action keyword_search_volume → is there real demand run_local_seo_action keywords_for_site → what this domain already associates with run_local_seo_action keywords_related → adjacent phrases run_local_seo_action keyword_trends → is interest rising or dying → add_local_keyword_target for the survivors → check_local_ranks to establish a baseline before any work starts ``` ### 8.6 Beat a specific competitor ``` run_local_seo_action competitor_gap · backlinks_gap · local_authority ``` Turn the gap into three things the user can do this month, not thirty they cannot. ### 8.7 Be visible in AI answers ``` run_local_seo_action ai_visibility · serp_ai_mode · ai_mentions · ai_top_sources · ai_top_pages · ai_compare ``` The actionable output is almost always the *cited sources*, not the answer text. ### 8.8 Reputation ``` run_local_seo_action reviews · review_velocity · multi_platform_reviews · reputation_audit ``` Unanswered reviews are the cheapest win in local search. ### 8.9 Links and mentions ``` get_backlink_snapshots (free) → run_local_seo_action backlinks_summary · backlinks_gap · brand_mentions ``` ### 8.10 Email that reaches the inbox ``` get_domain_email_security { domain_id } → live SPF, DKIM, DMARC, MX, MTA-STS, TLS-RPT + records.spf.recommended / records.dmarc.recommended get_dmarc_setup_status { domain } → the authoritative setup checklist get_dmarc_inbox → the rua= address to publish get_dmarc_summary { domain } → 30-day volume and alignment rate get_dmarc_senders { domain } → every source sending as the domain, aligned or not get_dmarc_diagnostics { domain } → ranked diagnostics list_dmarc_reports { domain } → the raw reports when a number needs checking ``` Read `get_dmarc_senders` before recommending a policy change. Moving a domain to `p=reject` while a legitimate sender is failing alignment stops that sender's mail. Every recommendation shows two records: `p=reject` first, labelled most secure, and `p=none` second, labelled safest start. If the user would rather DomainGuard publish and maintain the records: `enable_managed_email_auth` → user publishes one CNAME → `check_managed_email_auth` → later `set_managed_dmarc_policy advance` when the senders align. ### 8.11 Security findings that touch search ``` audit_domain → start here: ranked, de-duplicated, stored and free get_checkup_history → the raw checkup rows behind it (failures and warnings) run_domain_checkup { domain } → live; also works on a hostname you do not own, in which case only passive checks run get_vuln_scan_latest → most recent completed scan get_scan_clearance_status → which domains are cleared for active scanning? start_vuln_scan → quick | standard | port-scan | deep | extreme get_vuln_scan_results { scan_id } list_software_alerts → KEV/CVE alerts with an exposure verdict ``` Active scanning (`deep`, `extreme`, port scans) requires that the domain itself is proven: `request_ownership_email_link` is the easiest way. ### 8.12 Prove the work worked ``` get_seo_history · get_local_rank_history · get_geogrid_scans · get_domain_history · get_uptime_snapshots + get_downtime_events ``` Compare against the baseline, cite the dates, and say plainly where nothing moved. --- ## 9. Scopes, plans, and what a key can reach A key carries resource scopes plus, optionally, `write`. | Scope | Unlocks | |---|---| | `domains` | inventory, onboarding, setup guide, history, Hijack Guard, uptime, DMARC, alerts, settings, groups, clients, support threads | | `scans` | domain scans, security checkups, dark-web monitoring, scanner reports | | `seo` | on-page SEO, site audits, deep crawls, single-URL scoring, reports, the entire Local SEO suite | | `accessibility` | audit-grade WCAG 2.2 AA evaluations and their manual review | | `vulnscan` | vulnerability scans, ports, components, vendors, software alerts, dismissals, lookalikes, clearance, IP targets | | `compliance` | PCI / HIPAA / cyber-insurance control records, incident records | | `status` | provider status and subscriptions | | `write` | required on top of the above for anything that changes state | | `admin` | the four operator reads; mintable only from the administrator's own session | Plan gating: - **Free / Starter** — no API or MCP access. - **Pro** — API and MCP access, read only: every `R` tool the key's scopes cover. - **Enterprise** — adds `write`: every customer tool. The `admin` reads are the operator's alone. The plan is re-read on every request. An account that drops from Enterprise to Pro loses `write` on its next call, without anyone rotating the key. `tools/list` only returns tools the key can actually call, so a read-only key never sees a write tool. If an expected tool is missing, the key is missing a scope — that is a dashboard fix, not a prompt fix. --- ## 10. Errors and what to do about them | What you see | What it means | Do this | |---|---|---| | HTTP 401 | Key missing, malformed, revoked, expired, or the plan has no API access | Ask the user to check Settings → API Keys and their plan | | HTTP 401 mentioning "requires a DomainGuard API key" | A dashboard session token was used | MCP requires an `nhm_` key; sessions are refused by design | | HTTP 403 "Forbidden origin" | Origin header failed validation | Client misconfiguration, not a credential problem | | HTTP 405 on an authenticated GET | Correct — there is no SSE stream | Use POST | | HTTP 403 "Error 1010: Access denied" (HTML or problem+json from Cloudflare) | Python's built-in `urllib` user agent is on Cloudflare's block list; the request never reached DomainGuard | Send any other `User-Agent` (the MCP SDKs, `requests`, `httpx`, Node and curl are all fine) | | HTTP 429 | Key's hourly rate limit, or a per-plan manual-scan limit | Wait; on free plans manual SEO scans are one per day | | Tool result with `isError`, "missing the … scope" | Key lacks that resource scope | Mint a key with the scope; do not retry | | Tool result with `isError`, "read-only" | Tool needs `write`, which is Enterprise | Use the read equivalent, or ask the user to upgrade | | Tool result 402 `plan_upgrade_required` | The feature is not on the plan | Say which plan has it; do not retry | | Tool result 403 on `report_scanner_issue` or `submit_verification_results` | Not one of the account's own API keys, or it lacks `scans` (reports) / `domains` (verdicts) | Carry on without reporting | | `idempotency_mismatch` (409) | This `request_key` was already used for a *different* request | Generate a fresh key for a genuinely new run | | `location_not_found` (400) with `suggestions` | The city is not in the rank-data gazetteer as written | Offer the suggestions, or `search_locations`, then call again | | Timeout on a paid action | Unknown whether it ran | `get_local_seo_operation { request_key }` — never re-run blind | | Scan returns immediately with no findings | It is asynchronous | Poll the matching results tool | | "requires ownership verification" / scan clearance | Active scanning needs that domain proven | `request_ownership_email_link`, or `get_scan_ownership_verification` → user publishes → `verify_scan_ownership` | | "Requires a saved business profile" | Local action needs profile first | `save_local_seo_profile` or `run_local_seo_quickstart` | | `onboard_domain` step `failed` | That one step was refused; the others applied | Read `detail`, tell the user, fix the input — do not repeat the whole call blind | A failed tool call comes back as a normal result carrying `isError: true` and a readable reason — read the reason and adapt rather than retrying the same call. --- ## 11. Full tool reference `R` = read, safe and free. `W` = write: needs the `write` scope, and does real work. `D` = destructive and irreversible. Inputs marked `?` are optional. The descriptions here are the first sentences of what `tools/list` returns; the tool list itself is always the fuller and more current text. Compliance statuses, for `update_compliance_item` and `bulk_update_compliance`: `unanswered` (nobody has answered yet), `met`, `partial`, `not_met` (assessed and failing — a finding, not a blank), `na` (does not apply). Never write `not_met` to mean you have not asked. ### Scope `domains` — 60 tools | Tool | | Inputs | What it does | |---|---|---|---| | `list_domains` | R | `detail?` | List every domain on this account, one compact row each: id, health score, last scan time and scan state, SSL and registration expiry, SEO score, group, which checks are paused, and vulnerabilities (level, counts, each affected host with its top CVE) — plus the plan's domain limit. Call this first in almost any session — the numeric ids it returns are what every other domain tool takes. `detail: true` returns the full stored scan for every domain (roughly 15 KB each); prefer get_domain_history or get_checkup_history for one domain. | | `add_domain` | W | `domain`, `onboarding?` | Start monitoring a new domain. Call this when the user names a domain that list_domains does not already return. | | `update_domain` | W | `domain_id`, `domain_group?`, `scan_client_id?`, `notes?`, `ignore_mail?`, `ignore_autoscan?`, `exposure_monitoring?`, `uptime_monitoring?`, `website_watch?` | Change the settings on one monitored domain: its group, its client, notes, and whether mail checks, auto-scanning, uptime or Website Watch are skipped. Only the fields you pass are changed. | | `delete_domain` | W D | `domain_id` | Permanently remove a domain and its entire scan history from the account. This cannot be undone and the history cannot be recovered by re-adding the domain. | | `get_domain_history` | R | `domain_id` | Return the recorded scan results for one domain over time. Call this when the user asks whether something regressed, when a problem started, or how a score has moved. | | `run_site_check` | W | `domain_id` | Load the domain's homepage (three times, the middle run reported) and two more pages in a real browser as a mid-range phone on 4G and record what makes them slow, broken on a phone, weak for search or exposed. Queued; poll list_site_checks, then read it with get_site_check. A domain that redirects elsewhere or does not answer returns `id: null` with status `skipped` or `unreachable`. | | `list_site_checks` | R | `domain_id`, `limit?`, `detail?` | A domain's site checks newest first, one short row each: status, the six section scores and the homepage's phone metrics (LCP, FCP, TBT, CLS, server time, page weight), the latest speed score and how far it moved. Default 10 checks. The trend line. | | `get_site_check` | R | `check_id`, `detail?` | One site check: scores, the homepage's metrics (with Speed Index and how many loads they are the middle of), each page's score, and the worst 30 findings with what is wrong, the cause, the fix, evidence and pages. `detail: true` returns everything. | | `get_domain_email_security` | W | `domain_id` | Return the current SPF, DKIM, DMARC, MX, MTA-STS, and TLS reporting evidence for one monitored domain. Use this instead of reducing email security to a single score. | | `discover_domain_subdomains` | W | `domain_id` | Passively discover public subdomains for one monitored domain from Certificate Transparency and DNS. This does not port-scan or probe the discovered hosts. | | `get_dns_change_events` | R | — | Return recorded DNS changes across the account so a caller can distinguish a new change from a long-standing finding. | | `get_domain_events` | R | — | Return Hijack Guard events across the account, including severity and lifecycle state for DNS, registrar, certificate, and hosting changes. | | `get_uptime_snapshots` | R | — | Return recent uptime probe observations for the monitored portfolio. | | `get_downtime_events` | R | — | Return recorded website downtime intervals and their recovery state across the monitored portfolio. | | `get_dmarc_summary` | R | `domain_id` or `domain` | Return the 30-day aggregate-report volume and alignment rate for one monitored domain. | | `get_dmarc_diagnostics` | R | `domain_id` or `domain` | Join the latest DNS scan, 30 days of aggregate reports, and sender attribution for one monitored domain into ranked diagnostics. | | `get_dmarc_setup_status` | R | `domain_id` or `domain` | Return the server-authoritative SPF, DKIM, DMARC, report-arrival, and inbox-routing setup checklist for one monitored domain. | | `mark_alerts_read` | W | `ids?` | Mark the account's alerts as read (done). With no ids, every open alert is marked at once; with ids, only those. | | `plan_domain_onboarding` | R | `domain?` | Call this FIRST whenever the user wants to add or set up a domain. | | `onboard_domain` | W | `domain`, `sends_email?`, `lookalikes?`, `subdomains?`, `vulnerabilities?`, `accessibility?`, `exposure_monitoring?`, `group?`, `notes?`, `scan_client_id?`, `local_business?`, `ownership_email?`, `dark_web_emails?` | The one write that puts a domain on the account fully configured, from the answers plan_domain_onboarding told you to collect. | | `get_domain_setup_guide` | R | `domain_id` | The server-computed checklist for one domain: first scan, alerts, email authentication, DMARC reports, ownership proof, monitoring, local business, lookalikes, vulnerability scan, exposure monitoring — each done, todo, running, blocked by plan, skipped or not applicable, with how to finish it. | | `skip_domain_setup_step` | W | `domain_id`, `step`, `skipped?` | Record that a setup step does not apply to this domain (or un-skip it). Only optional steps can be skipped; the required ones cannot. | | `add_domains_bulk` | W | `domains` | Add up to 100 domains in one call. Returns which were added and which were skipped (already here, invalid). | | `list_alerts` | R | `limit?`, `before?` | The alert inbox: every notification the account was sent, newest first, with the domain, event type, where it was delivered (in-app feed and push; there is no alert email), and whether it has been read. Read it when the user asks "what have you told me about" or before mark_alerts_read. | | `get_alert_activity` | R | `limit?`, `before?` | The History log: alerts, scans, changes and events across the account merged into one timeline. Broader than list_alerts, which is only what was sent. | | `get_account_settings` | R | — | The account's alert and scanning settings: which alerts are on, how often domains are scanned, and the thresholds. Alerts arrive in the account's alert feed and as a push on its iPhone. Read before changing anything so you change only what the user asked. | | `update_account_settings` | W | `scan_frequency?`, `notify_expiry_days?`, `notify_ssl_days?`, `security_threshold?`, `notify_expiry?`, `notify_ssl_expiry?`, `notify_dns_issues?`, `notify_security_issues?`, `notify_dns_record_changes?`, `notify_status_push?`, `notify_uptime_push?`, `notify_dmarc_alerts?`, `notify_scan_complete?`, `notify_scan_results_email?` | Change the account's alert and scan settings. Pass only the fields the user asked to change; everything else is kept. Alerts go to the feed and the iPhone; there is no alert email. | | `list_domain_groups` | R | — | The group labels used to organise the portfolio (clients, brands, regions) with how many domains sit in each. | | `create_domain_group` | W | `name` | Create an empty group label. Assigning domains to it is assign_domains_to_group; update_domain also takes a group name. | | `delete_domain_group` | W D | `group_id` | Remove a group label. The domains in it are kept and become ungrouped. | | `rename_domain_group` | W | `group_id`, `name` | Give an existing group a new name. The domains in it stay in it. | | `recheck_host_software` | W | `domain_id`, `host` | "Check again" for one host: re-read its software and versions and re-match advisories. Limited per host per day. | | `assign_domains_to_group` | W | `domain_ids`, `group_name?` | Put up to 50 domains under one group name in a single call, creating the group if needed. Pass an empty group_name to ungroup them. | | `list_scan_clients` | R | — | Clients are the optional grouping a consultancy uses to hold domains and network targets per customer. Returns each with its id. | | `create_scan_client` | W | `name` | Create a client record to assign domains and IP targets to. Names are unique per account. | | `delete_scan_client` | W D | `client_id` | Remove a client record. Domains and targets assigned to it are kept, unassigned. | | `suggest_feature` | R | `title`, `details?` | Send the DomainGuard team an idea for something the product should do. The user sees in Alerts when we plan it, build it, ship it or decline it. Read-only keys can send it; 10 a day. | | `list_my_feature_suggestions` | R | — | The account's own suggestions, newest first, with their status and any note from us. | | `get_domain_hijack_events` | R | `domain_id` | Hijack Guard events for one domain: DNS, registrar, certificate and hosting changes with severity and lifecycle. get_domain_events is the account-wide view. | | `get_domain_event` | R | `event_id` | One Hijack Guard event in full: what changed, from what to what, when, and what has been done about it. | | `act_on_domain_event` | W | `event_id`, `action` | Move a Hijack Guard event through its lifecycle. authorize means "we made this change" and promotes it to the new baseline so it stops alerting; deny means "we did not" and keeps it flagged. | | `list_dmarc_reports` | R | `domain?`, `limit?`, `offset?` | The aggregate reports received, newest first, optionally for one monitored domain. Each names the reporter, the window, the published policy and the message counts. | | `get_dmarc_senders` | R | `domain`, `days?` | Sender attribution from the aggregate reports: each sending source, its volume, and whether it passed SPF and DKIM alignment. The list to read before tightening a DMARC policy. | | `get_dmarc_inbox` | R | — | The rua= address DomainGuard receives aggregate reports at for this account, whether it is provisioned, and any delivery error. This is the address to put in the DMARC record. | | `rotate_dmarc_inbox` | W | — | Issue a new report address and retire the old one. Every domain's DMARC record then has to be updated, so confirm with the user; there is a cooldown between rotations. | | `get_managed_email_auth` | R | — | Which domains have delegated their DMARC or SPF record to DomainGuard to publish and maintain, and whether the delegation is live. | | `enable_managed_email_auth` | W | `domain`, `service`, `source_record?` | Start managing one record: returns the CNAME the user publishes (for DMARC) or takes the SPF record to flatten and serve. After they publish, check_managed_email_auth confirms it. | | `disable_managed_email_auth` | W | `domain`, `service` | End a delegation. The user must publish their own record again or the domain will have none. | | `check_managed_email_auth` | W | `domain` | Look up DNS to confirm the delegation CNAME is published and serving. A live lookup; call it after the user says they published. | | `set_managed_dmarc_policy` | W | `domain`, `action` | For a managed DMARC record: advance moves the policy one step towards reject (none → quarantine → reject) when the reports show every legitimate sender aligned; revert steps it back. | | `list_support_conversations` | R | — | The account's support threads with NHM, newest first, with status. | | `get_support_conversation` | R | `conversation_id` | One support thread in full, including replies from NHM. | | `reply_to_support_conversation` | W | `conversation_id`, `message` | Add the user's message to an existing support thread. Opening a new thread needs the dashboard; say so if there is none. | | `update_support_conversation` | W | `conversation_id`, `status?`, `archived?` | Set a thread's status to open or closed, or archive/unarchive it. | | `get_domain_schedule` | R | `domain_id?` | Free. Every automatic check for a domain, how often it runs, whether it is on, and when it last ran and runs next. Without domain_id, what would run on a domain about to be added. | | `get_verification_tasks` | R | `domain_id?`, `kind?`, `limit?`, `include_verified?` | Every claim DomainGuard makes about a domain as a task: the claim, how it was tested, read-only commands that reproduce it, and what to compare. Without `domain_id`, the domains with open counts. | | `submit_verification_results` | R | `domain_id`, `agent?`, `results[]`, `missed[]` | Your verdict per task (confirmed, incorrect, could_not_verify) with what you saw and how, plus problems DomainGuard missed. Records only; a read-only key can send it. | | `audit_domain` | R | `domain_id`, `limit?` | Free. Everything stored for one domain, interpreted in one call: `verdict` (critical / at_risk / needs_attention / healthy / not_scanned_yet, headline, counts, scores), `findings` ranked by risk (`priority`, duplicates merged, `where.hosts` for several hosts) with the measured `evidence`, `where` (DNS name and record type, header and URL, host:port, page and CSS selector), `fix` and `fix_owner`, `categories`, `not_measured` (never read these as clean), `next_steps` and `summary_markdown`. Each part is held to the key's scopes. | | `get_domain_certificates` | R | `domain_id` | Free. Every TLS certificate the domain serves (site, www, watched subdomains, TLS ports) as last read from the server: expiry, issuer, names, key, chain trust, hostname match, protocols, OCSP and each problem with its remediation. `current: false` means the reading is over 7 days old. | | `get_subdomain_sightings` | R | `domain_id`, `since_days?`, `limit?` | Free. Every subdomain seen in certificate logs or DNS probes, newest first, with `first_seen_at` and `source`. `baseline: true` hosts already existed when watching began, so their date is not when they appeared. `audit_domain` reports non-baseline hosts from the last 14 days as a change. | | `get_domain_vuln_state` | R | `domain_id` | Free. The domain's vulnerability verdict (confirmed, possible, clear, unchecked), each affected host with every product, version, top CVE and fix version, and `host_software`: every product read on every host. Unchecked is not clean. | | `get_verification_summary` | R | `days?` | Free. Verdicts sent with submit_verification_results over the window (confirmed, incorrect, could not verify), the agents that sent them and the disputed findings. | ### Scope `scans` — 19 tools | Tool | | Inputs | What it does | |---|---|---|---| | `run_domain_scan` | W | `domain_id` | Trigger an immediate domain-health scan — DNS, registration, inferred certificate, security checkup, and single-page SEO. | | `get_checkup_history` | R | `domain_id`, `detail?` | Return the newest security checkup (its failing and warning checks, passes counted) and the score trend of earlier ones; `detail: true` returns every check of every checkup. The raw list; `audit_domain` ranks and de-duplicates. This was the richest source of "what is actually wrong with this site" — prefer it over a fresh checkup when recent data exists. | | `run_domain_checkup` | W | `domain` | Run a live security checkup. For a domain on this account this includes the exposed-file probe, which makes about 100 requests to the site. | | `get_dark_web_status` | R | — | Cached dark-web results without a new upstream lookup: breaches per monitored address (XposedOrNot), and infostealer exposure: `emails[].infostealer.infections` are stealer logs in XposedOrNot's results for that address (`recentBreaches[].breachType` = `StealerLogs`); `infostealers.domains` has per-domain counts only when the optional Hudson Rock source is on. Never passwords. | | `run_dark_web_scan` | W | — | Refresh every monitored email address against the breach index. This spends the account manual-scan allowance and may be rate limited. | | `report_scanner_issue` | R | `domain_id`, `category`, `severity?`, `check_name?`, `title`, `detail`, `evidence?`, `suggested_fix?` | Tell DomainGuard's operators that a scan result is wrong. Records only; a read-only key can send it. | | `submit_scanner_review` | R | `domain_id` | Close the review you built with report_scanner_issue for one domain and send it to DomainGuard's operators as one message. Call it once, when you have finished reading that domain's results. | | `get_dark_web_breach` | R | `breach` | Details of one breach: what was exposed, when, how many records, the breached site and industry. `breach` is the `breachID` under `recentBreaches` in get_dark_web_status. `monitoredAddresses` lists each of this account's monitored addresses that appeared in it (masked, with its `emailHash`). | | `add_dark_web_email` | W | `email`, `cadence?` | Watch one address in breach data. Only addresses at the account's own domains or its verified sign-in address; cadence weekly and daily need a paid plan. | | `remove_dark_web_email` | W D | `email_hash` | Remove one monitored address by its hash, as get_dark_web_status lists it. | | `get_unreadable_versions` | R | — | Software a scan found on your hosts but could not read a version from (appliances such as UniFi OS Server, SonicWall or Fortinet print none), with any version the owner has entered. Advisories for these stay "possible" until a version is known. | | `declare_software_version` | W | `host`, `product`, `version` | Record the version shown on the device's own settings page for a host + product from get_unreadable_versions. Advisories are re-checked against it immediately. A version a scan reads later always replaces it. Only ask the owner; never guess a version. | | `list_on_demand_scans` | R | `domain_id?`, `domain?`, `limit?` | Free. Saved quick and full scans from the app's Scan tab, newest first, with score, grade and counts, and when the next full scan is allowed. | | `get_on_demand_scan` | R | `scan_id` | Free. One saved on-demand scan with its whole report. | | `run_on_demand_scan` | W | `domain`, `mode?` | Scan any public domain now and save it: quick (passive, default) or full (active probes and SEO, one per hour). Returns the finished report. | | `share_on_demand_scan` | W | `scan_id` | A public link to a completed scan (`url`), with private file paths hidden from visitors. Asking again returns the same link. | | `unshare_on_demand_scan` | W | `scan_id` | Revoke a scan's public link. | | `delete_on_demand_scan` | W D | `scan_id` | Hide a finished scan from the history and revoke its link. It still counts toward the hourly full-scan limit. | | `remove_declared_software_version` | W | `host`, `product` | Withdraw a version entered with declare_software_version; its advisories are re-judged at once. | ### Scope `seo` — 75 tools | Tool | | Inputs | What it does | |---|---|---|---| | `get_seo_results` | R | `domain_id` | Return the most recent SEO and accessibility scan for one monitored domain: page titles, meta descriptions, heading structure, and accessibility violations. Call this when the user asks what to fix on their site for search. | | `get_seo_history` | R | `domain_id` | Return SEO scores for one monitored domain over time, to show whether changes actually moved the number. | | `run_seo_scan` | W | `domain_id` | Trigger a fresh SEO and accessibility scan of one monitored domain. Free-plan accounts are limited to one manual scan per day and will get a 429. | | `start_site_audit` | W | `domain_id`, `max_pages?` | Start a persisted crawl of a monitored site, following its sitemap or same-site links and checking every discovered page for SEO defects. Poll get_site_audit_status and then read get_site_audit_results. | | `get_site_audit_status` | R | `audit_id` | Poll a whole-site SEO audit by the audit id returned by start_site_audit. Check `truncated`: when it is true the site has more pages than the audit opened, `pagesDiscovered` says how many were seen, and the findings describe only the pages in `pagesChecked`. | | `get_seo_intent_conflicts` | R | `domain_id`, `limit?`, `detail?` | Read the newest crawl of a monitored domain and report which pages compete for the same search: pairs with one subject, one intent (informational, commercial, transactional, navigational) and one place, each with the page to keep and the steps to change the other (redirect, merge, differentiate with a proposed title, split). Capped at `limit` (default 25, max 200) highest severity first, with totals; `detail: true` for the whole report. Check `crawl.completed_at` and open both pages before acting. | | `get_site_audit_results` | R | `audit_id` | Return the findings from a whole-site SEO audit: per-page checks plus site-wide ones made when the crawl finishes (broken internal links, redirect chains and loops, orphan and deep pages, dead ends, thin and duplicate content, templated pages, canonical conflicts). Orphan and deep-page findings appear only when the audit covered the whole site. A `truncated` result covers only `pagesChecked` of the `pagesDiscovered` pages on the site, so absence of a finding is not evidence the rest are clean. | | `get_indexnow_status` | R | `domain_id` | Whether IndexNow is on for the domain, its key and the key file the site must serve, whether that file checked out on each host, the engines selected, automatic sitemap submission, the last sitemap read, totals by engine and by answer, and the five most recent sends. | | `list_indexnow_urls` | R | `domain_id`, `engine?`, `result?`, `q?`, `limit?`, `offset?` | Every URL the domain has sent through IndexNow: `lastmod` (when the page changed), first and last sent, how many times, `last_result`, and each engine's last answer with time and HTTP status. | | `list_indexnow_submissions` | R | `domain_id`, `limit?`, `offset?` | The send log: source, URL count, engines, each engine's HTTP status and meaning, outcome, and the first 20 URLs. | | `configure_indexnow` | W | `domain_id`, `enabled?`, `engines?`, `auto_submit?`, `sitemap_url?`, `key?`, `key_location?`, `regenerate_key?` | Turn IndexNow on (creates the key) or change which engines, daily automatic sitemap submission, the sitemap address, or use the site's own key. Only the fields passed change. | | `verify_indexnow_key` | W | `domain_id`, `host?` | Fetch the key file from the domain's hosts now and say, per host, whether it holds exactly the key. URLs on a failing host are not sent. | | `submit_urls_to_indexnow` | W | `domain_id`, `urls`, `lastmod?`, `engines?` | Send up to 1,000 changed URLs on the domain to each selected engine and record every answer (200 accepted, 202 pending, 403 key not found, 422 wrong host, 429 slow down). 10,000 URLs a day per domain. | | `list_search_console_sites` | R | `refresh?` | The Search Console properties on the owner's connected Google account (`sc-domain:` Domain properties and URL-prefix ones), the domains each is matched to, and the domains no property matched. | | `get_search_console_performance` | R | `domain_id`, `start_date?`, `end_date?`, `dimension?`, `limit?`, `refresh?` | Clicks, impressions, CTR and average position for a domain: totals, a daily series, and the top queries or pages. Default window is the 28 days ending three days ago, from a daily snapshot; other ranges are read live and cached for an hour. | | `inspect_url_index_status` | R | `domain_id`, `url` | Google's URL Inspection result for one page: verdict, coverage state, robots.txt and fetch state, last crawl, Google's chosen canonical against the page's own, sitemaps, referring URLs, mobile and rich-result verdicts. 2,000 a day per property; a repeat within an hour is cached. | | `get_search_console_opportunities` | R | `domain_id`, `min_impressions?`, `limit?` | Queries at average position 8-20 with at least `min_impressions` impressions, most impressions first, each marked with whether it is a tracked keyword and our latest organic rank check. | | `get_keyword_gap` | R | `domain_id`, `bucket?`, `ads_only?`, `limit?`, `offset?`, `run_id?` | The latest keyword gap run: a summary (rows per bucket, each competitor's shared and gap keywords, ad groups by landing page, notes on what could not be read) and rows most important first, each with bucket, priority, volume, cost per click, intent, our position and page, the page to rank, competitors, advertisers, "People also ask" questions and the action. | | `quote_keyword_gap` | R | `domain_id`, `shortlist?` | The worst-case price of a run, the balance, the competitors it would use, whether Search Console is connected and how many crawled pages it compares against. | | `run_keyword_gap` | W | `domain_id`, `approved_credits`, `request_key`, `shortlist?` | Buy each competitor's keyword list (and the site's own when Search Console is not connected), one search-volume call when needed, and the search page for the shortlist; classify and store. Charged for calls that returned data, never above the quote. | | `list_keyword_gap_runs` | R | `domain_id` | Earlier runs, newest first, with summaries and credits. | | `get_keyword_gap_competitors` | R | `domain_id` | Saved competitors (active or ignored, manual or suggested) and up to 15 free suggestions. | | `add_keyword_gap_competitor` | W | `domain_id`, `competitor`, `note?` | Save a competitor domain; active unless five already are. | | `set_keyword_gap_competitor` | W | `competitor_id`, `status` | Set a saved competitor `active` or `ignored`. | | `remove_keyword_gap_competitor` | W | `competitor_id` | Delete a saved competitor. | | `update_keyword_gap_settings` | W | `domain_id`, `frequency?`, `shortlist?` | Monthly refresh on or off, and the shortlist size. | | `sync_sitemap_to_indexnow` | W | `domain_id`, `sitemap_url?`, `dry_run?` | Read the sitemap and send URLs never sent or whose `` moved, newest first, up to 1,000 a call; `remaining` is what is left. `dry_run` lists them without sending. | | `get_search_visibility_overview` | R | none | Every domain on the account: IndexNow on, automatic submission, key file verified per host, last sitemap read and outcome, URLs on record, last send, and the latest crawler access check, with `attention` listing what needs fixing. | | `get_crawler_access` | R | `domain_id` | The latest crawler access check: per crawler, whether robots.txt allows it (group and rule), what the site answered our request made with its user agent, and the blocked crawlers by purpose (search, ai_search, ai_user, ai_training). | | `check_crawler_access` | W | `domain_id` | Read robots.txt and fetch the home page as every crawler now, store it, and return it with `newly_blocked`. Refused for a minute after the last check. | | `list_crawler_access_history` | R | `domain_id`, `limit?` | Earlier crawler access checks, newest first, with which crawlers were blocked each time. | | `get_local_seo_profile` | R | `domain_id` | Return the saved business profile for one monitored domain: business name, location, service radius, and targeting mode. This profile is required before rank checks or map scans can run. | | `save_local_seo_profile` | W | `domain_id`, `business_name`, `location`, `targeting?`, `radius_miles?`, `is_service_business?`, `street_address?`, `phone?` | Create or update the business profile for a domain. Required fields are business_name and location (city, state). | | `get_local_keyword_targets` | R | `domain_id` | Return the keyword phrases being tracked for a domain in local search. These are the phrases whose rankings get checked when you run a rank check or a map scan. | | `add_local_keyword_target` | W | `domain_id`, `keyword`, `targeting?` | Add a keyword phrase to track for a domain. This is the phrase whose local ranking will be checked. | | `get_local_rank_history` | R | `domain_id` | Return recorded local ranking positions for a domain over time. Shows where the domain ranked for each tracked keyword on each check. | | `get_rank_tracker` | R | `domain_id` | Whether the saved phrases are rank-checked on a schedule (off, daily or weekly), the surfaces, the plan's phrase limit, credits per run and per month, the balance, and how the last run went. Free. | | `set_rank_tracker` | W | `domain_id`, `cadence`, `surfaces?` | Turn the scheduled rank tracker on (daily or weekly) or off. Each run costs 2 credits per phrase per surface when it runs; a run the balance cannot cover is skipped uncharged. Results show in get_local_rank_history with source "scheduled". | | `check_local_ranks` | W | `domain_id`, `request_key` | Run a live rank check for a domain: look up where it currently ranks for each tracked keyword in local search. Requires a saved business profile. | | `get_geogrid_scans` | R | `domain_id` | Return past geogrid (map) scan results for a domain. A geogrid scan checks ranking at multiple points across a grid covering the service area, showing where rankings drop off. | | `run_geogrid_scan` | W | `domain_id`, `keyword`, `grid_size?`, `request_key` | Run a geogrid scan that checks rankings at multiple points across the service area. Requires a local business profile with verified location. | | `get_local_seo_catalog` | R | — | Return the server-authoritative Local SEO catalog: every available action, its exact required inputs, price basis, prerequisites, and REST endpoints. Call this before run_local_seo_action so you can choose the right action and body. | | `run_local_seo_action` | W | `action_id`, `request_key`, `input` | Run one Local SEO action from get_local_seo_catalog without using the web interface. Pass its action_id, a fresh request_key, and exactly the action inputs as an object. | | `get_local_seo_operation` | R | `request_key` | Get the saved or in-progress result for a Local SEO action by the request_key used to start it. Use this to recover safely after a timeout instead of running the paid action again with a new key. | | `get_local_seo_credits` | R | — | Return the remaining rank-check and map-scan credits on the account. Call this before running a scan to avoid a credit-exhausted error. | | `delete_local_keyword_target` | W D | `keyword_id` | Remove a keyword phrase from tracking for a domain. Use the keyword id from get_local_keyword_targets. | | `get_seo_policy` | R | — | The account's SEO allowances: manual scans per day, crawl page caps, whether deep crawls and reports are on the plan. | | `list_manual_url_scans` | R | — | Past one-off SEO scans of individual URLs saved on the account. | | `fetch_sitemap` | W | `domain` | Read the sitemap of one monitored domain and return its URLs — the input for start_deep_seo_job. A live fetch of the site. | | `start_deep_seo_job` | W | `domain_id`, `domain`, `pages`, `sitemap_url?` | Crawl and score a list of specific pages on a monitored domain (from fetch_sitemap or the user). The plan caps how many; the response says if the list was trimmed. | | `list_deep_seo_jobs` | R | — | Every queued, running and finished deep crawl on the account with progress. | | `get_deep_seo_job` | R | `job_id` | Progress and status of one crawl job by id. | | `cancel_deep_seo_job` | W | `job_id` | Stop a queued or running crawl. Pages already scored are kept. | | `get_deep_seo_scans` | R | `domain_id` | The finished deep crawls for one domain with their scores and page counts. Each has a scan id for get_deep_seo_scan_pages. | | `get_deep_seo_scan_pages` | R | `scan_id`, `detail?` | Every page in one deep crawl with its SEO and accessibility scores, page signals and defects (warn and fail checks; passes counted). `detail: true` for every check. | | `check_page_seo` | W | `url`, `detail?` | Fetch any public page and return its findings, not its HTML: scores, the problems worst first each with its page-specific fix (drafts marked as drafts, and where in the site builder to set it), passing checks named, robots.txt, sitemap and http-to-https redirect. A live fetch of that one page. Compact by default; detail: true for every check. | | `assess_website` | W | `url`, `detail?` | A quick combined read of any public URL: an overall score, category scores, the five most important findings, and the findings behind the SEO, accessibility, performance and best-practices scores (problems with fixes, passes named). A live fetch. detail: true for every check. | | `score_page_accessibility` | W | `url`, `detail?` | A quick static accessibility score of any public page with the problems worst first (element, drafted label or alt text, WCAG criterion) and passes named. Not audit grade — start_accessibility_audit is. detail: true for every check. | | `score_page_performance` | W | `url`, `detail?` | A quick performance read of any public page: HTML weight, render-blocking scripts in the head, lazy loading. Not a speed test. A live fetch. detail: true for every check. | | `email_seo_report` | W | `domain_id`, `to`, `note?` | Send the branded PDF SEO report for one monitored domain to an address, with an optional note. Needs a completed SEO scan. | | `get_report_branding` | R | — | The white-label branding applied to emailed reports (company name, logo, accent colour, footer) and whether the plan allows changing it. | | `save_report_branding` | W | `company_name?`, `logo_url?`, `accent_hex?`, `footer_text?` | Set the white-label branding on emailed reports. Enterprise only. | | `get_local_seo_quickstart` | R | `domain_id` | One read that shows where a domain stands in local search setup: the saved business (name, city, whether the city was confirmed), tracked phrases, suggested phrases from the homepage when nothing is saved yet, the newest local-pack and organic positions per phrase, the last Google Business Profile read, the credit balance, and the exact price of a run. | | `run_local_seo_quickstart` | W | `domain_id`, `business_name`, `location`, `keywords`, `request_key` | The whole first step in one call: saves the business name and city, saves up to 5 phrases, confirms the city against the gazetteer, then checks the local-pack AND organic position for every phrase and reads the Google Business Profile. | | `get_local_seo_brief` | R | `domain_id` | Free. What to do next to rank better locally: Google profile fixes with the text to paste, a page for each phrase, on-page issues, why competitors are ahead, and the schedule. | | `autofill_local_seo` | W | `domain_id`, `refresh?` | Free. Does the free part of the local setup: reads the website, fills the blank business details, confirms the city and picks phrases, never over what the owner typed. Returns every value with its source and the paid steps (first report, weekly report) with their price. Run a pending step only after a yes. | | `get_local_autopilot` | R | `domain_id` | Free. The local setup as it stands: each field with its source, phrases, followed competitors, schedule and the paid steps waiting for a yes. | | `preview_local_autopilot` | W | `domain_id`, `refresh?` | Free. Reads the business's own website (homepage plus contact, about, services and areas pages) and proposes the business details and phrases for a first scan, with the exact price. | | `run_local_autopilot` | W | `domain_id`, `business_name`, `location`, `keywords`, `approved_credits`, `request_key`, `street_address?`, `phone?`, `grid_size?`, `grid_keyword?`, `frequency?`, `report_emails?` | Saves the confirmed business and phrases, then runs the first full local scan and emails the report. | | `quote_local_scan` | R | `domain_id`, `grid_size?`, `grid_keyword?` | Free. The price of a full local scan for the saved phrases, with or without a map grid. | | `run_local_scan` | W | `domain_id`, `approved_credits`, `request_key`, `grid_size?`, `grid_keyword?` | Runs a full local scan now and returns the report with the change since the last one. | | `list_local_scans` | R | `domain_id` | Free. Every local scan with its headline numbers, newest first. | | `get_local_scan` | R | `scan_id` | Free. One full local scan report. | | `email_local_scan` | W | `scan_id` | Sends a report again to the owner and the saved recipients. | | `get_local_competitors` | R | `domain_id` | Free. The competitor set with positions, 3-pack count, rating, reviews and the owner's pins and hides. | | `add_local_competitor` | W | `domain_id`, `name`, `website?` | Free. Follow a competitor in every local report, matched to Google Maps by name or website. | | `remove_local_competitor` | W | `competitor_id` | Free. Stop following a competitor: one the owner added is deleted, one Google showed is hidden. | | `update_local_competitor` | W | `competitor_id`, `pinned?`, `hidden?` | Free. Pin a competitor to the top of every report, or hide one that is not a real competitor. | | `update_local_scan_settings` | W | `domain_id`, `frequency?`, `email_reports?`, `report_emails?`, `scan_grid?`, `grid_size?`, `grid_keyword?` | Free. Weekly or monthly scans, report email and recipients, map grid in scheduled scans. | | `set_local_listing` | W | `domain_id`, `place_id`, `name?` | Free. Pins the business's Google listing when a scan could not confirm it. | | `discover_local_seo_details` | W | `domain_id` | Read one monitored domain's public homepage and return candidate business name, city, street address, phone, whether it looks like a service-area business, and up to 12 candidate phrases — each with its source and a confidence. Nothing is saved. | | `verify_local_seo_location` | W | `domain_id`, `location` | Check a "City, ST" against the rank-data gazetteer before saving it, and get the resolved name or close matches. Free. | | `get_local_seo_access` | R | — | Says whether this account can use the Local SEO suite and on which plan. Check it before promising a rank check. | | `quote_local_ranks` | R | `domain_id`, `keywords?`, `surfaces?` | What check_local_ranks or a rank_check action would cost for this domain: per phrase per surface, against the current balance. Free. | | `quote_geogrid_scan` | R | `domain_id`, `keyword`, `grid_size?`, `location?` | What run_geogrid_scan would cost for one phrase and grid size, and whether the balance covers it. Free. | | `get_geogrid_entitlement` | R | `domain_id` | Free map scans remaining this period for a domain, and what more would cost. | | `quote_local_seo_endpoint` | R | `domain_id`, `endpoint` | The credit cost of one catalogued action before running it. `endpoint` is the key after `?endpoint=` in the action's `quote_endpoint` (e.g. `localAudit`); a run_endpoint path is mapped to it. Reports, rank checks and map scans have their own quote tools. | | `search_locations` | R | `q`, `state?`, `country?`, `limit?` | Find the exact place name the rank data covers ("Canton,Ohio,United States") from a partial name. `state` may be "OH" or "Ohio". Free. Use it when verify_local_seo_location or the quick start says the city was not found. | | `list_covered_locations` | R | `prefix?`, `state?`, `country?`, `limit?`, `offset?` | Page through the cities the rank data covers, optionally by state ("OH" or "Ohio") or name prefix. | | `get_local_keyword_ideas` | R | `domain_id`, `limit?` | Phrase ideas already gathered for a domain by earlier keyword research, without spending anything. | | `get_backlink_snapshots` | R | `domain_id` | Past backlink summaries recorded for a domain, so link growth can be shown over time without a new lookup. | | `get_local_seo_audit_history` | R | `domain_id`, `kind?` | Saved local, citation and reputation audits for a domain — reopening one is free. | | `list_local_seo_reports` | R | `domain_id` | Saved profile-health, reviews, AI-visibility, competitor-gap and citation reports for a domain. Reopening is free; the catalog actions refresh them. | | `quote_local_seo_report` | R | `domain_id`, `type`, `refresh?` | Whether a report of this type is already saved (free to reopen) and what a refresh would cost. | ### Scope `accessibility` — 7 tools | Tool | | Inputs | What it does | |---|---|---|---| | `quote_accessibility_audit` | R | `domain_id` | Return cost, available credits, page cap, and whether an audit can start for one monitored domain. | | `list_accessibility_audits` | R | `domain_id?`, `limit?` | Return recent audit-grade WCAG 2.2 AA accessibility evaluations, optionally narrowed to one monitored domain. | | `start_accessibility_audit` | W | `domain_id`, `request_key`, `max_pages?`, `viewport?` | Queue an audit-grade WCAG 2.2 AA browser evaluation for one monitored domain. It spends accessibility credits. | | `get_accessibility_audit` | R | `audit_id` | Return pages, axe findings, WCAG rollups, manual-review coverage, and regression data for one audit id. Automated results do not establish conformance. | | `record_accessibility_manual_review` | W | `audit_id`, `wcag_criterion`, `status`, `notes?`, `evidence_reference?` | Record the human judgement on one WCAG criterion the automated audit cannot decide. Only after the automated checks finish. | | `complete_accessibility_audit` | W | `audit_id` | Mark an audit complete once the manual reviews are recorded. It can then be exported. | | `export_accessibility_audit` | R | `audit_id` | The full audit as a JSON evidence pack: pages, findings, manual reviews, WCAG rollups. Automated results do not establish conformance; say so when handing it over. | ### Scope `vulnscan` — 51 tools | Tool | | Inputs | What it does | |---|---|---|---| | `get_vuln_scan_latest` | R | `domain_id`, `detail?` | Return the most recent completed vulnerability scan for one monitored domain: findings worst first, informational readings counted, the per-host tables left out unless `detail: true`. Check this before starting a new scan — scans take minutes and a recent one usually answers the question. | | `get_vuln_scan_results` | R | `scan_id` | Return the findings for one vulnerability scan by its scan id. Use this to poll a scan that start_vuln_scan just kicked off, since that tool returns immediately while the scan runs in the background. | | `get_vuln_scan_history` | R | `domain_id` | Return the last 20 vulnerability scans for one monitored domain, including status, profile, timing, and severity summary. | | `get_vulnerability_assets` | R | `domain_id` | Return every public hostname discovered for a monitored domain and the latest finding counts attached to each host. | | `get_open_ports` | R | `domain_id` | Return the most recent per-host port, service, banner, version, and risk observations for one monitored domain. | | `list_lookalikes` | R | `domain_id` | Return domains registered by other people that imitate one monitored domain, each with a risk band, the evidence behind it, and when it was first seen. | | `get_lookalike_events` | R | `domain_id` | Return what changed about a monitored domain's impostors over time — one appearing, one gaining the ability to receive email, one getting a certificate, one becoming more dangerous. This is the history; list_lookalikes is the current state. | | `run_lookalike_scan` | W | `domain_id` | Generate variations of a monitored domain, check which are registered, and score how dangerous each one is. Takes up to a minute and counts against the plan, so prefer list_lookalikes for stored results unless a fresh check is specifically wanted. | | `set_lookalike_verdict` | W | `domain_id`, `lookalike`, `verdict?` | Say what one impostor actually is, so it stops being reported. | | `get_observed_components` | R | — | Return the account portfolio technology inventory discovered by site-intelligence and vulnerability scans, including version confidence where observed. | | `get_scan_ownership_verification` | W | `domain_id` | Return the DNS, file, or email proof required before active vulnerability scans and port scans may run, creating a challenge when one does not exist. | | `verify_scan_ownership` | W | `domain_id` | Re-check the published DNS/file proof for one monitored domain. This records verification when the proof is found. | | `start_vuln_scan` | W | `domain_id`, `scan_type?`, `scan_profile?`, `target_hosts?` | Start a vulnerability scan against one monitored domain. Returns a scan id immediately; the scan itself runs in the background, so poll get_vuln_scan_results rather than expecting findings in the reply. | | `get_scan_clearance_status` | R | — | Account-wide view of ownership proof: which domains carry a current proof. A proof clears that domain only. | | `verify_scan_ownership_bulk` | W | `domain_ids?` | Re-check the published DNS or file proof for the domains given (or every domain when omitted) and record what is found. | | `request_ownership_email_link` | W | `domain_id`, `address` | The easiest ownership proof: send a one-click link to an address AT the domain (postmaster@, admin@, the owner's own mailbox — never a gmail or yahoo address). When they click it that domain is cleared for active scanning for 90 days. | | `request_ownership_instructions_email` | W | `domain_id` | Email the three ways to verify a domain (email link, DNS record, file) to the account's own verified sign-in address. Three a day; `email_verification_required` while that address is unverified. | | `request_scan_authorization` | W | `domain_id`, `reason`, `contact_email?` | For a domain the account cannot prove ownership of but is authorised to test (a client engagement), file a request with the reason and a contact; an operator approves or declines. Check get_scan_authorization for the answer. | | `get_scan_authorization` | R | `domain_id` | The latest operator authorisation for one domain and whether it is currently active. | | `list_scan_authorizations` | R | `limit?`, `offset?` | Every authorisation request the account has filed, newest first, with its status. | | `set_lookalike_watch` | W | `domain_id`, `enabled` | Continuous impostor-domain monitoring for one domain. Turning it on needs the plan capability; turning it off is always allowed. | | `get_lookalike_evidence` | R | `domain_id`, `lookalike` | Everything recorded about one lookalike domain — registration, DNS, mail capability, certificates, timeline — assembled for a registrar abuse report or a takedown filing. | | `act_on_lookalike_event` | W | `event_id`, `action` | Mark one lookalike change event as seen (acknowledge) or dealt with (resolve). Event ids come from get_lookalike_events. | | `get_lookalike_alert_settings` | R | — | Whether lookalike alerts are on. When on, a lookalike that needs action lands in the alert feed and is pushed to the account's iPhone. | | `update_lookalike_alert_settings` | W | `push` | Turn lookalike alerts on or off. On means a feed entry and a push when a lookalike gains mail or turns into an active threat. | | `list_vuln_dismissals` | R | `kind?` | Findings, inventory readings and software alerts the account has permanently silenced, with the reason given. | | `dismiss_vuln_finding` | W | `kind`, `target_key`, `reason`, `domain_id?`, `label?`, `note?` | Silence one thing for good, with a reason: false_positive, accepted_risk, fixed or not_applicable. target_key is the finding's stable key from the scan result (what it IS, not its row id), so the dismissal survives re-scans. | | `restore_vuln_dismissal` | W | `dismissal_id` | Remove a dismissal so the finding, component or alert is reported again. Use the id from list_vuln_dismissals. | | `list_software_vendors` | R | — | The observed technology inventory rolled up by company: every vendor seen across the account's domains with component counts and open advisories. | | `get_software_vendor` | R | `vendor` | Every component, version and domain observed for one vendor, with its advisories. `vendor` is the `vendorId` from list_software_vendors (e.g. `woocommerce`). | | `get_vulnerability_feed` | R | — | The global feed of recently ingested vulnerabilities (CISA KEV and NVD), not filtered to this account. Use get_software_advisory_history for one product. | | `get_software_advisory_history` | R | `product`, `vendor?` | Every known advisory for one software product, matched the same way the alert ingest matches it. Use it when a component has no status feed: "CVEs and KEVs only". | | `get_software_overview` | R | — | One list of every application the account watches for advisories and every application a scan found running, with where each runs, the versions read and its open advisories by exposure verdict. `unwatched` software has had no advisory matched, which is not the same as clear. | | `list_tracked_software` | R | — | The software products the account watches for new vulnerabilities, with alert preferences and open alert counts. | | `search_software_catalog` | R | `q?`, `category?` | Find a product to track by name or category. Returns slugs to pass to track_software. | | `get_software_catalog` | R | — | Every product in the catalog. Large; prefer search_software_catalog. | | `get_software_catalog_categories` | R | — | The categories the software catalog is organised by. | | `track_software` | W | `app_name`, `app_slug?`, `vendor?`, `sources?`, `severity_filter?` | Start watching one product for new KEV and CVE entries. Each new entry lands in the alert feed and as a push when it is found; severity_filter narrows what counts. | | `update_tracked_software` | W | `app_id`, `is_active?`, `severity_filter?`, `sources?` | Change which severities and feeds one tracked product alerts on, or pause it with is_active false. Only the fields passed change. | | `untrack_software` | W D | `app_id` | Remove a tracked product and its alerts. Confirm the product name first. | | `watch_all_detected_software` | W | — | Add every detected, unwatched product to the software watchlist in one call. Free. | | `list_software_alerts` | R | `status?`, `source?`, `limit?`, `offset?` | KEV and CVE alerts raised for tracked and observed software, each with severity and an exposure verdict: confirmed (the vulnerable version was observed), possible, not_affected or unconfirmed. Lead with confirmed ones. | | `get_software_alert_summary` | R | — | Counts of unread, critical, KEV and confirmed-exposure software alerts, per tracked product and in total. | | `update_software_alert` | W | `alert_id`, `alert_status` | Set the status of one software alert. remediated records that the fix was applied; dismissed means it will not be. | | `list_ip_targets` | R | — | The IP addresses and CIDR ranges the account port-scans daily, with their client and last result. | | `add_ip_target` | W | `target`, `attestation`, `label?`, `client_id?`, `daily_scan?` | Start daily port scanning of a public IP or small CIDR. The user must confirm they own or are authorised to scan it; pass attestation true only after they say so in words. | | `update_ip_target` | W | `target_id`, `client_id?`, `daily_scan?` | Move a target to another client or turn its daily scan on or off. | | `delete_ip_target` | W D | `target_id` | Stop scanning a target and remove it with its history. Confirm first. | | `list_ip_target_scans` | R | `target_id` | Past port scans of one target with the open ports and services seen each time. | | `scan_ip_target` | W | `target_id` | Run the port scan of one target immediately instead of waiting for the daily run. Sends traffic to the target. | | `get_exploited_software` | R | — | Free. CISA Known Exploited Vulnerabilities matching software seen running on the account's hosts: CVE, product, version, hosts, CISA's due date and required action, ransomware use, fix version. `hostsChecked: 0` means nothing has been read. | ### Scope `compliance` — 11 tools | Tool | | Inputs | What it does | |---|---|---|---| | `get_compliance_records` | R | — | Return the account's compliance checklist — PCI, HIPAA, and cyber-insurance controls with their current status and notes. Each control carries one of unanswered, met, partial, not_met, na. | | `update_compliance_item` | W | `record_id`, `status`, `notes?` | Set the status and notes on one compliance control. Use the record id from get_compliance_records, not the item_id. | | `initialize_compliance_records` | W | `items` | Create compliance control records from a list of items (item_id, framework, category, requirement, optional status and notes). Existing records are kept. | | `bulk_update_compliance` | W | `items` | Set status and notes on many controls in one call, by item_id. Same status vocabulary as update_compliance_item, same rule: never write not_met for a control nobody assessed. | | `list_wisps` | R | — | Every written information security plan on the account: status, progress, when last built. Call first so you continue a draft. | | `start_wisp` | W | `title?` | Start a WISP by interview; returns the first questions to ask. | | `get_wisp_interview` | R | `wisp_id`, `next?` | Progress, saved answers, and the next questions (id, question, why, type, options or fields, suggested, example). | | `answer_wisp_questions` | W | `wisp_id`, `answers` | Save answers keyed by question id; returns saved, rejected with reasons, progress and the next questions. | | `build_wisp` | W | `wisp_id` | Write the finished plan as a Word document; returns a 7-day `download_url` and `open_items`. 409 with the questions still needed if the interview is not done. | | `get_wisp_download_link` | R | `wisp_id` | A fresh 7-day link for a built plan. | | `delete_wisp` | WD | `wisp_id` | Delete a plan and its answers. Confirm first. | ### Scope `status` — 9 tools | Tool | | Inputs | What it does | |---|---|---|---| | `get_status_services` | R | `detail?` | Status across the whole third-party catalogue (~940 vendors), not only the account's: counts by status plus every vendor not operational with its affected components. `list_status_subscriptions` says which vendors the account follows; `detail: true` returns every vendor and component. | | `get_status_incidents` | R | — | Open incidents across every tracked provider right now. Read with get_status_services when a user reports an outage. | | `get_status_history` | R | `limit?`, `offset?` | Past status changes across tracked providers, newest first. | | `list_status_subscriptions` | R | — | Which third-party providers this account is subscribed to for status alerts, and at what level. | | `get_status_subscription_suggestions` | R | — | Providers the account's domains were observed depending on (host, DNS, email, CDN): `suggestions` gives each one's id, name, role, domains and whether the account already follows it (`following`). Offer the ones not followed, by name, during onboarding. | | `update_status_subscriptions` | W | `subscriptions` | Subscribe or unsubscribe from providers. Each entry names a service_id from get_status_services and a notify_level: all, major, or none. | | `subscribe_all_status_services` | W | — | Subscribe to every provider the account's domains depend on, in one call. | | `unsubscribe_all_status_services` | W | — | Clear every provider subscription. Confirm first. | | `suggest_status_service` | W | `status_page_url`, `service_name?`, `notes?` | Ask for a provider status page to be added to the catalogue. Needs its https:// status page URL. | ### Scope `admin` — 5 tools | Tool | | Inputs | What it does | |---|---|---|---| | `list_error_events` | R | `days?`, `source?` | The Worker's own error log (`error_events`): every recorded failure in the window, grouped by fingerprint with occurrence counts and the accounts affected, plus the newest 100 raw rows. Admin-scoped keys only. | | `get_self_check_status` | R | — | Every self-check invariant's last verdict — the failing ones first, with the observed value and the sentence the check wrote — and when each cron scope last completed a pass. Admin-scoped keys only. | | `list_scan_failures` | R | `days?` | Domain scans, exposure scans, site crawls and accessibility audits that ended in failure in the window, with their recorded reason, plus exposure scans stuck queued or running for over two hours. Admin-scoped keys only. | | `list_scanner_reports` | R | `days?`, `status?` | What customers' assistants reported wrong with the scanner, open ones first: the claim, the evidence, the suggested fix, who sent it. Admin-scoped keys only. | | `get_verification_stats` | R | `days?` | Scanner accuracy from customers' agent verdicts: confirmed versus disputed per test method, and the recent disputes. Admin-scoped keys only. | | `list_feature_requests` | R | `status?` | The feature-suggestion queue (open by default, or all, or one status): what was asked, by whom, from where, its status and note, with a count per status. Admin-scoped keys only. | | `update_feature_request` | W | `suggestion_id`, `status`, `note?` | Move a request to planned, building, shipped or declined; the note is shown to the person (required to decline), and shipped pushes them. Admin-scoped keys only. | | `domainguard_waitlist_launch_email` | W | `action?`, `app_store_url?` | The DomainGuard iPhone app waiting list's one launch email: `status` (default), `preview` (count and rendered email), `test` (one copy to the admin), `send` (50 every five minutes, never twice to one address, unsubscribed skipped). Admin-scoped keys only; confirm with the operator before `send`. | | `monthly_report_email` | W | `action?`, `user_id?`, `month?` | The monthly report email (one per account on the 1st: still open, fixed last month, record changes DomainGuard made, new findings, fix requests): `status` (default; who is on the list, how many went out), `preview` (the rendered email for `user_id`, sends nothing), `test` (one copy to the signed-in admin's own address, nowhere else), `on` / `off` (the list for `user_id`; off for every account until switched on). `month` is YYYY-MM, default last month. Admin-scoped keys only; confirm with the operator before `on` for a customer. | --- ## 12. Protocol notes - One endpoint, `POST /api/mcp`, JSON-RPC 2.0. An authenticated `GET` returns 405 with `Allow: POST`; an unauthenticated one returns 401, since the key is checked first. - Methods: `initialize`, `server/discover`, `tools/list`, `tools/call`, `prompts/list`, `prompts/get`, `resources/list`, `resources/read`, `resources/templates/list` (empty), `ping`. - Prompts: `onboard_domain`, `set_up_local_seo`, `review_domain`, `portfolio_triage`, `fix_email_deliverability`. Each takes an optional `domain` argument (`review_domain` and `fix_email_deliverability` require it) and returns one user-role message that tells the model which tools to call, in what order, and what to ask in between. - Resources: `domainguard://guide` (this document, read from the deployed file), `domainguard://onboarding`, `domainguard://local-seo`, `domainguard://review` (the three protocols in §4–§5). All `text/markdown`. - Protocol revisions supported: `2026-07-28` (stateless) and the handshake era `2025-11-25`, `2025-06-18`, `2025-03-26`. Both eras are served from the same stateless code path. - No sessions and no `Mcp-Session-Id`. Nothing is remembered between calls. - JSON-RPC batching is not supported; it was removed from the spec in 2025-06-18. - Notifications receive `202` with no body. - A tool that fails returns a normal result with `isError: true` and readable text. Only transport-level problems return a non-200 status. - `tools/list` results are cacheable, private, and scope-dependent — never share one key's tool list with another key. Prompt and resource lists are public and identical for every key. - Every tool is re-dispatched through the same route table the dashboard uses, so the tenant checks, plan gates and rate limits are the REST API's own. --- ## 13. Paste-in block for a project's CLAUDE.md ```markdown ## DomainGuard MCP The `domainguard` MCP server is connected. It reaches every part of the account: DNS, SSL, mail authentication and DMARC reports, security checkups, Hijack Guard, lookalikes, vulnerabilities and ports, software CVE/KEV alerts, dark web, on-page and site-wide SEO, accessibility, local rankings and the Google Business Profile, provider status, compliance and incident records. Rules: - Start with `list_domains`; every other tool needs the numeric `domain_id`. - Read stored results (`get_checkup_history`, `get_seo_results`, `get_vuln_scan_latest`, `get_local_rank_history`, `get_domain_setup_guide`) before running any scan. - To add or set up a domain: `plan_domain_onboarding` first, ask its questions in plain words, then `onboard_domain` once. It spends nothing. Paid steps (`run_local_seo_quickstart`, `start_accessibility_audit`) come after, on a quote the user agreed to. - Verify, then report back: `get_verification_tasks` gives each claim with how it was tested and read-only commands to re-test it. Run them on the account's own domains only, then send every verdict (confirmations too) with `submit_verification_results`. Other mistakes: `report_scanner_issue`, then `submit_scanner_review` once per domain. Never log in, fuzz or exploit. - `run_*`, `start_*`, `check_*`, `verify_*`, `scan_*`, `fetch_*`, `discover_*` and `assess_*` cost time or credits. So do `get_domain_email_security` and `get_scan_ownership_verification`. Say the cost before spending. - Call `get_local_seo_catalog` before `run_local_seo_action`; it is the authority on inputs and price. - Every paid action needs a fresh `request_key` (8-100 chars, [A-Za-z0-9._-]). After a timeout use `get_local_seo_operation`, never a re-run. - `start_vuln_scan`, `start_site_audit`, `start_deep_seo_job` and `start_accessibility_audit` are asynchronous — poll their results tools. - Confirm every `delete_*`, `remove_*`, `untrack_*` and `unsubscribe_*` in words before calling it. - DomainGuard monitors these domains, it does not host them. Deliver the diagnosis plus the exact change, and say where the user applies it — registrar, DNS provider, host, CMS, or Google Business Profile. - Never estimate a score, position, volume, or price that a tool did not return; never invent a business name, city, phrase or email address. Full guide: https://nhmohio.com/mcp/claude-guide.md (also the MCP resource `domainguard://guide`). Prompts: /mcp__domainguard__audit_domain, onboard_domain, set_up_local_seo, review_domain, portfolio_triage, fix_email_deliverability. ```