The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the SEO & GEO listing page.
An MCP server that lets an AI agent audit a page for SEO and GEO (Generative Engine Optimization) — on-page tags, structured data, robots.txt, sitemaps, hreflang, and whether ChatGPT, Claude, Perplexity and Gemini can actually crawl and cite you. No API keys required.
Built on the v2 MCP SDK: the server speaks the 2026-07-28 protocol revision and still accepts 2025-era clients (Claude Desktop, Claude Code, Cursor) from the same factory — one build, both eras, nothing to configure.
Ask Claude "how is this page doing, and will AI assistants cite it?" and it runs a full audit and hands you a graded report with prioritised fixes — instead of you pasting a URL into six different web tools.
Two gaps, one server.
The SEO gap: the on-page checkers are all web UIs. None of them let an agent run the audit, read the result and fix the code in the same loop.
The GEO gap: "generative engine optimization" tooling is mostly rank-tracking dashboards behind a subscription. The mechanics that actually decide whether an AI assistant can cite you are cheap to check and almost never checked:
GPTBot stops training.
Blocking OAI-SearchBot stops you being cited. Most sites that meant to do
the first have accidentally done the second. This server separates them.GPTBot,
ClaudeBot, PerplexityBot and CCBot largely do not. A client-rendered page
can rank fine in Google and be invisible to every AI assistant.It is the agent-facing companion to the tools at ortamarco.me,
and shares its core (the connect-time SSRF guard and host validation)
with domain-security-mcp-server.
| Tool | What it does |
|---|---|
seo_audit ⭐ | One fetch → seven weighted sections (meta, headings, content, schema, images, links, crawlability) → 0–100 score, A–F grade, prioritised fixes. include_geo=true adds the GEO dimension |
geo_audit ⭐ | AI answer-engine readiness: crawler access (25), server-rendered content (20), structured data (15), extractable structure (15), authorship (10), freshness (8), depth (7) |
| Tool | What it does |
|---|---|
ai_crawler_access | Resolves ~35 AI crawler tokens against robots.txt. Separates training from citation bots, flags blocks that vendors document as unenforceable, handles the Applebot→Googlebot fallback |
render_check | Whether content survives without JavaScript — detects unhydrated SPA shells that AI crawlers cannot read |
llms_txt_check | Detects and validates /llms.txt against the llmstxt.org proposal — and reports its real adoption status rather than implying it earns visibility |
| Tool | What it does |
|---|---|
meta_tags_check | Title, description, canonical, robots (meta and X-Robots-Tag), lang, charset, viewport |
social_preview_check | Open Graph + Twitter Card, and verifies the og:image actually loads |
heading_structure | Full h1–h6 outline, multiple h1s, skipped levels, question-shaped headings |
structured_data_check | JSON-LD/microdata/RDFa extraction, parse errors, and Google rich-result requirements for 21 schema types |
content_analysis | Word count, Flesch reading ease, thin-content detection, text-to-HTML ratio, term density (EN + ES stopwords) |
image_seo_check | Missing alt text, missing dimensions (layout shift), lazy loading, WebP/AVIF adoption |
| Tool | What it does |
|---|---|
robots_txt_check | RFC 9309 parse; flags wildcard Disallow: / and 5xx responses (which Google reads as disallow-all) |
sitemap_check | Discovery via robots.txt → conventional paths; index following, gzip, 50k/50MiB limits, lastmod validity |
canonical_host_check | All four http/https × apex/www variants — do they converge on one canonical URL, and via 301 or 302? |
redirect_trace | Hop-by-hop chain with loop and temporary-redirect detection |
link_audit | Internal/external split, rel attributes, generic anchor text, optional broken-link sampling |
hreflang_check | BCP-47 validity, self-reference, x-default, duplicates — plus optional reciprocity verification |
Every tool is read-only, declares an outputSchema and returns
structuredContent (validated by the SDK) alongside human-readable Markdown
(default) or JSON (response_format="json"), plus actionable error messages.
This server deliberately refuses to overstate two things that most GEO content gets wrong. Both are surfaced in tool output, not buried here:
llms.txt is not an adopted standard. It is a community proposal from
September 2024. No major AI vendor has documented that its crawlers read it
from third-party sites, and Google has publicly said it does not. The tool
reports presence and validates shape — and geo_audit deliberately does not
score it. (llms-full.txt is a docs-tooling convention, not part of the proposal.)Perplexity-User, ChatGPT-User and
meta-externalfetcher are documented by their own vendors as ignoring or
possibly ignoring robots.txt. Reporting those as cleanly "blocked" would be
misleading, so they are listed separately as unenforceable.Crawler tokens carry a provenance field distinguishing first-party vendor
documentation from community aggregators, and vendors that publish no token at
all (xAI/Grok, Microsoft Copilot) are named explicitly — because a missing rule
cannot be read as either allowed or blocked.
Requires Node.js 20.18+. Nothing to clone — every MCP client can run it with npx.
Add to claude_desktop_config.json (or ~/.cursor/mcp.json) — see examples/:
On Windows use "command": "cmd" with "args": ["/c", "npx", "-y", "seo-geo-mcp-server"].
Restart the client, then ask: "Audit the SEO and GEO of example.com."
The same server speaks stateless Streamable HTTP for remote or multi-client
use. One endpoint serves both protocol eras; there is no session state and no
Mcp-Session-Id to carry.
It is safe by default: it binds to 127.0.0.1 and only accepts localhost
Host and Origin headers, which blocks DNS-rebinding attacks from a web page.
To expose it — for example behind Coolify or Traefik — opt in explicitly:
| Variable | Default | Purpose |
|---|---|---|
TRANSPORT | stdio | http to serve Streamable HTTP |
PORT | 3000 | Listening port |
HOST | 127.0.0.1 | Bind address; 0.0.0.0 to accept remote connections |
ALLOWED_HOSTS | — | Comma-separated hostnames the Host header may carry (e.g. mcp.example.com) |
ALLOWED_ORIGINS | — | Comma-separated origins allowed to call from a browser |
MCP_AUTH_TOKEN | — | If set, every request needs Authorization: Bearer <token> |
Binding to a non-loopback address without ALLOWED_HOSTS or MCP_AUTH_TOKEN
works, but the server says so on stderr. With Docker (the image sets HOST=0.0.0.0):
evals/ holds a 10-question LLM evaluation set (stable, verifiable)
and instructions for running it — see evals/README.md.
The core/ layer is deliberately free of any MCP types, so the same logic can
power both this server and a web UI.
The tools fetch URLs the caller names and URLs the audited site names (links,
og:image, hreflang alternates, sitemap children, redirects), so every outbound
connection is screened against server-side request forgery:
[::ffff:127.0.0.1]).Found a problem? Please open a private security advisory.
MIT © Marco Orta