SEO MCP Server
<div align="center">
# SEO MCP Server
### Chat with your site's SEO β with real data, not guesses.
**Crawl a site, audit its on-page factors, read your actual Google Search
Console performance, measure Core Web Vitals and compare against a competitor β
from inside Claude, Cursor or any MCP client.**
<br/>
<img src="./assets/demo.gif" alt="Asking 'Audit my site' in an MCP client: the SEO MCP Server crawls the site and reports broken URLs, pages blocked by robots.txt, missing basics and the worst individual issues" width="88%">
<br/><br/>
[](https://www.npmjs.com/package/@soyrageagency/seo-mcp)
[](https://www.npmjs.com/package/@soyrageagency/seo-mcp)
[](https://github.com/soyrageagency/seo-mcp-server/actions/workflows/ci.yml)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
[](./LICENSE)
### Designed, built & maintained by **[SoyRage Agency](https://soyrage.es/)**
**β‘ No site, no credentials, no setup β [try it in 30 seconds](#-try-it-in-30-seconds).**
</div>
---
## π Table of contents
- [Try it in 30 seconds](#-try-it-in-30-seconds)
- [Install](#-install)
- [What it does](#-what-it-does)
- [Crawl a whole site](#crawl-a-whole-site)
- [Audit one page](#audit-one-page)
- [Read real Search Console data](#read-real-search-console-data)
- [Compare two periods](#compare-two-periods)
- [Measure Core Web Vitals](#measure-core-web-vitals)
- [Compare against a competitor](#compare-against-a-competitor)
- [Draft a title and description](#draft-a-title-and-description)
- [Chrome extension β zero setup](#-chrome-extension--zero-setup)
- [Worked examples](#-worked-examples)
- [Connect Google Search Console](#-connect-google-search-console)
- [Tool reference](#-tool-reference)
- [Guided workflows (prompts & resources)](#-guided-workflows-prompts--resources)
- [Run it over the network (HTTP)](#-run-it-over-the-network-http)
- [Run it in Docker](#-run-it-in-docker)
- [Configuration reference](#-configuration-reference)
- [How it behaves on other people's servers](#-how-it-behaves-on-other-peoples-servers)
- [What it will not do](#-what-it-will-not-do)
- [Troubleshooting](#-troubleshooting)
- [Development](#-development)
- [Roadmap](#-roadmap) Β· [full roadmap β](./ROADMAP.md)
- [Support the project](#-support-the-project)
- [Credits & License](#-credits--license)
---
## β‘ Try it in 30 seconds
Demo mode serves a small fabricated site and a fabricated Search Console
history, so you can see exactly what every tool returns before pointing it at
anything real. No credentials, no network access.
```jsonc
{
"mcpServers": {
"seo": {
"command": "npx",
"args": ["-y", "@soyrageagency/seo-mcp"],
"env": { "SEO_MCP_DEMO": "true" }
}
}
}
```
Restart your client and ask: **βAudit my site.β**
The fake site is deliberately imperfect β a 404 linked from an article, a page
missing its H1, a title 83 characters long, images with no alt text, a page
that quietly says `noindex`. A demo where everything is fine would teach you
nothing about what the tool actually finds.
---
## π¦ Install
Nothing to clone or build. Add this to your MCP client's config
(`%APPDATA%\Claude\claude_desktop_config.json` on Windows,
`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
```jsonc
{
"mcpServers": {
"seo": {
"command": "npx",
"args": ["-y", "@soyrageagency/seo-mcp"],
"env": {
"SEO_MCP_SITE": "https://yoursite.com",
// Optional, but this is where the real data is β see below
"GSC_SERVICE_ACCOUNT_FILE": "/absolute/path/to/service-account.json",
"GSC_SITE_URL": "https://yoursite.com/",
// Optional: a free key, avoids PageSpeed rate limits
"PAGESPEED_API_KEY": "AIzaβ¦"
}
}
}
}
```
`npx` fetches the package on first run and keeps it current. A ready-to-edit
copy lives in
[`examples/claude_desktop_config.json`](./examples/claude_desktop_config.json).
Everything works with no credentials at all β you get the crawler and the
on-page auditor, just not the real search data.
---
## π§ What it does
Most SEO tooling hands you a dashboard and leaves you to interpret it. This
hands an LLM the underlying data, so you can ask a question in your own words
and get an answer about **your** site.
Every screenshot below is the real, unedited output of the tool named in its
title bar, generated by [`npm run shots`](./scripts/shots.mjs) against the demo
site. If the tool's output changes, the screenshots are regenerated, not
retouched.
### Crawl a whole site
`crawl_site` finds what only a crawl can see: **broken URLs and the page that
links to them**, duplicate titles, missing basics as a proportion, and pages
buried too deep to be crawled often. `link_graph`, `find_orphan_pages` and
`find_duplicate_content` go further into the same crawl.
<img src="./assets/screenshots/crawl-site.png" alt="crawl_site output: 7 pages crawled, one 404 with the page that links to it, one URL blocked by robots.txt, missing basics counted, then the worst individual issues ranked by severity" width="100%">
### Audit one page
`audit_page` reports every on-page signal for a single URL, with findings
**ranked by impact** instead of listed flat.
<img src="./assets/screenshots/audit-page.png" alt="audit_page output: a verdict line, every extracted field from title to page weight, then findings ordered by severity" width="100%">
### Read real Search Console data
`search_console_queries` returns what Google actually recorded β not an
estimate from a third-party index.
<img src="./assets/screenshots/search-console.png" alt="search_console_queries output: totals, then queries with clicks, impressions, CTR and average position" width="100%">
### Compare two periods
`search_console_compare` answers the question people actually ask: *is this
going up or down, and because of what?*
<img src="./assets/screenshots/search-console-compare.png" alt="search_console_compare output: totals for both periods, then what gained, what lost ground, and what dropped out entirely" width="100%">
### Measure Core Web Vitals
`pagespeed` keeps **field data** β what real Chrome users experienced, the part
tied to ranking β separate from the lab simulation. Conflating the two is the
standard mistake.
<img src="./assets/screenshots/pagespeed.png" alt="pagespeed output: performance and SEO scores, a field-data verdict, then lab metrics with targets" width="100%">
### Compare against a competitor
`compare_competitor` puts the two pages side by side and then says what they do
that you do not.
<img src="./assets/screenshots/compare-competitor.png" alt="compare_competitor output: a field-by-field table of both pages, then a list of the gaps worth acting on" width="100%">
### Draft a title and description
`generate_meta` gathers the evidence β current tags, structure, and **the
queries that already bring you impressions** β and hands it over with the
limits stated. It does not fill in a template, because a templated
`Buy X | Brand` is worse than what the model writes from the page's real
content.
<img src="./assets/screenshots/generate-meta.png" alt="generate_meta output: current tags with lengths, page structure, the real queries reaching this page, then the constraints to write against" width="100%">
---
## π§© Chrome extension β zero setup
<div align="center">
<img src="./assets/extension.gif" alt="Clicking the SEO Inspector toolbar button: the popup opens with scores, a search-result preview and ranked findings, then switches to the Leads tab showing contact details and conversion problems" width="92%">
</div>
The MCP server is for asking questions. The extension is for **landing on a page
and immediately knowing what you are looking at** β no client, no credentials,
no config. Install it, click the button, done.
It scores three things separately, because they fail independently: a page can
rank beautifully and convert nothing.
<table>
<tr><td width="34%" align="center"><b>SEO</b><br/><sub>can it be found</sub></td>
<td width="33%" align="center"><b>LEADS</b><br/><sub>does a visit become one</sub></td>
<td width="33%" align="center"><b>SPEED</b><br/><sub>measured on this load</sub></td></tr>
</table>
<table>
<tr>
<td width="50%" valign="top">
### SEO
A **search-result preview** showing what Google will actually display, measured
in **pixels rather than characters** β Google truncates by rendered width, so a
title of capitals is cut far sooner than one of narrow letters. Plus findings
ranked by severity, the full heading outline, images without alt, anchor-text
problems, structured data, `lang` and `hreflang`.
</td>
<td width="50%" valign="top">
<img src="./assets/screenshots/extension-popup.png" alt="The SEO tab: brand identity, three scores, a Google-style result preview with pixel widths, and findings ordered by severity" width="100%">
</td>
</tr>
<tr>
<td valign="top">
### Leads
**The contact details, extracted.** Emails, phone numbers, WhatsApp, postal
address, tax id and social profiles β from `mailto:`/`tel:` links, JSON-LD and
a deliberately strict text scan. On a prospect's site, this *is* the lead.
Then the conversion audit: are there calls to action, and are any visible
without scrolling? Is there a contact form? Any trust signals β testimonials,
reviews, guarantees, case studies? Is a live chat available? **And is anything
being measured at all?**
</td>
<td valign="top">
<img src="./assets/screenshots/extension-leads.png" alt="The Leads tab: extracted email and social profiles, conversion findings including no analytics detected, and the calls to action found on the page" width="100%">
</td>
</tr>
<tr>
<td valign="top">
### Stack
**What a competitor is running tells you more about their strategy than their
copy does.** CMS, framework, and every analytics and ad pixel on the page. A
Meta pixel means they are buying traffic. A session-recording tool means
somebody is watching behaviour. No pixels at all means nobody is measuring
anything β which is a finding in itself.
</td>
<td valign="top">
<img src="./assets/screenshots/extension-stack.png" alt="The Stack tab: detected CMS and framework, and the analytics and advertising pixels found on the page" width="100%">
</td>
</tr>
</table>
### Two things the crawler cannot see
The extension is not a smaller copy of the server. It runs in a real browser:
- **The rendered page.** If a site builds its content with JavaScript, an HTTP
fetch gets an empty shell. The extension reads the DOM you are looking at.
Comparing the two is often the finding.
- **Real Core Web Vitals for that load** β the Largest Contentful Paint, TTFB
and bytes transferred your browser measured, not a simulation. Plus images
served at more than twice their displayed size, which costs bandwidth on
every single visit and is invisible unless you look for it.
**Copy** puts the whole report on your clipboard as text β contact details,
stack, measurements and every finding β ready for a doc, a ticket, or straight
into Claude.
### Install
Not on the Chrome Web Store yet, so load it unpacked β about thirty seconds:
1. Download or clone this repo.
2. Open **chrome://extensions**.
3. Turn on **Developer mode**, top right.
4. **Load unpacked** β select the [`extension`](./extension) folder.
5. Pin it, then click it on any page.
### What it can and cannot do
Worth being precise, because this is where comparable tools overpromise:
| | |
| --- | --- |
| β
Audit **any** page β yours, a client's, a competitor's | It reads what your browser already loaded. |
| β
See JavaScript-rendered content | It reads the live DOM. |
| β
Pull contact details for prospecting | From links, schema and a strict text scan. Only what the page publishes. |
| β Show heatmaps of **your visitors** | An extension only runs for whoever installed it. Your visitors will not install yours. Visitor analytics needs a snippet on the site β that is why Hotjar and Clarity work the way they do, and it is on the [roadmap](./ROADMAP.md). |
### Privacy
It requests `activeTab`, **not** host permissions: it can read a page only
after you click the button, and only that tab. No content script runs on pages
you have not asked about, nothing is sent anywhere, and there is no account or
analytics. The report exists in the popup until you close it.
---
## π§ͺ Worked examples
Real questions, and what the assistant does with them.
<details open><summary><b>βWhat's broken on my site?β</b></summary>
Calls `crawl_site`. Comes back with the 404 **and the article that links to
it** β which is the part that turns a finding into a fix, because you now know
where to edit. Also surfaces the page quietly serving `noindex`, which is the
single most expensive thing to have wrong and the least visible.
</details>
<details><summary><b>βWhich pages get impressions but no clicks?β</b></summary>
Calls `search_console_queries` with `dimension: "page"`. High impressions with
a low CTR means Google shows you and people choose someone else β usually a
snippet problem rather than a ranking problem, and one of the cheapest things
to fix. Follow it with `generate_meta` on the worst offender.
</details>
<details><summary><b>βWhy did traffic drop last month?β</b></summary>
The **why-did-traffic-drop** prompt. It compares the window against the one
before it, then breaks the loss down by page, query, device and country,
because a drop concentrated in one page and a drop spread evenly have
completely different causes.
The prompt explicitly tells the assistant not to reach for "a Google update"
unless the pattern actually fits one β broad, sudden, sitewide. It is the
explanation that requires no evidence, which is exactly why it gets used when
there is none.
</details>
<details><summary><b>βWe're on page two for X. What's the page above us doing?β</b></summary>
The **beat-competitor** prompt: `compare_competitor` for the on-page
differences, `audit_page` on each for the detail, `search_console_queries`
filtered to your URL for what you are already close on.
It is also told to say when the gap looks off-page. Sending you off to rewrite
headings when the real difference is 400 referring domains wastes your week,
and this server cannot see links pointing at you.
</details>
<details><summary><b>βIs my sitemap lying?β</b></summary>
`list_sitemap` for what the site says it publishes, `crawl_site` for what it
actually links to. The gap between the two is where stale URLs, orphan pages
and forgotten sections live.
</details>
<details><summary><b>βAudit this client site before the call.β</b></summary>
The **audit-site** prompt against a site you do not own. It obeys robots.txt,
identifies itself honestly and paces itself, so you get the picture without
being rude to their server. Everything is read-only.
</details>
---
## π Connect Google Search Console
This is the step worth doing. Without it, everything about search intent is
inference; with it, the assistant is working from what Google actually
recorded.
A **service account** is the easier path β no browser flow, no token to
refresh.
1. In [Google Cloud Console](https://console.cloud.google.com/), create a
project and enable the **Google Search Console API**.
2. Create a **service account** and download its JSON key.
3. Open the JSON and copy the `client_email` β it looks like
`something@project.iam.gserviceaccount.com`.
4. In [Search Console](https://search.google.com/search-console) β your property
β **Settings β Users and permissions β Add user**, paste that email and give
it **Full** or **Restricted** access.
5. Point the server at the key:
```jsonc
"env": {
"GSC_SERVICE_ACCOUNT_FILE": "/absolute/path/to/key.json",
"GSC_SITE_URL": "https://yoursite.com/"
}
```
Then ask: *βWhat did we rank for last month, and what moved?β*
<details><summary><b>Prefer OAuth, or already have a refresh token?</b></summary>
```jsonc
"env": {
"GSC_CLIENT_ID": "β¦apps.googleusercontent.com",
"GSC_CLIENT_SECRET": "β¦",
"GSC_REFRESH_TOKEN": "β¦",
"GSC_SITE_URL": "https://yoursite.com/"
}
```
</details>
If Search Console is not configured, its tools **do not register at all** β and
the server says why in its startup log. A tool that can only fail is worse than
one that is not there.
---
## π§° Tool reference
| Tool | What it does |
| --- | --- |
| `crawl_site` | Crawl and report site-wide problems: broken URLs with their referrers, duplicate titles and descriptions, missing basics, crawl depth, then the worst individual findings. |
| `list_sitemap` | The URLs a site declares, following the sitemap index. Often revealing on its own β what a site publishes is frequently not what it links to. |
| `audit_page` | Every on-page signal for one URL, ranked by impact. `showOutline` adds the full heading structure. |
| `compare_competitor` | Your page and a competitor's, field by field, ending in what they do that you do not. |
| `search_console_queries` | Real performance by query, page, country, device or date. Filterable to a single URL. |
| `search_console_compare` | A window against the one before it: what gained, what lost ground, what dropped out entirely. |
| `list_search_console_sites` | Which properties your credentials can read, and at what permission level. |
| `pagespeed` | Core Web Vitals, field data separated from the lab simulation. |
| `generate_meta` | Assembles the evidence for a new title and description β current tags, structure, and the queries that already work. |
| `link_graph` | PageRank over the internal links: where authority accumulates, which important pages are starved of it, which minor pages hoard it. |
| `find_orphan_pages` | Indexable pages nothing links to β reachable only from the sitemap, crawled rarely, invisible page by page. |
| `find_duplicate_content` | Pages saying the same thing in different words, by exact phrase overlap with site boilerplate excluded. |
| `validate_structured_data` | Schema checked against what Google requires for the rich result each type is for, not merely listed. |
| `check_hreflang` | Reciprocity, self-reference and valid codes across the crawl β where hreflang actually breaks. |
| `find_broken_links` | Outbound links that no longer resolve, with the pages that link to them. |
| `serp_preview` | The result as Google will render it, measured in pixels.Try a different title without editing the page. |
| `about` | Version, configuration and which capabilities loaded. Start here when a tool is missing. |
---
## π¬ Guided workflows (prompts & resources)
Tools answer a question you already knew how to ask. SEO is a field where most
people do not know which question to ask β so this server also ships **MCP
prompts**, which your client lists for you.
| Prompt | What it does |
| --- | --- |
| **audit-site** | Full sweep, ending in the three things to fix first with what each is plausibly worth. Narrow it with `technical`, `content` or `performance`. |
| **why-did-traffic-drop** | Works out what actually changed from real Search Console data. |
| **improve-page** | One URL end to end: what it has, what it already ranks for, how fast it is, then a specific edit list. |
| **beat-competitor** | Why their page outranks yours β and honest about what is not visible from here. |
Each prompt names the tools to call, the order, and what a good answer looks
like: verdict first, findings ranked, no padding, and **ground every claim in
tool output**. An SEO audit full of confidently invented numbers is worse than
no audit, so the instruction is explicit rather than hoped for.
Two **resources** are exposed as attachable context:
- `seo://server/capabilities` β what is loaded and how it is configured.
- `seo://reference/checklist` β every check and the threshold behind it, so the
numbers in an audit can be questioned rather than taken on faith.
---
## π Run it over the network (HTTP)
By default an MCP server talks over **stdio**: your client starts it as a child
process on your machine. Set one variable and it serves **Streamable HTTP**
instead, so one instance can serve a whole team:
```sh
SEO_MCP_HTTP=true \
SEO_MCP_HTTP_TOKEN="$(openssl rand -hex 32)" \
SEO_MCP_SITE=https://yoursite.com \
npx -y @soyrageagency/seo-mcp
```
```jsonc
{
"mcpServers": {
"seo": {
"type": "http",
"url": "http://10.0.0.5:8621/mcp",
"headers": { "Authorization": "Bearer <the token you generated>" }
}
}
}
```
It binds loopback by default and warns loudly if you start it without a token.
Anyone who can reach the port can crawl anything the server can reach and read
your Search Console data, so keep it behind your VPN. `GET /health` needs no
token, for container healthchecks.
---
## π³ Run it in Docker
```sh
docker run --rm -i \
-e SEO_MCP_SITE=https://yoursite.com \
-v /path/to/key.json:/keys/gsc.json:ro \
-e GSC_SERVICE_ACCOUNT_FILE=/keys/gsc.json \
ghcr.io/soyrageagency/seo-mcp
```
Keep `-i` and do not allocate a TTY: stdout is the JSON-RPC stream. To serve it
over the network instead, add `-p 8621:8621 -e SEO_MCP_HTTP=true -e
SEO_MCP_HTTP_HOST=0.0.0.0 -e SEO_MCP_HTTP_TOKEN=β¦`.
---
## βοΈ Configuration reference
Everything is settable by environment variable, by `seo-mcp.config.json`, or by
a local `.env` β in that order of precedence.
| Variable | Default | What it does |
| --- | --- | --- |
| `SEO_MCP_SITE` | β | Default site, so tools do not need the URL every time. |
| `SEO_MCP_DEMO` | `false` | Fabricated site and search data. No network access at all. |
| `SEO_MCP_MAX_PAGES` | `200` | Hard cap on pages per crawl. |
| `SEO_MCP_MAX_DEPTH` | `3` | How many links deep to follow. |
| `SEO_MCP_CONCURRENCY` | `4` | Parallel requests. |
| `SEO_MCP_DELAY_MS` | `200` | Pause between requests to the same host. |
| `SEO_MCP_TIMEOUT_MS` | `15000` | Per-request timeout. |
| `SEO_MCP_RESPECT_ROBOTS` | `true` | Obey robots.txt. **Only turn this off for a site you control.** |
| `SEO_MCP_USER_AGENT` | `SeoMcpServer/1.0 (+β¦)` | How the crawler identifies itself. |
| `GSC_SITE_URL` | `SEO_MCP_SITE` | Search Console property. Mind the trailing slash, or use `sc-domain:`. |
| `GSC_SERVICE_ACCOUNT_FILE` | β | Path to a service-account JSON key. |
| `GSC_SERVICE_ACCOUNT_JSON` | β | The key inline, for clients that only pass env vars. |
| `GSC_CLIENT_ID` / `GSC_CLIENT_SECRET` / `GSC_REFRESH_TOKEN` | β | The OAuth alternative. |
| `PAGESPEED_API_KEY` | β | Free key. Without it PageSpeed is rate-limited. |
| `SEO_MCP_HTTP` | `false` | Serve over HTTP instead of stdio. |
| `SEO_MCP_HTTP_HOST` / `PORT` / `PATH` / `TOKEN` | `127.0.0.1` / `8621` / `/mcp` / β | HTTP transport settings. |
| `SEO_MCP_PLUGINS` | *(all)* | Allowlist: `crawl,audit,searchconsole,pagespeed,meta,prompts`. |
| `SEO_MCP_DISABLED_PLUGINS` | β | Blocklist. Always wins over the allowlist. |
| `SEO_MCP_LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error`. |
---
## π‘οΈ How it behaves on other people's servers
Every crawl hits somebody's server, often one you do not own β a competitor, a
client's staging box. So the crawler is deliberately conservative:
- **It identifies itself honestly.** The default User-Agent names the tool and
links to this repo, so a site owner can see who hit them and block it.
- **It obeys robots.txt**, including wildcards, `$` anchors, longest-match
precedence and per-agent groups. There is an override, and it warns you every
time you use it.
- **It honours `Crawl-delay`**, and paces itself per host regardless.
- **It is bounded** by page count and depth, and gives up rather than hanging.
- **It follows redirects by hand**, up to five hops, because the chain itself is
a finding worth reporting.
---
## π« What it will not do
Saying this plainly is more useful than another feature list:
- **It will not change your site.** No edits, no submissions, no settings.
- **It will not invent numbers.** The server's instructions tell the assistant
to ground every claim in tool output and to say when it is inferring.
- **It will not tell you a page is "optimised".** It reports conventions and
thresholds. Relevance and links matter more than any of them, and a page can
pass every check here and rank nowhere.
- **It has no backlink data.** Nothing here sees links pointing *to* you.
- **No telemetry.** Nothing leaves your machine except the requests you asked
for: the sites you crawl, and Google's APIs when you configure them.
---
## π©Ί Troubleshooting
<details><summary><b>A Search Console tool is missing</b></summary>
It does not register without credentials. Run `about` β it reports whether
Search Console is connected and lists which capabilities loaded. The server
also logs the reason on startup.
</details>
<details><summary><b>βGoogle says this account cannot read β¦β</b></summary>
Run `list_search_console_sites` to see exactly which properties your
credentials can read. The usual cause is a mismatch in how the property is
verified: `https://example.com/` (with the trailing slash),
`https://www.example.com/` and `sc-domain:example.com` are three different
properties to Google.
For a service account, also check you added its `client_email` as a user on the
property itself β enabling the API is not enough.
</details>
<details><summary><b>PageSpeed is rate-limiting me</b></summary>
Set `PAGESPEED_API_KEY`. The key is free and takes a minute to create; without
one the quota is tiny.
</details>
<details><summary><b>The crawl returns almost nothing</b></summary>
Two common causes. Either robots.txt is blocking the crawler β the report lists
those URLs under **NOT CRAWLED** with the reason β or the site renders its
content with JavaScript, and a plain HTTP fetch sees an empty shell. Rendered
crawling is on the [roadmap](./ROADMAP.md).
</details>
<details><summary><b>Everything says DEMO MODE</b></summary>
`SEO_MCP_DEMO` is still `true`. Remove it, or set it to `false`.
</details>
---
## π§ͺ Development
```sh
git clone https://github.com/soyrageagency/seo-mcp-server.git
cd seo-mcp-server
npm install
npm run build
npm test # 25 unit tests: robots.txt parsing, extraction, audit rules
npm run smoke # 42 checks end to end: boots the real server and drives it
npm run inspect # the MCP Inspector, against your local build
npm run shots # regenerate the README screenshots from real output
npm run gif # regenerate the demo GIF
npm run banner # regenerate the sponsor banner and social card
npm run icons # regenerate the extension icons
npm run extension:shots # load the extension in a real Chrome and screenshot it
npm run extension:gif # the extension demo GIF
```
`npm run dev` watches and rebuilds. TypeScript `strict`, with
`noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns` and
`noFallthroughCasesInSwitch`. CI runs the whole suite on Linux and Windows.
---
## πΊοΈ Roadmap
The full roadmap β shipped, next, and what is **deliberately not planned** β
lives in [**ROADMAP.md**](./ROADMAP.md).
The short version: self-hosted visitor analytics with click and scroll
heatmaps (a cookieless snippet, since an extension can only ever see its own
installer), JavaScript-rendered crawling, and audits that remember so the
question becomes "what changed since last week". Backlink data and
keyword-volume estimates are not planned, because doing either honestly needs
an index this project will never have.
---
## π Support the project
<div align="center">
<a href="https://www.paypal.com/paypalme/soyrageagency"><img src="./assets/sponsor.png" alt="Support the project β SEO MCP Server is MIT licensed, has no paid tier and sends no telemetry. Sponsoring buys maintenance time." width="82%"></a>
</div>
---
## ποΈ Credits & License
<div align="center">
**Designed, built and maintained by [SoyRage Agency](https://soyrage.es/) β https://soyrage.es/**
Part of a family of self-hosting tools you can talk to:
**[Proxmox MCP](https://github.com/soyrageagency/proxmox-mcp-server)** Β·
**[Docker MCP](https://github.com/soyrageagency/docker-mcp-server)** Β·
**[RageVault](https://github.com/soyrageagency/ragevault)**
</div>
Released under the **[MIT License](./LICENSE)** β use it, modify it, self-host
it, ship it commercially.
If you build something on top of it, a link back to
[soyrage.es](https://soyrage.es/) is appreciated but never required.
<div align="center">
**Β© 2026 SoyRage Agency β https://soyrage.es/** Β· Made with care in Valencia, Spain.
</div>
TDQS
Scored across 14 tools
Each tool targets a clearly distinct concernβsite-wide crawl issues, individual page audits, external link checking, internal link analysis, orphans, duplicates, structured data, and hreflang. Even related tools like crawl_site and find_duplicate_content are differentiated by whether they catch exact duplicates or semantic similarity.
Most tools follow a clear verb_noun snake_case pattern such as crawl_site, find_broken_links, and validate_structured_data. A few exceptions like about, pagespeed, serp_preview, and link_graph break the pattern, but the naming remains readable and predictable overall.
14 tools is well within the ideal range for a specialized SEO server. Each tool addresses a discrete diagnostic need, and the count feels comprehensive without being bloated or redundant.
The toolset covers the major technical SEO workflows: crawling, page audits, performance, metadata, internal links, orphans, duplicates, structured data, hreflang, sitemaps, and competitor comparison. There are no obvious dead ends or missing life-cycle operations for an analysis-focused SEO server.