seomonster — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited seomonster (Plugin) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center">
<picture> <source media="(prefers-color-scheme: dark)" srcset="assets/wordmark-dark.svg"> <source media="(prefers-color-scheme: light)" srcset="assets/wordmark-light.svg"> <img alt="SEOMonster" src="assets/wordmark-light.svg" width="440"> </picture>
<h3>Don't learn another SEO dashboard. Just ask.</h3>
<p>The open-source SEO analyst that lives inside the AI assistant you already use —<br>working from <b>your own</b> Search Console, Analytics, and PageSpeed data, on your machine.</p>
<p> <a href="https://pypi.org/project/seo-monster/"><img src="https://img.shields.io/pypi/v/seo-monster?color=2BD9A8&label=pypi" alt="PyPI version"></a> <a href="https://pypi.org/project/seo-monster/"><img src="https://img.shields.io/pypi/pyversions/seo-monster" alt="Python versions"></a> <a href="https://pypi.org/project/seo-monster/"><img src="https://img.shields.io/pypi/dm/seo-monster?color=2BD9A8" alt="Downloads"></a> <a href="https://github.com/avansaber/seo-monster/actions"><img src="https://img.shields.io/badge/tests-567%20passing-2BD9A8" alt="Tests"></a> <img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT"> <a href="https://github.com/avansaber/seo-monster/stargazers"><img src="https://img.shields.io/github/stars/avansaber/seo-monster?style=flat&color=2BD9A8" alt="GitHub stars"></a> </p>
<p> <a href="https://cursor.com/en/install-mcp?name=seomonster&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyJzZW8tbW9uc3RlciJdfQ=="><img src="https://cursor.com/deeplink/mcp-install-dark.svg" alt="Add to Cursor" height="32"></a> <a href="https://insiders.vscode.dev/redirect/mcp/install?name=seomonster&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22seo-monster%22%5D%7D"><img src="https://img.shields.io/badge/VS_Code-Install_server-0098FF?style=flat&logo=visualstudiocode&logoColor=white" alt="Install in VS Code" height="32"></a> <a href="https://seomonster.avansaber.com"><img src="https://img.shields.io/badge/Website-seomonster.avansaber.com-555" alt="Website" height="32"></a> </p>
</div>
You don't open a new tool — you just ask, in plain English, inside Claude (or Cursor / Cline / Codex):
You "What should I write about next?"
>
🦖 SEOMonster Pulls your Search Console and surfaces three topics you're almost ranking for — positions 8–20 with real demand — ranked by opportunity. Say the word and it drafts the brief.
| You ask … | … SEOMonster does |
|---|---|
| "What should I write about next?" | Surfaces near-ranking topics from your own Search Console demand |
| "Is this page ready to publish?" | Runs the technical-SEO + structured-data checks before you ship |
| "Get this indexed." | Nudges Google (Indexing API) and Bing / Yandex (IndexNow) |
| "Did my change actually move rankings?" | Before/after attribution vs a matched control group — with a confidence interval, not a guess |
| "Is ChatGPT recommending us, or our competitors?" | Tracks your brand's share of voice across the AI answer engines |
70 tools across Search Console, GA4, PageSpeed, Cloudflare, AI-citation tracking, keyword discovery, and technical SEO — every one returning the same JSON result envelope, every one driven by your own credentials. No new dashboard to learn; you chat with the assistant you already use.
[!NOTE] Published on PyPI as `seo-monster` (so the command isuvx seo-monster). The import package isseo_mcp;seo-mcpis a dev/local alias. The package ships zero secrets — every credential is resolved at runtime from your own environment or config file.
SEO_MCP_ALLOW_DESTRUCTIVE, and the riskier ones also need a per-call confirm token.mcp SDK + the Google client libraries; PageSpeed and Cloudflare ride on urllib, no extra HTTP dependency.[!TIP] Fastest path: the one-click install buttons above. Or add it to any MCP client by hand:
{
"mcpServers": {
"seomonster": { "command": "uvx", "args": ["seo-monster"] }
}
}Run the one-time Google sign-in once — uvx --from seo-monster seo-monster auth — then ask your assistant to call `system_status` to confirm what's connected. Full per-client setup and credentials are in Install and Auth.
flowchart LR
U["You — plain English"] --> H["Claude · Cursor · Cline · Codex"]
H -- "MCP · stdio" --> S["SEOMonster<br/>70 tools"]
S --> GSC["Search Console · GA4"]
S --> PSI["PageSpeed · CrUX"]
S --> CF["Cloudflare · IndexNow"]
S --> AI["AI engines · SERP<br/>(optional)"]
S -. "your creds · your machine" .-> USEOMonster is a stdio MCP server: your AI host launches it, it calls Google / Cloudflare / the AI engines with your credentials, and returns a consistent JSON envelope the assistant reads back to you in plain language.
For the `.mcpb` bundle path (Claude Desktop): just Claude Desktop on macOS or Windows. The bundle declares Python 3.11+ as a runtime; Claude Desktop materializes the environment for you. No prior uv install needed.
For the `uvx` path (Cursor, Cline, Codex, advanced Claude Desktop): Python 3.11 or newer plus uv (which provides uvx). Find the absolute path to uvx with which uvx; GUI hosts do not read your shell profile, so MCP configs need the full path.
70 tools, grouped by service. All return the same result envelope (see Result envelope). Call system_status first if unsure what is configured. The server also publishes thirteen named workflow prompts.
Cross-service
system_status - which services are configured/reachable, the Google authmethod and scopes, whether destructive mode is on, the full tool catalog, and the list of registered prompts.
<details> <summary><b>📂 Browse all 70 tools</b> — grouped by service (GSC · GA4 · AI/GEO · keyword discovery · content · PageSpeed/CrUX · Cloudflare · IndexNow · technical · schema). Click to expand.</summary> <br>
<a name="gsc"></a> Google Search Console (18)
Workhorses
gsc_list_properties - properties the credentials can see, with permissionlevel and a derived writable flag (true for siteOwner / siteFullUser).
gsc_search_analytics - the workhorse: clicks/impressions/CTR/position bydimensions, date range, filters, and data_state.
gsc_top_queries / gsc_top_pages - convenience top-N wrappers.gsc_compare_periods - current vs prior window with per-key deltas.v0.2.0 added sort_by, sort_dir, min_delta_clicks / _impressions / _position, anomalies_only + sigma_threshold, and top for one-call movers / losers / outliers reporting.
gsc_inspect_url - URL Inspection (index verdict, coverage, canonicals).gsc_batch_inspect_urls - inspect up to 25 URLs, per-URL failures collected.gsc_list_sitemaps - registered sitemaps and their status.gsc_submit_sitemap - submit a sitemap (write, un-gated; needs the writablescope). Accepts either sitemap_url (friendly) or feedpath (raw API field).
gsc_request_indexing - request (re)crawl via the Indexing API (write,un-gated). Accepts singular url or urls.
Query intelligence (v0.2.0)
gsc_query_opportunities - queries already ranking top N with below-targetCTR. Title and meta optimization candidates.
gsc_query_gaps - queries that draw impressions but barely any clicks.Content opportunity signal.
gsc_new_queries - queries appearing in the current window with no priorimpressions. Emerging topics.
gsc_top_pages_by_query - which pages rank for a specific query. Thecannibalization audit input.
Multi-property + lifecycle (v0.5.0)
gsc_portfolio_summary(days, include?, exclude?) - multi-property fleetview. Per-property one-row summary (clicks, impressions, CTR, position) for the last N days, plus a portfolio-level rollup. Honors optional include / exclude filters. The single fastest answer to "how is the whole portfolio doing?" across agency or multi-brand setups.
gsc_trending_pages(days, limit) - pages whose impressions grew most overthe last N days vs the prior N days. Wrapper on gsc_compare_periods with dimensions=["page"], sort_by="delta_impressions", sort_dir="desc".
gsc_decaying_pages(days, limit) - same wrapper, ascending sort. Pagesto rescue.
gsc_coverage_audit(urls, site_url?) - heuristic coverage audit. The GSCIndex Coverage report is not exposed in the API; this tool takes a user- supplied URL list (typically pulled from a sitemap) and bulk-inspects each, then rolls up verdicts (PASS / PARTIAL / FAIL) and coverage_state frequencies.
<a name="content"></a> Content intelligence (4)
content_opportunities(site_url?, days?, count?, impressions_min?) - ranksdata-grounded content topics from your own Search Console data: fuses CTR-vs-expected gap (curve self-calibrated from your own per-position CTR), striking-distance position, demand, and momentum into a transparent opportunity score; flags cannibalization. If a GA4 property is configured, it also weights each topic by the organic conversions its top page already drives (up to +50%), so topics that convert rank higher; filters_applied.ga4_value_status reports whether that ran and why (applied / no_ga4_property / ga4_unreachable / no_conversions). Prioritizes demand you already have; does not do cold-start keyword research or write the content. Pairs with the content workflow prompts below. (GA4 weighting v0.7.3) v0.9.0 adds an additive per-candidate winnability block (banded: striking-distance + topical- proximity, GSC-personalization tier; existing fields unchanged).
content_brief_data(target_query, competitor_urls?, topic?, site_url?, days?) -data-wired backing for a content brief: fetches the competitor pages (or your own GSC-ranking pages as a fallback) and returns the heading union, median word-count floor, schema types, and entity coverage, plus the 2026 GEO writing directives and validation rules. The host writes the prose; SEOMonster brings rules + evidence. Backs the content_brief prompt. (v0.9.0)
topic_cluster_map(cluster_path | pillar_url, site_url?, days?, impressions_min?)Classifies each cluster query into defend / optimize / create / monitor by demand and best position; the create quadrant is your missing-subtopic list. Flags cannibalization. GSC-only; honest about the ~47% query anonymization. (v0.9.0)
rank_change_attribution(url | urls, change_date, query?, site_url?, pre_days?, post_days?, gap_days?, control_scope?)difference-in-differences against a matched control group (never a naked before/after). Returns an estimated lift with a 95% CI, a three-state verdict (likely_positive / likely_negative / inconclusive), and a confounders block that auto-detects the 2025 GSC data-regime breaks (impression bug, num=100) and downgrades position reliability. Observational, not causal -- a server-side split test is the only true causal test. GSC-only. (v0.9.0)
<a name="ai"></a> AI / GEO citation (3, v0.9.0) - whether the AI answer surfaces reach and cite you.
ai_citation_readiness(url) - is a page structured to be extracted/cited byLLM answer engines? Leads with a render-blindness check (GPTBot / ClaudeBot / PerplexityBot fetch but do not run JS, so a client-rendered SPA is invisible to them), then scores evidence-backed signals (statistics, quotations, cited sources, no keyword-stuffing). schema.org / FAQ / llms.txt are reported as informational only -- the 2026 evidence does not support them as AI-citation drivers, so they are not scored. Free, HTTP-only.
ai_referral_overview(property_id?, site_url?, days?) - first-party AI traffic:GA4 referral sessions from AI apps (the native ai-assistant channel plus a configurable source-host regex) and AI-crawler robots coverage (GPTBot, ClaudeBot, PerplexityBot, ...). Surfaces the ~70% dark-traffic undercount and keeps AI-Overview clicks (counted as Organic) separate. Free.
ai_citation_track(prompts, brand, brand_domains?, competitors?, engines?, samples?)(Perplexity / OpenAI / Anthropic / Gemini APIs + Google AI Overviews via DataForSEO) for a managed prompt set, vs competitors. N samples/prompt (default 7) with a 95% CI, share-of-voice, and run-to-run volatility -- NOT an "AI rank" (single runs are statistically meaningless). Discloses that developer-API output differs from the logged-in consumer UI and that AIO has no API. Paid + non-deterministic. Needs at least one engine key and/or DataForSEO.
<a name="discovery"></a> Keyword discovery (3, v0.9.0) - find terms you don't already rank for.
gsc_keyword_expand(candidates, site_url?, days?, impressions_min?) - you(the host) brainstorm candidate terms from your winning queries; this grounds each against your own Search Console data (footprint covered / thin / none) with a sibling-strength confidence band. "none" = no VISIBLE footprint (GSC hides ~75% of impressions), so net-new terms are scored hypotheses. Free.
serp_adjacency_expand(seeds, include_paa?) - expand seed terms into adjacentterms. FREE core: Google Autocomplete (no key). Optional People-Also-Ask + related searches via DataForSEO. Returns per-seed suggestions plus the aggregated net-new terms; degrades gracefully without a key.
keyword_universe(target_domain?, competitors?, keywords?, limit?) - optional,paid. Core value: the competitor keyword GAP (DataForSEO Domain Intersection; no Google equivalent). Optional search volume / difficulty / intent via a provider chain (DataForSEO, else Google Ads volume-only). External volume is a degraded directional signal -- a tiebreaker, never a gate.
<a name="ga4"></a> Google Analytics 4 (7)
ga4_run_report - the workhorse: arbitrary dimensions/metrics/date range,optional dimension filter and ordering.
ga4_top_landing_pages - top landing pages, organic-only by default.ga4_traffic_by_channel - sessions/engagement/conversions by channel group.ga4_organic_search_overview - organic totals plus a day-by-day trend.ga4_setup_audit(property_id?) - read-only SEO-measurement-readiness audit:web data stream, key events, data retention, content-group dimensions, and (v0.7.4) enhanced measurement, internal site search, and Google Signals. Severity-graded with a benign exception per finding. Uses the GA4 Admin API over REST (analytics.readonly; no extra dependency). (v0.7.0)
ga4_site_search(days?, limit?) - internal site-search query report (adirect content-gap signal); honest envelope when no real search terms. (v0.7.1)
ga4_landing_page_conversions(days?, organic_only?, limit?) - organiclanding pages ranked by conversions. (v0.7.1)
<a name="psi"></a> PageSpeed Insights (2)
psi_analyze - Lighthouse scores, lab Core Web Vitals, and field (CrUX) CoreWeb Vitals for a URL. Defaults to the mobile strategy. Field data carries a field_data_note: Google is deprecating PSI field data, so use crux_snapshot / crux_history for durable field metrics.
psi_opportunities(url, strategy?) - the actionable Lighthouse "opportunity"audits (with estimated savings) plus the SEO-category audits, severity-graded. Lab data only. An on-page-basics checklist, not a ranking predictor. (v0.7.1)
<a name="cf"></a> Cloudflare (13)
cf_list_zones - zones the token can see.cf_zone_info - status, plan, name servers for a zone.cf_list_dns - DNS records (read-only); useful for verifying canonical hostand TXT verification records during migrations.
cf_web_analytics - read-only edge Web Analytics (RUM), to compare againstGA4. Cloudflare returns host: null for some sites; pass the site_tag to look those up explicitly.
cf_purge_cache - purge specific URLs (gated).cf_purge_cache_all - purge an entire zone (gated + confirm token).cf_settings_audit(zone?) - read-only audit of SEO-relevant Cloudflare zonesettings (SSL mode, Always-Use-HTTPS, HSTS, Automatic HTTPS Rewrites, Brotli, cache TTL). Severity-graded with a "verify, not fail" discipline because CF cannot see the origin; HSTS is never a hard failure. Needs Zone Settings Read on the token. Each finding carries a machine-readable fix hint (the exact cf_settings_update setting + recommended value) to chain audit -> fix. (v0.7.1)
cf_settings_update(settings, zone?, confirm?, acknowledge_hsts_risk?, dry_run?)HTTPS, Automatic HTTPS Rewrites, Brotli, browser cache TTL, HSTS), closing the audit -> remediate loop. Gated. ssl_mode or any HSTS-raise needs confirm=<zone> (HSTS-raise also needs acknowledge_hsts_risk=true); validates locally, supports dry_run, and re-runs the audit so you see the finding clear. Needs Zone Settings:Edit (vs the audit's Read). (v0.7.10)
cf_list_redirects(zone?) - list a zone's single (dynamic) redirect rulesplus the account's Bulk Redirect lists (read-only). Call before any redirect write so nothing is clobbered. (v0.7.8; bulk lists added v0.7.9)
cf_create_redirect(source, target, status_code?, ...) - create one edgeredirect (e.g. a 301 for a renamed URL). Gated. Pre-flights the target (no redirecting to a dead URL), refuses loops/duplicates, supports dry_run. (v0.7.8)
cf_delete_redirect(rule_id, zone?) - remove a single-redirect rule by id(rollback for cf_create_redirect). Gated. (v0.7.8)
cf_bulk_redirect_upsert(items, list_name, confirm, ...) - create/append manyredirects at once via an account-level Bulk Redirect List (for migrations). Gated + a confirm token equal to list_name. Validates every item locally first and rejects the whole batch on any bad item (never half-applies); supports dry_run. (v0.7.9)
cf_managed_robots(action, zone?, ...) - get / configure / disableCloudflare's managed robots.txt and Content-Signals policy (these ride on the zone's Bot Management config). action="get" reads the current state (read-only, un-gated). action="configure" sets the managed robots.txt (managed_robots), the Content-Signals variant (cf_robots_variant: off / policy_only), and the AI-bot blocking levers (ai_bots_protection, content_bots_protection, crawler_protection). action="disable" turns the managed robots.txt and the policy back off. Managed robots.txt and the Content-Signals policy are mutually exclusive in Cloudflare, so the valid combinations are managed_robots=true + cf_robots_variant="off" (managed robots.txt) OR managed_robots=false + cf_robots_variant="policy_only" (the policy); the tool rejects the invalid combo locally with INVALID_INPUT. A custom Content-Signal line (e.g. from robots_ai_posture) is not a managed option - put that in your origin robots.txt. Writes are gated, need confirm=<zone>, and support dry_run; reads are safe (GET -> overlay -> PUT, so nothing else in the config is clobbered). Every response carries a caveat separating the stated-preference signals (Content-Signals, honored only by adopting crawlers and ignored by Googlebot) from the levers that actually enforce at the edge. Needs Bot Management:Edit for writes (Read for get). (v0.8.2)
<a name="indexnow"></a> IndexNow (2, v0.2.0)
indexnow_submit(url) - submit a single URL to Bing, Yandex, Naver, Seznam,Yep. Complements (does not replace) gsc_request_indexing, which only talks to Google. Requires SEO_MCP_INDEXNOW_KEY plus a verification file at https://<your-host>/<key>.txt (see IndexNow setup for the full key + file format + same-host rules).
indexnow_bulk_submit(urls) - up to 10,000 URLs sharing one host in asingle POST. Mixed-host batches are rejected client-side with INVALID_INPUT before any network call. The SEO_MCP_INDEXNOW_KEY_LOCATION env var overrides the default verification-file URL when your CDN rewrites /key.txt paths.
<a name="technical"></a> Technical SEO (8, v0.3.0) - no credentials needed; built-in HTTP client.
inspect_meta(url) - on-page surface in one call: title, meta description,meta robots, canonical, Open Graph + Twitter Card tags, hreflang, H1 count.
check_canonical(url) - canonical-link audit: self-referential / cross-host/ protocol-mismatched / trailing-slash drift / canonical target reachable.
mixed_content_check(url) - parses an HTTPS page and flags any http://references (img / script / iframe / form action / srcset). No-op for http://.
redirect_chain_audit(url, max_redirects=10) - walks the chain hop by hop.Flags long chains, protocol downgrades, loops, non-2xx terminus.
robots_txt_validate(site_url, probes?) - parses robots.txt (per-grouprules + sitemaps + Content-Signals), optionally verdicts (user_agent, url) probes using RFC 9309 longest-match (matches what Google + Bing actually do, not stdlib's first-match). Also detects a stale edge-cached robots.txt (cache-bust comparison; re-parses from the fresh content) and a Cloudflare Managed robots.txt / Content-Signals policy overriding your origin - catching false-clean robots on migrated/CF-fronted sites. (v0.8.0)
sitemap_validate(sitemap_url) - validates a sitemap or sitemap-index XML,counts entries, flags oversize + cross-host + missing lastmod. .gz transparent.
sitemap_health(sitemap_url, sample_size=25) - sample-HEAD audit. Statushistogram + first non-2xx examples.
robots_ai_posture(goal?, sitemap_url?) - deterministic, offline advisor forthe Content-Signals levers (search / ai-input / ai-train). Takes a business goal (content_authority default / maximize_visibility / protect_ip), recommends a posture with a plain-language rationale, lays out the trade-off alternatives, and emits a ready-to-apply artifact: the Content-Signal: directive line plus a full suggested robots.txt. No network, no writes. Every response carries the mandatory caveat that Content-Signal is honored only by adopting crawlers, is ignored by Googlebot, and is not a ranking factor. (v0.8.1)
<a name="crux"></a> Chrome UX Report (2)
crux_history(url? | origin?, form_factor?, metrics?) - 25 weeks of p75Core Web Vitals via the CrUX History API. Reuses PSI_API_KEY; works anonymously at a tighter rate limit when no key is configured.
crux_snapshot(url? | origin?, form_factor?) - the current p75 Core WebVitals (point-in-time, vs the history). Each metric reports a category (GOOD / NEEDS_IMPROVEMENT / POOR); the rolled-up rating is overall_category. Time metrics use p75_ms; the unitless CLS uses p75. Small origins return a no_data envelope. (v0.7.1)
Structured data + cross-site + on-page (7) - no new credentials (the v0.9 SERP auto-fetch is optional).
inspect_schema(url) - extract every JSON-LD block from a page; reportthe schema.org @type counts and a sample entity per type.
validate_schema(url, types?) - verdict each JSON-LD entity against theGoogle Rich Results required-field set. Covers Article, NewsArticle, BlogPosting, Product, FAQPage, BreadcrumbList, Organization, LocalBusiness, Event, Review, Recipe. Per-entity verdict plus missing_required and missing_recommended lists.
hreflang_consistency_check(urls) - cross-page hreflang audit on auser-supplied URL set. Flags missing reciprocity, broken hreflang targets, duplicate hreflang on one page, missing self-link, missing x-default when there are 3+ language variants.
internal_link_graph(start_url, max_depth=2, max_pages=50) - smallBFS crawl within the same host. Per-page in-degree + out-degree, orphan pages, broken internal links, depth distribution. Hard caps (max_depth <= 4, max_pages <= 200) so a misuse never melts the host.
lighthouse_budget(url, budget) - wraps psi_analyze and verdictsthe results against a budget dict, e.g. {performance: 80, LCP_ms: 2500, CLS: 0.1}. Per-metric pass/fail and an overall verdict. Useful as a CI / pre-deploy gate inside an LLM session. Reuses PSI_API_KEY.
internal_link_recommend(start_url, site_url?, days?, position_min?, position_max?, relevance_floor?, limit?)to GSC striking-distance pages (default position 8-20 with real impressions), with anchor text. Ranks sources by lexical relevance + internal authority, skips pages that already link the target, balances anchor text, and never suggests nofollow. Built on internal_link_graph + GSC. Free. (v0.9.0)
onpage_serp_gap(target_url, query?, competitor_urls?, max_competitors?) - theheadings, entities, and schema the top SERP results have that a target page lacks, turned into on-page actions. FREE with caller-supplied competitor_urls; optional DataForSEO SERP auto-fetch by query, which also returns winnability signals (serp_composition AI-Overview / UGC zero-click risk) and, with Open PageRank, competitor domain authority. Surfaces information gain, not just parity. Boilerplate/nav headings are filtered out (pattern-class chrome filter). (v0.9.0; F6 fix v0.9.1, F1 generalized v0.9.2)
Every tool's tools/list entry carries the MCP standard annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) so MCP hosts can decide what to auto-approve and what to confirm.
</details>
The server publishes thirteen named MCP prompts (via prompts/list / prompts/get) that chain the granular tools into common SEO workflows. Hosts that surface prompts (Claude Desktop's slash menu, Cursor's command palette, Cline's prompt picker) advertise them automatically.
| Prompt | Arguments | Chains |
|---|---|---|
post_deploy_verify | urls, zone?, skip_psi? | cf_purge_cache -> gsc_request_indexing -> indexnow_bulk_submit -> psi_analyze |
weekly_review | days?, site_url? | gsc_compare_periods (gainers + losers via sort_dir) -> gsc_query_opportunities -> gsc_query_gaps -> ga4_organic_search_overview |
content_audit | site_url?, days?, top_n_queries? | gsc_top_queries -> per-query gsc_top_pages_by_query -> cannibalization recommendation |
migration_check | urls, site_url? | gsc_batch_inspect_urls -> gsc_list_sitemaps -> canonical-agreement table -> remediation list |
technical_seo_audit | url | inspect_meta -> check_canonical -> redirect_chain_audit -> mixed_content_check -> robots_txt_validate -> sitemap_health -> severity-ranked triage list |
structured_data_audit | urls | per-URL inspect_schema -> validate_schema -> (if 2+ URLs) hreflang_consistency_check -> per-URL + cross-URL report |
pre_deploy_check | urls | robots_txt_validate -> per-URL inspect_meta -> check_canonical -> validate_schema -> redirect_chain_audit -> mixed_content_check -> deploy-gate verdict (block on critical issues, approve otherwise) |
content_brief (v0.7.1) | topic, target_query, site_url? | gsc_top_pages_by_query -> inspect_meta / inspect_schema on top rankers -> brief with required sections + validation rules |
content_outline (v0.7.1) | brief | outline with rules: >=5 H2, >=70% target-query coverage, H1 has the primary keyword |
content_article (v0.7.1) | outline, brief | article with rules: word count within +/-15%, per-section minimum, internal links, inline JSON-LD hint, no em-dashes |
content_workflow (v0.7.1) | site_url?, days? | content_opportunities -> brief -> outline -> article -> pre_deploy_check -> gsc_request_indexing + indexnow_submit -> scheduled content_performance |
content_performance (v0.7.1) | url, target_queries?, site_url? | gsc_compare_periods + gsc_search_analytics before / after the publish window |
seo_setup_audit (v0.7.1) | site_url?, property_id? | ga4_setup_audit -> cf_settings_audit -> psi_opportunities -> robots_txt_validate -> consolidated stack-config report |
Why prompts and not megatools: composability. A failed step inside a megatool poisons the megatool's envelope and the host loses the ability to retry just the failing leg. Prompts hand the host a recipe; each step's envelope arrives intact at the LLM.
SEOMonster ships two install paths, both fully local:
credentials, secret-typed inputs stored in the OS keychain. Recommended for most users.
to hand-edit MCP config files.
Both paths run the same Python package (seo_mcp) and expose the same 52-tool surface. The difference is only how the host launches the server and how it collects credentials.
.mcpb bundleThree short steps. The OAuth consent is run once from a terminal (the GUI flow inside Claude Desktop's MCP subprocess times out before a real user can finish; see Why pre-flight auth? below).
1. Install the bundle. Download seo-monster-0.2.0.mcpb from GitHub releases (or, when listed, from the Claude Directory) and double-click it. Claude Desktop verifies the bundle, runs uv to materialize the Python environment, and shows a configuration form:
| Field | Type | Required | Notes |
|---|---|---|---|
| Google OAuth Client Secrets | file picker | yes | Desktop-app client-secrets JSON from Google Cloud Console. |
| Google OAuth Token Cache Path | string | yes | Defaults to ~/.config/seo-monster/token.json. Written on consent. |
| GSC Default Property | string | no | e.g. sc-domain:example.com or https://www.example.com/. |
| GA4 Default Property ID | string | no | properties/123456789 or bare 123456789. |
| PageSpeed Insights API Key | string, secret | no | Stored in the OS keychain. Strongly recommended (why?). |
| Cloudflare API Token | string, secret | no | Stored in the OS keychain. Required only for the Cloudflare tools. |
| Cloudflare Default Zone | string | no | e.g. example.com. |
| IndexNow Key | string, secret | no | Required only for IndexNow tools. Any 8-128 hex string you generate. |
| IndexNow Key File URL | string | no | Override the default verification location (https://<host>/<key>.txt). |
Fill the fields, click Save, then toggle the extension on. Quit Claude Desktop completely (⌘Q on macOS) and reopen.
2. Run the one-time OAuth consent from a terminal. Before using any Google-backed tool, run:
uvx seo-monster authA browser opens. Approve the requested scopes. The command writes token.json to the path you configured (default ~/.config/seo-monster/token.json) with 0600 permissions, then exits. This step is the recommended pattern; it sidesteps the timeout that Claude Desktop imposes on every tool call.
3. Start a new chat in Claude Desktop and use the tools. Click the 🔧 tools icon in the input box; you should see 52 SEOMonster tools. Try system_status first to verify everything is configured.
#### Why pre-flight auth?
The OAuth installed-app flow opens a local browser and waits for the user to finish the consent screen. Inside Claude Desktop, MCP servers are launched as subprocesses whose tool calls have a ~30-60 second timeout. Real users do not complete browser consent that fast, so the originating call times out, and since every Google tool retries the flow until a token exists, every call times out in turn. Running uvx seo-monster auth once from a terminal puts the token on disk; from that point on, Claude Desktop's MCP server just reads the cached token and silently refreshes it as needed.
uvx for Cursor, Cline, Codex (and Claude Desktop power users)uvx runs the published PyPI package seo-monster in an ephemeral environment. Add the snippet for your host below, using the absolute path to uvx (find it with which uvx; GUI hosts do not read your shell profile).
#### Cursor (~/.cursor/mcp.json or project .cursor/mcp.json)
{
"mcpServers": {
"seomonster": {
"command": "/Users/me/.local/bin/uvx",
"args": ["seo-monster"],
"env": {
"SEO_MCP_GOOGLE_OAUTH_CLIENT": "/Users/me/.config/seo-monster/client_secret.json",
"SEO_MCP_GOOGLE_TOKEN": "/Users/me/.config/seo-monster/token.json",
"SEO_MCP_GA4_PROPERTY_ID": "properties/123456789",
"PSI_API_KEY": "AIza...",
"CF_API_TOKEN": "..."
}
}
}
}#### Cline (cline_mcp_settings.json)
{
"mcpServers": {
"seomonster": {
"command": "/Users/me/.local/bin/uvx",
"args": ["seo-monster"],
"env": {
"SEO_MCP_GOOGLE_OAUTH_CLIENT": "/Users/me/.config/seo-monster/client_secret.json",
"SEO_MCP_GOOGLE_TOKEN": "/Users/me/.config/seo-monster/token.json"
},
"alwaysAllow": ["system_status", "gsc_search_analytics", "ga4_run_report", "psi_analyze"]
}
}
}alwaysAllow lists read tools so Cline does not prompt on each call. Leave the cache-purge tools off so they always prompt.
#### Codex (~/.codex/config.toml)
[mcp_servers.seomonster]
command = "/Users/me/.local/bin/uvx"
args = ["seo-monster"]
[mcp_servers.seomonster.env]
SEO_MCP_GOOGLE_OAUTH_CLIENT = "/Users/me/.config/seo-monster/client_secret.json"
SEO_MCP_GOOGLE_TOKEN = "/Users/me/.config/seo-monster/token.json"
SEO_MCP_GA4_PROPERTY_ID = "properties/123456789"#### Claude Desktop, direct uvx (advanced)
If you prefer to hand-edit claude_desktop_config.json instead of using the .mcpb bundle, the same snippet shape as Cursor above works.
<a name="auth"></a>
The four services authenticate independently. Configure only the ones you use; a tool for an unconfigured service returns a clear AUTH_MISSING error rather than failing the server.
seo-monster setupRun seo-monster setup once from a terminal. It interactively collects your Cloudflare token, PageSpeed Insights key, IndexNow key, and the default GSC and GA4 properties, validates what it can against the live APIs, and writes them to ~/.config/seo-mcp/config.toml with 0600 permissions. Your MCP host config then needs no secrets in it:
{ "command": "uvx", "args": ["seo-monster"] }Two things setup does not do, by design:
setup,run seo-monster auth to complete Google consent (see the next section).
envblock still wins over the config file, so CI and Docker keep using env vars.
setup is re-runnable: existing values are shown as defaults and kept when you leave a field blank. The sections below document the per-service env vars, which are what setup writes for you and what CI pipelines can set directly.
This is the lower-friction path: no Cloud service account, no per-property email grants.
pick) a project and enable the APIs you will use:
gsc_request_indexing)JSON.
SEO_MCP_GOOGLE_OAUTH_CLIENT = path to the client-secrets JSONSEO_MCP_GOOGLE_TOKEN = a writable path where the token will be cacheduvx seo-monster auth from a terminal. A browser opens;approve the scopes. The command writes token.json (0600) and exits.
never opens a browser**; if the cached token is missing, tools return AUTH_MISSING pointing back at the auth command.
The signed-in Google account must have access to the Search Console properties and GA4 properties you query.
Token-cache hardening. The cached token is refresh-capable and equivalent to a long-lived credential for the requested scopes. The server writes it with 0600 and its parent directory with 0700. Keep SEO_MCP_GOOGLE_TOKEN under a directory you control (e.g. ~/.config/seo-monster/) and do not put it on a shared filesystem.
For fully headless or server deployments where a browser is not available:
SEO_MCP_GOOGLE_CREDENTIALS (or the standardGOOGLE_APPLICATION_CREDENTIALS) to the key path.
If both OAuth and a service account are configured, OAuth is used.
Coverage note. The OAuth installed-app path is exercised in our validation pass and in production-style smoke tests. The service-account path is documented but not independently validated against a live Cloud project. If you hit issues on the SA path, please open an issue.
The default consent requests the scopes needed for every tool, including the two writes:
| Capability | Scope |
|---|---|
| GSC read | webmasters (covers readonly) |
| GSC sitemap submit | webmasters |
| GSC indexing request | indexing |
| GA4 reporting | analytics.readonly |
If you only want reads, you can consent to a narrower set (webmasters.readonly + analytics.readonly) and simply not call gsc_submit_sitemap / gsc_request_indexing; calling a write tool without its scope returns SCOPE_INSUFFICIENT with remediation, never a crash.
PSI works without a key in principle, but in practice the anonymous quota is shared across every caller without a key and is frequently exhausted: a single psi_analyze call against the anonymous endpoint often returns RATE_LIMITED. Treat the anonymous mode as a fallback, not the steady state.
To get reliable PSI access:
takes a minute. The key is free.
PSI_API_KEY (or use the field in the .mcpb configuration form).The PSI API only accepts the key as a URL query parameter (not a header), so treat PSI keys as low-sensitivity. Scope the key to the PageSpeed Insights API only and attach no other GCP roles.
Create an API token at dash.cloudflare.com/profile/api-tokens and set CF_API_TOKEN (and optionally CF_ZONE for a default zone). Grant only the permissions you need:
| Permission | Needed for |
|---|---|
Zone: Zone:Read | cf_list_zones, cf_zone_info |
Zone: DNS:Read | cf_list_dns |
Account: Account Analytics:Read | cf_web_analytics |
Zone: Cache Purge:Purge | cf_purge_cache, cf_purge_cache_all (only if you enable destructive mode) |
Zone: Single Redirect:Edit | cf_create_redirect, cf_delete_redirect (only if you enable destructive mode); cf_list_redirects reads with it |
Zone: Zone Settings:Edit | cf_settings_update (only if you enable destructive mode); cf_settings_audit only needs Zone Settings Read |
Account: Account Rulesets:Edit + Account Filter Lists:Edit | cf_bulk_redirect_upsert (only if you enable destructive mode) |
IndexNow notifies Bing, Yandex, Naver, Seznam, and Yep when a URL is created or updated. Google does not participate, so the IndexNow tools complement rather than replace gsc_request_indexing.
#### One-time setup
hyphens (a-z, A-Z, 0-9, -) per the IndexNow spec. Common patterns: a 32-char lowercase hex string (e.g. python -c "import secrets; print(secrets.token_hex(16))") or any random alphanumeric of similar length. Treat it like an API key; do not commit it.
SEO_MCP_INDEXNOW_KEY to that string(or use the .mcpb configuration form; the field is marked sensitive and lands in the OS keychain).
https://<your-host>/<key>.txt. Thefile body MUST be exactly the key string with no trailing newline, no BOM, no extra whitespace, no HTML wrapper. The Content-Type should be text/plain. Confirm with curl -i https://<your-host>/<key>.txt before moving on; the response body must be byte-identical to the key.
SEO_MCP_INDEXNOW_KEY_LOCATION if the verificationfile lives at a non-standard URL (some CDNs rewrite /key.txt paths). The default location is https://<host>/<key>.txt derived from the URLs you submit, so you usually do not need this.
#### Same-host constraint
Every URL submitted in one indexnow_submit or indexnow_bulk_submit call must share the same host as the verification file. Mixed-host batches are rejected by IndexNow with HTTP 422; indexnow_bulk_submit enforces this client-side and returns INVALID_INPUT before any network call when it detects mixed hosts.
If you have multiple hosts, host a verification file per host and either make separate calls per host or override SEO_MCP_INDEXNOW_KEY_LOCATION per call (the tool does not currently expose per-call override; set distinct env values per session).
#### Common errors
| Symptom | Likely cause |
|---|---|
AUTH_INVALID from indexnow_submit | Engines could not fetch https://<host>/<key>.txt. Confirm the file returns HTTP 200 with the exact key as the body |
INVALID_INPUT from indexnow_bulk_submit mentioning mixed hosts | URL list spans multiple hosts; split into per-host batches |
RATE_LIMITED | Hit IndexNow's per-host rate cap. Wait before retrying |
After configuring, call system_status to see what is detected. Call it with {"probe": true} to make one cheap live request per configured service and confirm the credentials actually work (GSC lists properties, GA4 runs a 1-row report against the default property, Cloudflare lists one zone, PSI pings the endpoint). With probe off (the default) it does a config-only check and makes no network calls.
<a name="destructive-mode"></a>
Cache purges affect every visitor, so they are off by default. Set SEO_MCP_ALLOW_DESTRUCTIVE=true to enable cf_purge_cache and cf_purge_cache_all. While off, those tools return DESTRUCTIVE_DISABLED and make no network call.
cf_purge_cache_all (purge the whole zone) carries an extra safeguard: it requires a confirm argument equal to the resolved zone hostname. A missing or mismatched confirm returns CONFIRM_REQUIRED and issues no purge.
The two GSC writes (gsc_submit_sitemap, gsc_request_indexing) are not gated; they are routine, low-blast-radius SEO tasks.
<a name="configuration"></a>
Resolution is environment-first, with a TOML file fallback. Environment always wins. The config file is normally written for you by seo-monster setup (with 0600 permissions); you can also write it by hand or set the env vars below.
| Env var | Service | Purpose |
|---|---|---|
SEO_MCP_GOOGLE_OAUTH_CLIENT | OAuth client-secrets JSON path (recommended). | |
SEO_MCP_GOOGLE_TOKEN | Writable cached-token path (OAuth). | |
SEO_MCP_GOOGLE_CREDENTIALS | Service-account key path (alternative). | |
GOOGLE_APPLICATION_CREDENTIALS | Standard service-account fallback. | |
SEO_MCP_GSC_DEFAULT_SITE | GSC | Default property, e.g. sc-domain:example.com. |
SEO_MCP_GA4_PROPERTY_ID | GA4 | Default property, e.g. properties/123456789. |
SEO_MCP_DATA_STATE | GSC | all (default) or final. |
PSI_API_KEY | PSI | PageSpeed Insights API key (optional). |
CF_API_TOKEN | CF | Cloudflare API token. |
CF_ZONE | CF | Default zone hostname. |
SEO_MCP_INDEXNOW_KEY | IndexNow | Shared key for the IndexNow tools (sensitive). |
SEO_MCP_INDEXNOW_KEY_LOCATION | IndexNow | Override default key-file URL (optional). |
SEO_MCP_ALLOW_DESTRUCTIVE | all | true enables cache-purge tools. Default off. |
SEO_MCP_CONFIG | all | Path to the TOML config file. |
DATAFORSEO_LOGIN / DATAFORSEO_PASSWORD | DataForSEO | Optional (v0.9): SERP/PAA, keyword volume/difficulty/intent, competitor gap, Google AIO. |
OPENPAGERANK_API_KEY | Open PageRank | Optional (v0.9): free competitor domain authority. |
PERPLEXITY_API_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY | AI engines | Optional (v0.9): engines for ai_citation_track (any subset). |
GOOGLE_ADS_DEVELOPER_TOKEN / GOOGLE_ADS_CUSTOMER_ID | Google Ads | Optional (v0.9): volume alt to DataForSEO (needs adwords-scope consent). |
Config file fallback at ~/.config/seo-mcp/config.toml (or SEO_MCP_CONFIG):
[google]
oauth_client = "/Users/me/.config/seo-mcp/client_secret.json"
token = "/Users/me/.config/seo-mcp/token.json"
# credentials = "/Users/me/.config/seo-mcp/sa.json" # service-account alternative
[gsc]
default_site = "sc-domain:example.com"
data_state = "all"
[ga4]
property_id = "properties/123456789"
[psi]
api_key = "AIza..."
[cloudflare]
api_token = "..."
zone = "example.com"
[server]
allow_destructive = false
# Optional v0.9 providers (discovery + AI/GEO). The free GSC/GA4/HTTP core
# works without any of these; each tool degrades gracefully when unset.
[dataforseo]
# login = "..."
# password = "..."
[openpagerank]
# api_key = "..."
[ai_engines]
# perplexity = "..."
# openai = "..."
# anthropic = "..."
# gemini = "..."
[google_ads]
# developer_token = "..." # volume alt to DataForSEO; needs adwords-scope OAuth
# customer_id = "..."<a name="errors"></a>
Every tool returns the same shape. On success:
{ "ok": true, "data": { /* tool-specific */ }, "error": null }On failure:
{
"ok": false,
"data": null,
"error": {
"code": "AUTH_MISSING",
"service": "gsc",
"message": "No Google credentials found for Search Console.",
"remediation": "Configure OAuth ... or a service-account key. See README > Auth.",
"docs_url": "https://seomonster.avansaber.com#auth",
"details": null
}
}Error codes:
| Code | Meaning |
|---|---|
AUTH_MISSING | No credential configured for the service. |
AUTH_INVALID | Credential present but rejected (401/403, bad key, expired). |
SCOPE_INSUFFICIENT | Token lacks the scope this tool needs. |
DESTRUCTIVE_DISABLED | A cache-purge tool was called with destructive mode off. |
CONFIRM_REQUIRED | cf_purge_cache_all called without a matching confirm. |
NOT_FOUND | Site / property / zone / record not found or not visible. |
INVALID_INPUT | Argument failed validation (bad date, missing required arg). |
RATE_LIMITED | Upstream 429. |
SERVICE_DISABLED | A Google Cloud API is not enabled; details has the activation URL. |
UPSTREAM_ERROR | Any other non-2xx from an upstream API. |
git clone https://github.com/avansaber/seo-monster
cd seo-monster
uv venv && uv pip install -e ".[dev]"
uv run pytest # offline test suite
uv run seo-monster # run the server over stdio
uv run seo-monster auth # one-time OAuth consent (or `uv run seo-mcp auth`)The package exposes two console-script aliases: seo-monster (canonical, matches the PyPI distribution) and seo-mcp (a v0.1.x dev alias kept for back-compat). Both invoke the same entry point. As of v0.2.0, invoking the server via seo-mcp emits a one-line stderr deprecation notice; nothing on stdout, so the MCP protocol channel is unaffected. Production configs should use seo-monster; the alias will be removed in a future major release.
Tests are fully offline: they mock at the client layer, so no network and no credentials are needed to run them.
Server identity note. Some MCP host UIs display the server name asseo-mcpand the version as themcpSDK version (e.g.1.27.1). The server-name string is the value we passed toServer("seo-mcp")and is kept stable for back-compat; the version readout is a quirk of the SDK (create_initialization_options()does not propagate the package version). The package's real version is inpyproject.tomlandseo_mcp.__version__.
Release-by-release notes, including the validation checks each version's external testing pass should cover, live in CHANGELOG.md.
SEOMonster runs entirely on your machine and talks only to the upstream APIs you configure. The maintainers do not see any of your data, credentials, queries, or tool calls. See PRIVACY.md for the full statement.
MIT. See LICENSE.
<!-- mcp-name: io.github.avansaber/seo-monster -->
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.