squirrelscan
GitHub

Hosted MCP server

Connect any MCP client to the hosted squirrelscan MCP server: cloud audits, reports, issues, and rules as native agent tools

squirrelscan runs a hosted Model Context Protocol server in the cloud. Point any MCP client at it and your agent gets tools for running cloud audits, reading and comparing reports, working the per-website issue tracker, catching up on notifications, sending product feedback, and browsing the 300+ rule catalog. Nothing to install, nothing to keep running:

https://mcp.squirrelscan.com/mcp

The transport is streamable HTTP. Authentication is OAuth 2.1 (browser login) or a squirrelscan API key as a bearer header, whichever suits the environment.

Quick connect

The fastest path is Claude Code:

claude mcp add --transport http squirrelscan https://mcp.squirrelscan.com/mcp

Then, inside a session, run /mcp, select squirrelscan, and choose Authenticate. Your browser opens, you log in to squirrelscan, pick the organization the agent should act for, and approve. That’s it: the tools are live.

Every other client is a couple of lines of config. See MCP client setup for exact snippets for Cursor, Codex CLI, VS Code, Windsurf, and Gemini CLI.

Authentication

There are two ways in. Pick one per config entry.

OAuth (browser login)

Add the server with no auth headers and the client handles everything: it discovers the authorization server at /.well-known/oauth-authorization-server, registers itself dynamically (DCR and CIMD are both supported), and runs the PKCE browser flow. You log in, choose an organization, and approve on the consent screen.

Behind the scenes the flow mints a 30-day squirrelscan API key scoped to the organization you approved, plus a rotating refresh token. A client that supports refresh renews the key on its own, so you are not prompted at 30 days. The refresh token lapses 90 days after its last use; after that the client prompts you to sign in again.

Best for: a human at an editor. No key to copy, no secret in any config file.

API key (bearer header)

Set a standard bearer header on the server entry:

Authorization: Bearer sq_xxxxxxxxxxxx

Get a key from the dashboard at app.squirrelscan.com/settings/api-keys, or without leaving the terminal:

squirrel keys create --shell

--shell offers to append export SQUIRRELSCAN_API_KEY=sq_... to your shell rc, and SQUIRRELSCAN_API_KEY is the conventional env var: most clients can interpolate it into the header so the key never lands in a config file. See squirrel keys and Authentication.

Key scopes apply to tools exactly like they do to the REST API: run_audit, add_website, delete_website, delete_websites, and comment_on_issue need audits:write; audit, report, and issue reads (compare_audits included) need audits:read; get_credit_balance and list_credit_transactions need credits:read; list_notifications needs org:read. A key missing a scope gets a structured FORBIDDEN error naming the missing scope. whoami works with any key but only includes the balance when the key has credits:read.

Best for: CI, headless machines, containers, and configs you commit and share.

Without credentials you can read the public catalog and the agent skills:

  • initialize, server/discover, tools/list, resources/list, prompts/list
  • skills/list and skills/get
  • resources/read for skill:// URIs only
  • the client notifications notifications/initialized, notifications/cancelled, notifications/progress and notifications/roots/list_changed

Everything else needs credentials: every tool call, every other resource, and every other method. Unauthenticated calls get a 401 with a WWW-Authenticate challenge that points OAuth-capable clients at the login flow. Anonymous requests share a limit of 30 per minute per IP, an anonymous batch holds at most 10 messages, and any request body over 1 MiB is refused with a 413.

Tools

All tools require authentication. Write tools are marked; everything else only reads.

ToolWhat it doesAccess
whoamiIdentify the active credentials: org, plan, auth kind, credit balance. Call it first.Read
run_auditRun a cloud audit of a URL (crawl + 300+ rule analysis + report) at an audit level. Estimate and confirm rail, see Credits. Registers the website automatically on first audit. If the requested page count exceeds the org’s plan ceiling, the audit still runs at the plan’s max (never a hard error) and the response carries a notice field saying so.Write, spends credits
get_audit_statusPoll a running audit by run_id until it completes. While running, the response includes a progress field (phase, message, page/link counts) when available. notice is present when the run’s page count was clamped to the plan’s ceiling at dispatch time.Read
get_reportFetch a finished report: summary (JSON scores, top issues grouped by rule with fix text deduped into a rules dict, and score/issue-count history across prior audits), llm (compact text for agent context), markdown (the readable report), or rule_ids (just the distinct failing and warning rule ids, uncapped, for building a disable list). Every format says how many rules the website’s config switched off, as rulesDisabledByConfig or a line such as “3 rules disabled by config”. A published report is a capped summary: each issue lists a sample of its pages and items with the true counts (affectedPagesCount, occurrences), and detail.note says where the full per-page list is.Read
compare_auditsDiff two completed audits of a website: the latest against the one before it by default, or any two by base and head run id. Each finding is new, resolved, worsened, improved, unchanged, still_open, not_crawled or not_checked, with score movement per category; the last four are listed only when asked for with include_unchanged, include_still_open, include_not_crawled and include_not_checked, and totals counts every kind. A page the newer audit did not visit counts as not_crawled, and a rule it could not run (a capped crawl’s inbound-link rules, a check that did not run) as not_checked with its skipReason, never resolved; resolutionEvidence says what backs the resolved verdicts.Read
list_auditsActive and recent audit runs for the org. An active run carries notice when its page count was clamped to the plan’s ceiling at dispatch time.Read
list_websitesWebsites the org has audited, with latest status and health score. Paginated (total/has_more).Read
add_websiteRegister a website without auditing it. Idempotent per domain. Optional kind and scope (discover or sitemap) set the site’s classification and stored crawl scope; the result reports both.Write
get_website_settingsOne website’s stored audit settings: its audit level with page cap, rendering and external link checks, crawl depth, crawl scope, include and exclude patterns, its disable_rules (with disabled_rule_count, the rules they match), its recurring audits, and whether broken link alerts are on. runs_as says how the next audit runs on your plan (with a notice when the plan holds a setting back), and schedule.estimated_cost is the most the recurring audits can spend per run, week and month. Needs audits:read.Read
update_website_settingsChange those settings for one website, including schedule (off, daily, weekly or monthly), broken_link_alerts (Pro and Team; turning it on is refused on Free) and disable_rules (rule ids and category globs the website’s cloud audits skip, see Tailor each website with rules). Omitted fields stay as they are, values are checked like the dashboard’s, and the result shows what is stored afterwards with the fields that were applied. Confirm rail for anything that raises spend, see below. Needs audits:write.Write
delete_websiteSoft-delete a website (confirm rail). Past audits, reports, and issues are preserved and published report links keep working; the site leaves listings and frees a slot under the plan’s website limit. Re-adding the domain later creates a fresh website with a new id.Write
delete_websitesSoft-delete up to 50 websites in one call, with the same confirm rail and the same soft delete as delete_website. Each result names the domain behind its id whenever it can be found, and an id that does not resolve is reported on its own rather than failing the call.Write
list_issuesAudit issues for a website, sorted by severity, filterable by status, severity, and category. Rows omit the long recommendation unless include_recommendation is set; website_totals counts the whole website, total counts the filtered set. website_totals replaces the earlier severity_summary and status_counts fields, an intentional change: read the counts there. After a smart audit each row also carries provenance and last_seen_at: see issue provenance.Read
get_issueOne issue with full detail, per-occurrence detail from the latest report (which pages/images, capped and countable via occurrence_limit), and its comment thread. It carries provenance and last_seen_at as list_issues does.Read
comment_on_issuePost a markdown comment on an issue, visible in the dashboard thread.Write
list_rulesBrowse the audit rule catalog by category or keyword.Read
get_ruleOne rule’s full definition by id, e.g. crawl/canonical-chain, including its fix recommendation and docs link.Read
list_entitiesThe entities a site declares in its JSON-LD, collapsed across every crawled page, so an Organization declared on 60 pages is one row rather than 60. Filter by @type, by declaring page, by problem class, or by a text match on the name. Page-local entities (a page’s own WebPage, BreadcrumbList and unnamed images) are hidden unless include_page_local is true. Paginated (total/has_more). Needs audits:read.Read
get_entityOne entity as the map recorded it: the properties the map keeps, the pages that declare it, the properties whose values disagree between those pages, and the references in and out of it. Accepts the entity key, its @id, or its name. Needs audits:read.Read
get_entity_graphThe whole entity graph, or a filtered part of it, as json, jsonld, mermaid, dot, graphml or markdown. Takes the same filters as list_entities. mermaid and markdown are capped and say so in truncation. Needs audits:read.Read
compare_entitiesThe change set between two audits: entities added and removed, entities that gained or lost an @id, occurrence changes, new and resolved conflicts and dangling references, and the pages each audit saw that the other did not. Defaults to the previous audit versus the latest. An entity counts as removed only when every page that declared it was crawled again, so a smaller crawl never reads as a site that deleted its structured data. Needs audits:read.Read
get_entity_findingsThe schema/entity-* rule verdicts for an audit: what is wrong with the site’s entity graph, which entity keys and pages each finding affects, and the fix text for each. The keys and pages on a finding are a sample, so use list_entities with the matching problem filter for the full set. Needs audits:read.Read
get_credit_balanceMonthly and pack credit buckets for the org.Read
list_credit_transactionsOrg credit accounting log: grants, per-audit debits (each carries a run_id), refunds and adjustments, newest first, paginated, with the current balance. Needs credits:read.Read
list_notificationsThe org’s notification feed, newest first: audits completed or failed, new issues, broken link alerts, paused schedules. Filter by category or unread_only, page with limit and offset, or pass next_cursor back as after to get only what arrived since, oldest first. It never marks anything read. Needs org:read; the org’s own entries also need audits:read, so a key or sign-in without it (an org:read-only key, or a billing member’s sign-in) gets none of the org’s entries; a sign-in still gets its own account-level ones.Read
create_api_keyMint a new org API key. Requires the keys:write scope, which only OAuth login grants, and only to organization owners and admins. The key is returned exactly once.Write, gated
send_feedbackSend product feedback to the team: bug_report, feature_request, what_worked, confusing, missing_data, tool_ergonomics, or other. Works with any authenticated key, no scope required.Write

A few tools deserve a closer look:

  • Unknown arguments are reported, not applied. An argument a tool does not accept is dropped, and the successful result lists it in ignored_args, so you can tell a setting that took effect from one that did not.

  • run_audit has a confirm rail. When the estimate is above the auto-run threshold (50 credits by default), the first call returns the estimate instead of starting; the agent shows it to you and calls again with confirm: true. An estimate at or under the threshold starts immediately, and an agent that already has your approval can pass confirm: true on the first call. A max_credits argument acts as a hard spend guard: the audit refuses to start if the estimate exceeds it.

  • run_audit takes a level. Pass quick (25 pages from the start page and its sitemaps, no cloud checks, with external link checks), surface (100 pages, one per URL pattern, with the cloud checks and external link checks) or full (500 pages, every page it can reach). Without one (or the deprecated coverage below), the website’s own level applies, and a new website starts on surface. max_pages sizes a single run on top of the level. Every level is billed: 50 credits plus 2 per audited page, plus 1 per external link the cloud verifies. external_links sets this run’s external link checks (true at any level, false to skip them; default: the website’s setting), and internal_links: false skips the free internal link check for this run. On the Free plan a full request samples the way surface does instead, one page per URL pattern first, and the response says so in level_notice. Both the estimate and the started run report the level they use in level and audit_level. The older coverage argument (fast, surface, deep, full) still works for now with the meaning it always had, but it is deprecated, and passing it together with level is an error.

  • delete_website and delete_websites have the same confirm rail. The first call previews what happens without deleting anything (for one site, the domain and how many audits and open issues stay attached to the old id; for a batch, the domain and kind behind every id); the agent confirms with you and calls again with confirm: true. Deletion is soft: nothing is destroyed, and re-adding the domain later registers a fresh website with a new id.

  • Recurring audits switch on by themselves, and the schedule says so. After a website’s first successful cloud audit, squirrelscan re-audits it on a schedule, and every run spends credits. run_audit, get_audit_status and both settings tools carry a schedule field: pending_first_audit: true means the schedule is about to switch on, and estimated_cost gives the most it can spend per run, per week and per month. update_website_settings with schedule: "off" turns it off at once.

  • update_website_settings has a confirm rail for spend. Turning recurring audits on, changing their cadence, or a change that raises the most one audit can cost (a bigger page cap, external link checks) returns confirmation_required with the estimate and changes nothing; the agent shows it to you and calls again with confirm: true. Turning the schedule off, and changes that cost nothing more (crawl scope, patterns, depth, internal_links, a cheaper level), apply straight away. A cadence your plan does not run is set to one it does, and a page cap above your plan’s ceiling is stored and held to the ceiling when audits run; the result says so in both cases.

  • Your role sets what an OAuth sign-in can do. The approval screen offers only the scopes your role in the chosen organization holds: a viewer’s sign-in reads but cannot run audits or change websites, and a billing member’s holds credits:read and org:read only. A tool outside them returns FORBIDDEN with your role and the missing_scope. If your role later changes to one with less access, the sign-in ends and the client asks you to sign in again.

  • create_api_key is scope-gated. It needs credentials carrying the reserved keys:write scope. OAuth login grants it to organization owners and admins (you consented on the approval screen); an editor, viewer or billing member’s sign-in works without it. The tool also checks your current role, the same rule as creating a key in the dashboard, so a member who is demoted or removed can no longer mint keys. Plain API keys never carry the scope, and keys minted by this tool can’t mint further keys. No escalation loops.

Issue provenance

After a smart audit, list_issues and get_issue return two extra fields on each issue:

  • provenance: fresh means this audit re-checked the issue. carried means the issue survives only because its pages were not re-crawled this audit, so confirm it with a re-crawl before you act on it. unrendered means the issue’s page has never been rendered in any scan, so there is no earlier observation.
  • last_seen_at: an ISO 8601 UTC timestamp of the crawl that last observed the issue. It is the evidence date for a carried issue, and it is absent for fresh and unrendered issues.

Both fields are omitted when the issue has no recorded provenance, for example on a website whose audits are not smart audits.

get_report returns seedRedirect only when the audited URL redirected off-site and the crawler did not follow it. Its finalUrl is null, with a note saying the redirect target was not a valid URL and is withheld, when the target was not a valid http(s) URL. The audit then graded the audited address, not the redirect target.

Tailor each website with rules

squirrelscan results are meant to be tailored to each site through rules. Not every rule applies to every site: a hobby site with no privacy, about or contact pages does not need those findings re-reported on every audit. Rules that do not apply are disabled, so the score reflects what matters for that site.

disable_rules on get_website_settings and update_website_settings is the per-website list:

{ "website_id": "...", "disable_rules": ["eeat/*", "legal/privacy-policy"] }
  • What it takes. Exact rule ids (eeat/about-page) and category globs (eeat/*), the same patterns as disable in squirrel.toml. It is stored per website and applied to every cloud audit of it, whether started by run_audit, a schedule or the dashboard.
  • It replaces the stored list. Send the whole list you want; an empty list clears it and every rule runs again. get_website_settings returns the list and disabled_rule_count, the number of rules it matches.
  • What it refuses. * (it would switch every rule off), blank entries, entries that match no rule and lists over 500 entries. Entries already stored are kept when you send the list back. Take ids from list_rules or from get_report with format: "rule_ids", not from memory.
  • Local runs read squirrel.toml, cloud runs read this list. A cloud audit cannot see your repository, so run_audit does not read a squirrel.toml. The CLI and the local MCP server do. To get the same rule set in both, set the same ids in both places. [[ignore]] waivers and enable are squirrel.toml settings and do not apply to cloud audits.
  • The report says what was switched off. get_report carries rulesDisabledByConfig in the summary and rule_ids formats, and a line such as “3 rules disabled by config” at the top of the llm and markdown formats, so two scores are compared knowing they ran the same rules. It appears for a cloud run that recorded its rule selection, 0 rules included.

To build a list, run an audit, call get_report with format: "rule_ids" for the failing rule ids (each with its name, category, status and occurrences), pick the ones that do not apply to the site, and send them to update_website_settings. Run the audit again and check the rules are gone from the report.

The entity tools run one loop

The five entity tools exist to be used in order. list_entities with problem: "no-id" returns the entities a site declares on several pages with nothing tying them together, you fix the site’s JSON-LD, run_audit re-audits, and compare_entities confirms the keys you fixed come back under gainedId. That last step is the one worth insisting on: an entity that gains an @id changes key, so a naive before-and-after comparison reports the fix as one removal plus one addition and it reads as damage.

The entity map over MCP covers the loop in full, including what the coverage field on each change is claiming.

Which number to compare

Three tools report counts that look comparable and are not. The published report is the source of truth, and everything else is derived from it:

WhereFieldWhat it is
get_report (summary)healthScoreThe audit’s overall score, 0-100. Canonical.
list_audits, get_audit_statushealth_scoreThe same value, read from that run’s published report. null for a blocked or unreachable site: no grade was earned.
get_report (summary)failed + warningsFailing and warning checks in the report.
list_audits, get_audit_statusissues_foundThe same total, read from the same report.
list_issuesthe open countA per-website issue tracker spanning audits, not a per-run count.

Compare runs on health_score. It is the one number rescored consistently across the whole site, so a move between two runs means the site moved.

Do not compare issues_found with the open count from list_issues. The tracker folds a rule failing on 600 pages into one issue and keeps an issue open until an audit re-checks it, so its count is legitimately much smaller and moves on its own schedule.

Credits

Cloud audits are pay-as-you-go: credits are spent while the audit runs, and nothing is charged up front. From the agent’s side a paid audit looks like this:

  1. Agent calls run_audit with the URL. The response comes back with status: "confirmation_required", an upper-bound estimate (estimate.cap), and your current balance.
  2. The agent shows you the estimate and asks. Actual spend is usually below the cap.
  3. You approve; the agent calls run_audit again with confirm: true. The audit starts and returns a run_id.
  4. The agent polls get_audit_status every 15-30 seconds, then pulls the results with get_report.

An audit costs 50 credits base plus 2 per audited page, however the page was fetched, plus 1 per distinct external link the cloud verifies when external link checking is on. If your balance can’t cover the page limit, the audit runs with a lower limit and the response’s notice says so; it is refused only when even one page is unaffordable. Check your balance with get_credit_balance, audit where the credits went with list_credit_transactions (or a single audit’s per-feature breakdown via get_report’s cost field), or top up at app.squirrelscan.com/billing.

Sample prompts

Copy-paste starters. Prompts marked spends credits run a cloud audit; prompts marked write change state in your org.

  • “Audit https://example.com and fix the top 3 issues in this repo.” (spends credits, write)
  • “Check my site’s open audit issues and comment on the ones this PR fixes.” (write)
  • “Compare the last two audits of example.com and tell me what regressed.”
  • “Create an API key for CI and add it to my GitHub Actions secrets.” (write, needs OAuth login as an organization owner or admin for keys:write)
  • “What would a full audit of example.com cost, and how many credits do we have left?”
  • “Run a full audit of staging.example.com, but stop if it would cost more than 200 credits.” (spends credits, write)
  • “List our websites and summarize the health of each one.”
  • “What does the rule crawl/canonical-chain check, and how do I fix it on a Next.js site?”

Local MCP server

The CLI ships a local stdio MCP server too:

squirrel mcp

It runs squirrelscan’s deterministic audit engine on your machine: it works offline and needs no account. Reach for it when you are auditing localhost or a private network, or have no network at all. Signed in, its audit_website is a billed cloud audit like squirrel audit, with the same confirmation step as the hosted run_audit. The hosted server is the better default for everything cloud-side: shared org state, the issue tracker, browser rendering, and zero local install.

It also serves the squirrelscan agent skills over MCP (the Skills extension), so a client that supports it gets the matching skills from the server itself: see Skills.

Full setup and tool reference: squirrel mcp.

Type to search…

↑↓ navigate↵ openesc close