# SEO & GEO (npm · seo-geo-mcp-server)

Audit a page's SEO and its readiness for AI answer engines: meta, schema, robots, sitemaps, AI bots.

- Trust score: 73/100 (medium)
- Change this week: +5
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-09-21

## Components

- npm · `seo-geo-mcp-server`: 73/100 (this document), [markdown](https://verifymcp.io/servers/ortamarco-seo-geo-mcp-server/seo-geo-mcp-server.md), [page](https://verifymcp.io/servers/ortamarco-seo-geo-mcp-server/seo-geo-mcp-server)

## Channel facts

- Registry: `npm`
- Package: `seo-geo-mcp-server`
- Version: `1.2.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-09-21.

- **Supply Chain Security**: 98/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - No install/post-install scripts declared.
  - 25 of 96 dependencies flagged as unhealthy (1 deprecated).
- **Provenance & Transparency**: 45/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 9 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 66/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 5645 tokens (~332/item across 17 items; 17 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 33/100
  - Stability observed for 10 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 17 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 18 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).

## Install

### How do I install the SEO & GEO MCP server?

SEO & GEO runs locally as an npm package, launched with npx -y seo-geo-mcp-server. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add ortamarco-seo-geo-mcp-server -- npx -y seo-geo-mcp-server
```

### Cursor

```json
{
  "mcpServers": {
    "ortamarco-seo-geo-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "seo-geo-mcp-server"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "ortamarco-seo-geo-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "seo-geo-mcp-server"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add ortamarco-seo-geo-mcp-server -- npx -y seo-geo-mcp-server
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "ortamarco-seo-geo-mcp-server": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "seo-geo-mcp-server"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add ortamarco-seo-geo-mcp-server --command npx --arg -y --arg seo-geo-mcp-server
```

### Hermes

```yaml
mcp_servers:
  ortamarco-seo-geo-mcp-server:
    command: "npx"
    args: ["-y", "seo-geo-mcp-server"]
```

### Netclaw

```json
{
  "McpServers": {
    "ortamarco-seo-geo-mcp-server": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "seo-geo-mcp-server"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add ortamarco-seo-geo-mcp-server -t stdio -c npx -a -y seo-geo-mcp-server
```

### Other

```json
{
  "mcpServers": {
    "ortamarco-seo-geo-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "seo-geo-mcp-server"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-21 (score 73, +1)

No change was recorded against any check on this day. Stability & Change Management went from 30 to 33. That category is still filling its 30-day observation window: 9 days of observed history at the previous scan, 10 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-19 (score 72, +4)

- [functional improvement] Stability: unverified → 0.27

### 2026-09-13 (score 68, +15)

- [security improvement] Malware scan: unverified → pass

### 2026-09-12 (score 53, 0)

- [functional regression] Security disclosure: unverified → fail

### 2026-09-11 (score 53)

First indexed and scored.

## MCP tools (17)

### `seo_audit` (~382 tokens)

Full On-Page SEO Audit

Fetch a page once and audit it across seven weighted sections — meta tags & social preview, heading structure, content quality, structured data, image SEO, links and crawlability — returning a 0–100 score, an A–F grade and a prioritised fix list.

This is the tool to start with for any "how is this page doing for SEO?" question; drill into the single-purpose tools afterwards for detail.

Args:
  \- url (string): the page to audit.
  \- include_geo (boolean): also score AI answer-engine readiness (default false).
  \- check_broken_links (boolean): sample-verify that links resolve (default false).
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { score, grade, indexable, sections[{id, label, score, grade, weight, issues[]}], top_recommendations[], geo, findings[] }.

Example: "Audit the SEO of https://example.com/pricing" -> seo_audit(url="https://example.com/pricing").
Note: a noindex page or a site-wide robots.txt block caps the score, because nothing else matters until that is fixed.
Errors: returns an error if the URL is unreachable, non-HTML, or returns an HTTP error.

Input parameters:

- `check_broken_links` (boolean): Sample up to 25 links and verify they resolve. Slower, but catches dead links.
- `include_geo` (boolean): Also score GEO (AI answer-engine) readiness. Adds ~2 requests.
- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `fetch_ms` (number)
- `final_url` (string)
- `findings` (array)
- `geo`
- `grade` (string)
- `indexable` (boolean)
- `redirect_hops` (number)
- `score` (number)
- `sections` (array)
- `status` (number)
- `top_recommendations` (array)
- `url` (string)

### `geo_audit` (~369 tokens)

GEO / AI Answer-Engine Readiness Audit

Score how readily an AI answer engine (ChatGPT, Claude, Perplexity, Gemini, Copilot) can fetch, parse and cite this page. Weighted across: AI crawler access (25), server-rendered content (20), structured data (15), extractable structure (15), authorship & entity signals (10), freshness (8) and content depth (7).

Two things this catches that a classic SEO tool does not:
  \- Pages that rank fine in Google but are invisible to AI assistants, because most AI crawlers do not execute JavaScript and the content only appears after hydration.
  \- robots.txt rules that block AI *search* crawlers (OAI-SearchBot, Claude-SearchBot, PerplexityBot) — the ones that build citation indexes — as opposed to the *training* crawlers people usually mean to block.

Args:
  \- url (string): the page to audit.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { geo{score, grade, signals[], top_recommendations[]}, rendering, crawler_access, llms_txt, robots }.

Example: "Is https://example.com/guide ready to be cited by ChatGPT?" -> geo_audit(url="https://example.com/guide").
Note: llms.txt presence is reported but deliberately NOT scored — it is a community proposal with no committed vendor support, and Google has stated it does not use it.

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `crawler_access`
- `geo` (object)
- `llms_txt`
- `rendering` (object)
- `robots`

### `ai_crawler_access` (~592 tokens)

AI Crawler Access Check

Resolve every known AI/LLM crawler against a site's robots.txt and report which may fetch a given path. Covers OpenAI (GPTBot, OAI-SearchBot, ChatGPT-User, OAI-AdsBot), Anthropic (ClaudeBot, Claude-User, Claude-SearchBot), Google (Google-Extended, Googlebot, Google-CloudVertexBot), Perplexity, Apple, Meta, Amazon, Mistral, Common Crawl, ByteDance and others.

Three things this gets right that a naive robots.txt reader does not:

  1\. **Training vs citation.** Blocking GPTBot stops training; blocking OAI-SearchBot stops you being *cited* in ChatGPT search. Most people want the first, not the second. Blocked citation-critical bots are called out separately.
  2\. **Which blocks are actually enforceable.** Perplexity-User, ChatGPT-User and meta-externalfetcher are documented by their own vendors as ignoring or possibly ignoring robots.txt. A "blocked" verdict for those is advisory, and is reported as such rather than as a clean block.
  3\. **Vendor quirks.** Apple documents that when robots.txt has no Applebot group but does have a Googlebot group, Applebot follows the Googlebot rules — so the effective verdict differs from the literal one.

Each crawler also carries its provenance: whether the token comes from first-party vendor documentation or only from community aggregators. Vendors that publish no crawler token at all (xAI/Grok, Microsoft Copilot) are listed separately, because absence of a rule cannot be read as allowed or blocked.

Args:
  \- site (string): domain or any URL on it.
  \- path (string): path to test (default '/').
  \- include_deprecated (boolean): include retired tokens (default false).
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { crawlers[{token, vendor, purpose, allowed, via_wildcard, matched_rule, respects_robots_txt, compliance_note, provenance, quirk}], allowed_count, blocked_count, blocked_citation_critical[], unenforceable_blocks[], undocumented_vendors[], findings[] }.

Example: "Can ChatGPT and…

Input parameters:

- `include_deprecated` (boolean): Also resolve retired tokens (anthropic-ai, claude-web) for historical coverage.
- `path` (string): Path to test the rules against, e.g. '/blog/post'. Defaults to '/'.
- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `site` (string, required): Domain or any URL on it, e.g. 'example.com'.

Output parameters:

- `allowed_count` (number)
- `blocked_citation_critical` (array)
- `blocked_count` (number)
- `crawlers` (array)
- `findings` (array)
- `path` (string)
- `robots_found` (boolean)
- `undocumented_vendors` (array)
- `unenforceable_blocks` (array)
- `url` (string)

### `llms_txt_check` (~373 tokens)

llms.txt Check

Check whether a site publishes /llms.txt and validate it against the llmstxt.org proposal: a required H1 title, an optional blockquote summary, and H2-delimited lists of `- [name](url): notes` links. Also detects /llms-full.txt.

\**Important context this tool always reports:** llms.txt is a community proposal from September 2024, not an adopted standard. No major AI vendor has documented that its crawlers read llms.txt from third-party sites, and Google has publicly stated it does not support it. Publishing one is cheap and may help human readers and some documentation tooling, but it does not earn AI visibility on its own — robots.txt access, structured data and server-rendered content do. Note also that `llms-full.txt` is a de-facto convention popularised by docs tooling, not part of the proposal.

Use this tool to answer "do they publish one, and is it well-formed?" — not as evidence that a site is or is not AI-optimised.

Args:
  \- site (string): domain or any URL on it.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { found, status, full_variant_found, bytes, title, has_summary_blockquote, sections[], link_count, spec_compliant, adoption_status, findings[] }.

Example: "Does example.com publish an llms.txt?" -> llms_txt_check(site="example.com").

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `site` (string, required): Site domain or any URL on it, e.g. 'example.com'. Only the origin is used.

Output parameters:

- `adoption_status` (string)
- `bytes` (number)
- `findings` (array)
- `found` (boolean)
- `full_variant_found` (boolean)
- `has_optional_section` (boolean)
- `has_summary_blockquote` (boolean)
- `link_count` (number)
- `sections` (array)
- `spec_compliant` (boolean)
- `status` (number|null)
- `title` (string|null)
- `url` (string)

### `render_check` (~276 tokens)

JavaScript Rendering Check

Determine whether a page's content exists in the server HTML, or only appears after JavaScript runs.

This matters more for AI visibility than for classic SEO: Googlebot renders JavaScript, but GPTBot, ClaudeBot, PerplexityBot and CCBot largely do not. A client-rendered page can rank perfectly well in Google and still be completely invisible to every AI assistant — this tool is how you catch that.

Detects unhydrated SPA shells (empty #root / #app / #__next containers), reports how many words survive without JS, and flags documents dominated by inline script bytes.

Args:
  \- url (string): the page to check.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { renders_without_js, server_text_words, script_bytes, html_bytes, spa_shell_detected, framework_hint, findings[] }.

Example: "Can ChatGPT actually read https://example.com/app?" -> render_check(url="https://example.com/app").

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `findings` (array)
- `framework_hint` (string|null)
- `html_bytes` (number)
- `renders_without_js` (boolean)
- `script_bytes` (number)
- `server_text_words` (number)
- `spa_shell_detected` (boolean)

### `meta_tags_check` (~230 tokens)

Meta Tags Check

Inspect a page's head tags: title, meta description, canonical, robots directives (meta AND the X-Robots-Tag header), html lang, charset, viewport and favicon. Flags length problems, missing or duplicated tags, and anything that makes the page non-indexable.

Args:
  \- url (string): the page to check.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { title, title_length, description, description_length, canonical, canonical_is_self, meta_robots, x_robots_tag, indexable, followable, lang, charset, viewport, score, grade, findings[] }.

Example: "Are the meta tags on https://example.com correct?" -> meta_tags_check(url="https://example.com").

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `canonical` (string|null)
- `canonical_is_self` (boolean)
- `charset` (string|null)
- `description` (string|null)
- `description_length` (number)
- `favicon` (string|null)
- `final_url` (string)
- `findings` (array)
- `followable` (boolean)
- `grade` (string)
- `indexable` (boolean)
- `lang` (string|null)
- `meta_robots` (string|null)
- `robots_directives` (array)
- `score` (number)
- `title` (string|null)
- `title_length` (number)
- `url` (string)
- `viewport` (string|null)
- `x_robots_tag` (string|null)

### `social_preview_check` (~268 tokens)

Social Preview (Open Graph & Twitter Card) Check

Validate the tags that build link-preview cards on X, LinkedIn, Facebook, Slack, WhatsApp and Discord: og:title, og:description, og:image, og:url, og:type, og:site_name and the twitter:* family. Optionally verifies the preview image actually loads, and flags the classic bug of a relative og:image URL (social scrapers require absolute URLs).

Args:
  \- url (string): the page to check.
  \- check_image (boolean): verify the og:image resolves (default true).
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { open_graph{}, twitter{}, og_image_url, og_image_reachable, og_image_status, score, grade, findings[] }.

Example: "Why does my link preview look broken on LinkedIn?" -> social_preview_check(url="https://example.com/post").

Input parameters:

- `check_image` (boolean): Issue a HEAD request to confirm the og:image actually loads.
- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `final_url` (string)
- `findings` (array)
- `grade` (string)
- `og_image_content_type` (string|null)
- `og_image_reachable` (boolean|null)
- `og_image_status` (number|null)
- `og_image_url` (string|null)
- `open_graph` (object)
- `score` (number)
- `twitter` (object)
- `url` (string)

### `heading_structure` (~228 tokens)

Heading Structure

Extract the full h1–h6 outline and evaluate it: how many h1s, whether levels are skipped (h2 followed by h4), empty heading tags, and how many headings are phrased as questions — the last being a strong signal for featured snippets and AI citations.

Args:
  \- url (string): the page to check.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { headings[{level, text, skips_level}], h1_count, h1_text[], level_skips, empty_headings, question_headings[], outline, score, grade, findings[] }.

Example: "Show me the heading outline of https://example.com/guide" -> heading_structure(url="https://example.com/guide").

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `empty_headings` (number)
- `final_url` (string)
- `findings` (array)
- `grade` (string)
- `h1_count` (number)
- `h1_text` (array)
- `headings` (array)
- `level_skips` (number)
- `outline` (string)
- `question_headings` (array)
- `score` (number)
- `url` (string)

### `structured_data_check` (~281 tokens)

Structured Data (Schema.org) Check

Extract and validate JSON-LD, microdata and RDFa. Reports every @type found, flags JSON-LD blocks that fail to parse (those are invisible to search engines), and checks recognised types against Google's rich-result requirements — required properties that are missing, plus recommended ones worth adding.

Covers Article/BlogPosting/NewsArticle, Product, FAQPage, HowTo, Recipe, Event, Organization, LocalBusiness, Person, WebSite, BreadcrumbList, VideoObject, JobPosting, Course, Review and AggregateRating.

Args:
  \- url (string): the page to check.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { json_ld_blocks, microdata_items, parse_errors[], items[{type, properties[], missing_required[], missing_recommended[], valid}], types_found[], has_organization, has_breadcrumb, score, grade, findings[] }.

Example: "Does https://example.com/product have valid Product schema?" -> structured_data_check(url="https://example.com/product").

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `final_url` (string)
- `findings` (array)
- `grade` (string)
- `has_article` (boolean)
- `has_breadcrumb` (boolean)
- `has_faq` (boolean)
- `has_organization` (boolean)
- `has_person` (boolean)
- `has_website` (boolean)
- `items` (array)
- `json_ld_blocks` (number)
- `microdata_items` (number)
- `parse_errors` (array)
- `rdfa_items` (number)
- `score` (number)
- `types_found` (array)
- `url` (string)

### `content_analysis` (~270 tokens)

Content Quality Analysis

Measure the page's main content: word count, sentence and paragraph counts, Flesch reading ease with a plain-language reading level, estimated reading time, text-to-HTML ratio, thin-content detection, and the top non-stopword terms with their density (English and Spanish stopwords are both filtered).

Content is read from the <main>/<article> landmark when present, so navigation and footer chrome do not inflate the counts.

Args:
  \- url (string): the page to check.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { word_count, sentence_count, paragraph_count, avg_words_per_sentence, reading_ease, reading_level, reading_time_minutes, thin_content, text_to_html_ratio, used_content_landmark, top_terms[{term, count, density}], score, grade, findings[] }.

Example: "Is the content on https://example.com/post too thin?" -> content_analysis(url="https://example.com/post").

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `avg_words_per_sentence` (number)
- `final_url` (string)
- `findings` (array)
- `grade` (string)
- `html_bytes` (number)
- `paragraph_count` (number)
- `reading_ease` (number)
- `reading_level` (string)
- `reading_time_minutes` (number)
- `score` (number)
- `sentence_count` (number)
- `text_to_html_ratio` (number)
- `thin_content` (boolean)
- `top_terms` (array)
- `url` (string)
- `used_content_landmark` (boolean)
- `word_count` (number)

### `image_seo_check` (~242 tokens)

Image SEO Check

Audit every <img> on the page: missing alt attributes (an accessibility failure and a lost image-search signal), decorative alt="" usage, missing width/height (which causes layout shift, a Core Web Vitals factor), lazy-loading adoption, and how many images use modern formats (WebP/AVIF) versus legacy JPEG/PNG. <picture> sources are counted as modern delivery.

Args:
  \- url (string): the page to check.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { total_images, missing_alt, decorative_alt, missing_dimensions, lazy_loaded, modern_format, legacy_format, images[], score, grade, findings[] }.

Example: "Which images on https://example.com are missing alt text?" -> image_seo_check(url="https://example.com").

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `decorative_alt` (number)
- `final_url` (string)
- `findings` (array)
- `grade` (string)
- `images` (array)
- `lazy_loaded` (number)
- `legacy_format` (number)
- `missing_alt` (number)
- `missing_dimensions` (number)
- `modern_format` (number)
- `score` (number)
- `total_images` (number)
- `url` (string)

### `robots_txt_check` (~266 tokens)

robots.txt Check

Fetch and parse a site's robots.txt per RFC 9309. Reports every user-agent group with its Allow/Disallow rules, the declared sitemaps, and any lines that could not be parsed. Flags the two failures that silently deindex a site: a wildcard `Disallow: /`, and a robots.txt that returns 5xx (which Google treats as "disallow everything").

Args:
  \- site (string): domain or any URL on it, e.g. 'example.com'.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { found, status, group_count, sitemaps[], blocks_everything, groups[{agents[], rules[], crawl_delay}], parse_warnings[], findings[] }.

Example: "What does example.com's robots.txt allow?" -> robots_txt_check(site="example.com").
For AI-crawler specifics use `ai_crawler_access` instead — it resolves each known AI bot against these rules.

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `site` (string, required): Site domain or any URL on it, e.g. 'example.com'. Only the origin is used.

Output parameters:

- `blocks_everything` (boolean)
- `findings` (array)
- `found` (boolean)
- `group_count` (number)
- `groups` (array)
- `parse_warnings` (array)
- `sitemaps` (array)
- `status` (number|null)
- `url` (string)

### `sitemap_check` (~343 tokens)

XML Sitemap Check

Discover, fetch and validate an XML sitemap. Finds it via the robots.txt `Sitemap:` directive, then falls back to /sitemap.xml, /sitemap_index.xml and /sitemap-index.xml. Handles sitemap indexes (following children) and gzipped sitemaps.

Validates: URL count against the 50,000 limit, uncompressed size against 50 MiB, <lastmod> presence and W3C-datetime validity, URLs pointing off-origin, http:// URLs, and duplicates.

Args:
  \- site (string): domain or any URL on it.
  \- sitemap_url (string, optional): explicit sitemap URL.
  \- follow_children (number): child sitemaps of an index to follow (default 3).
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { found, type, url_count, child_sitemaps[], with_lastmod, invalid_lastmod[], newest_lastmod, off_origin_urls[], exceeds_url_limit, discovered_via, score, grade, findings[] }.

Example: "Check the sitemap for example.com" -> sitemap_check(site="example.com").

Input parameters:

- `follow_children` (integer): How many child sitemaps of an index to follow (default 3).
- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `site` (string, required): Domain or any URL on it, e.g. 'example.com'.
- `sitemap_url` (string): Explicit sitemap URL. Omit to discover it via robots.txt, then the conventional paths.

Output parameters:

- `bytes` (number)
- `child_sitemaps` (array)
- `child_sitemaps_followed` (number)
- `discovered_via` (string)
- `duplicate_urls` (array)
- `entries` (array)
- `exceeds_size_limit` (boolean)
- `exceeds_url_limit` (boolean)
- `findings` (array)
- `found` (boolean)
- `grade` (string)
- `invalid_lastmod` (array)
- `newest_lastmod` (string|null)
- `non_https_urls` (array)
- `off_origin_urls` (array)
- `oldest_lastmod` (string|null)
- `score` (number)
- `status` (number|null)
- `type` (string)
- `url` (string)
- `url_count` (number)
- `with_lastmod` (number)

### `link_audit` (~333 tokens)

Link Audit

Audit a page's outbound links: the internal/external split, rel attributes (nofollow, sponsored, ugc), links with no anchor text at all, generic anchor text ("click here", "leer más") that carries no topical signal, and the distribution of external domains. Optionally sample-verifies that links actually resolve, retrying with GET when a server rejects HEAD.

Args:
  \- url (string): the page to audit.
  \- check_broken (boolean): verify links resolve (default false).
  \- sample_size (number): how many links to verify (default 25).
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { total_links, internal_links, external_links, nofollow_links, empty_anchor_text, generic_anchor_text[], external_domains[{domain, count}], checked_count, broken[], score, grade, findings[] }.

Example: "Are there broken links on https://example.com/resources?" -> link_audit(url="https://example.com/resources", check_broken=true).

Input parameters:

- `check_broken` (boolean): Sample links and verify they resolve. Adds up to sample_size requests (default 25), six at a time.
- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `sample_size` (integer): How many links to verify when check_broken is true.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `broken` (array)
- `checked_count` (number)
- `empty_anchor_text` (number)
- `external_domains` (array)
- `external_links` (number)
- `final_url` (string)
- `findings` (array)
- `generic_anchor_text` (array)
- `grade` (string)
- `internal_links` (number)
- `nofollow_links` (number)
- `score` (number)
- `sponsored_links` (number)
- `total_links` (number)
- `ugc_links` (number)
- `url` (string)

### `hreflang_check` (~322 tokens)

Hreflang Check

Validate a page's `<link rel="alternate" hreflang>` annotations: language/region code validity (BCP-47), the required self-referencing entry, the x-default fallback, and duplicate codes.

With check_reciprocity=true it fetches each alternate and confirms it links back to this page — non-reciprocal hreflang is silently ignored by Google, and it is impossible to detect from one page in isolation.

Args:
  \- url (string): the page to check.
  \- check_reciprocity (boolean): verify alternates link back (default false).
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { declared_lang, entries[{hreflang, href, valid_code, is_self, reciprocates}], has_x_default, self_referencing, duplicate_codes[], invalid_codes[], findings[] }.

Example: "Is hreflang set up correctly on https://example.com/es/pagina?" -> hreflang_check(url="https://example.com/es/pagina", check_reciprocity=true).

Input parameters:

- `check_reciprocity` (boolean): Fetch each alternate to confirm it links back. Catches the most common hreflang bug.
- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `declared_lang` (string|null)
- `duplicate_codes` (array)
- `entries` (array)
- `final_url` (string)
- `findings` (array)
- `has_x_default` (boolean)
- `invalid_codes` (array)
- `reciprocity_checked` (boolean)
- `self_referencing` (boolean)
- `url` (string)

### `redirect_trace` (~221 tokens)

Redirect Trace

Follow a URL's redirect chain hop by hop, reporting each status code and target. Flags long chains (which waste crawl budget), redirect loops, temporary 302/307 redirects where a permanent 301/308 belongs, and chains that do not end on HTTPS.

Args:
  \- url (string): the starting URL.
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { final_url, final_status, hops[{url, status, location}], hop_count, https_upgrade, ends_https, has_loop, has_temporary_redirect, elapsed_ms, findings[] }.

Example: "Where does http://example.com/old-page end up?" -> redirect_trace(url="http://example.com/old-page").

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `url` (string, required): Page URL to analyse, e.g. 'https://example.com/blog/post'. The scheme defaults to https://.

Output parameters:

- `elapsed_ms` (number)
- `ends_https` (boolean)
- `final_status` (number)
- `final_url` (string)
- `findings` (array)
- `has_loop` (boolean)
- `has_temporary_redirect` (boolean)
- `hop_count` (number)
- `hops` (array)
- `https_upgrade` (boolean)
- `url` (string)

### `canonical_host_check` (~261 tokens)

Canonical Host Check

Fetch all four host/scheme variants of a domain — http/https × apex/www — and confirm they converge on a single canonical URL. Divergence is the classic cause of a homepage competing with itself in the index.

Also reports whether plain HTTP is upgraded to HTTPS, whether canonicalisation uses permanent (301/308) rather than temporary (302/307) redirects, and which variants do not serve content at all.

Args:
  \- site (string): a domain such as 'example.com' (www and scheme are ignored).
  \- response_format ('markdown' | 'json'): output format (default 'markdown').

Returns: { domain, variants[{variant, reachable, status, final_url, hop_count, redirect_statuses[]}], canonical_url, converges, distinct_endpoints[], forces_https, score, grade, findings[] }.

Example: "Do all versions of example.com redirect to one URL?" -> canonical_host_check(site="example.com").

Input parameters:

- `response_format` (string): Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload.
- `site` (string, required): Site domain or any URL on it, e.g. 'example.com'. Only the origin is used.

Output parameters:

- `canonical_url` (string|null)
- `converges` (boolean)
- `distinct_endpoints` (array)
- `domain` (string)
- `findings` (array)
- `forces_https` (boolean)
- `grade` (string)
- `score` (number)
- `variants` (array)

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/ortamarco-seo-geo-mcp-server/seo-geo-mcp-server#diagnostics

## Score history

- 2026-09-21: 73
- 2026-09-20: 72
- 2026-09-19: 72
- 2026-09-18: 68
- 2026-09-17: 68
- 2026-09-16: 68
- 2026-09-15: 68
- 2026-09-14: 68
- 2026-09-13: 68
- 2026-09-12: 53
- 2026-09-11: 53

## Common questions

### What is the SEO & GEO MCP server?

SEO & GEO is an MCP server listed in the public MCP registry as io.github.OrtaMarco/seo-geo-mcp-server. Audit a page's SEO and its readiness for AI answer engines: meta, schema, robots, sitemaps, AI bots. This page covers its npm package (seo-geo-mcp-server).

### Is the SEO & GEO MCP server safe to use?

SEO & GEO scores 73 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 21 September 2026. It declares no install or post-install scripts. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the SEO & GEO MCP server expose?

SEO & GEO exposes 17 tools: seo_audit, geo_audit, ai_crawler_access, llms_txt_check, render_check, and 12 more. Their descriptions and schemas cost roughly 5,257 tokens of context every time the server is loaded.

### Is the SEO & GEO MCP server still maintained?

SEO & GEO is still listed as active in the MCP registry. We last reached this channel on 21 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the SEO & GEO MCP server under?

SEO & GEO declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- npm package: https://www.npmjs.com/package/seo-geo-mcp-server
- Socket report: https://socket.dev/npm/package/seo-geo-mcp-server
- Repository: https://github.com/OrtaMarco/seo-geo-mcp-server
- Changelog RSS feed: https://verifymcp.io/servers/ortamarco-seo-geo-mcp-server/seo-geo-mcp-server.xml
- Changelog JSON feed: https://verifymcp.io/servers/ortamarco-seo-geo-mcp-server/seo-geo-mcp-server.json
- HTML version of this page: https://verifymcp.io/servers/ortamarco-seo-geo-mcp-server/seo-geo-mcp-server
