@soyrageagency/seo-mcp
v1.0.0
Published
Chat with your site's SEO. A Model Context Protocol (MCP) server that lets any LLM crawl a site, audit on-page factors, read Google Search Console, measure Core Web Vitals and compare against competitors — read-only by default.
Downloads
42
Maintainers
Readme
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.
Designed, built & maintained by SoyRage Agency
⚡ No site, no credentials, no setup → try it in 30 seconds.
📑 Table of contents
- Try it in 30 seconds
- Install
- What it does
- Chrome extension — zero setup
- Worked examples
- Connect Google Search Console
- Tool reference
- Guided workflows (prompts & resources)
- Run it over the network (HTTP)
- Run it in Docker
- Configuration reference
- How it behaves on other people's servers
- What it will not do
- Troubleshooting
- Development
- Roadmap · full roadmap →
- Support the project
- 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.
{
"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):
{
"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.
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 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.
Audit one page
audit_page reports every on-page signal for a single URL, with findings
ranked by impact instead of listed flat.
Read real Search Console data
search_console_queries returns what Google actually recorded — not an
estimate from a third-party index.
Compare two periods
search_console_compare answers the question people actually ask: is this
going up or down, and because of what?
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.
Compare against a competitor
compare_competitor puts the two pages side by side and then says what they do
that you do not.
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.
🧩 Chrome extension — zero setup
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.
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.
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?
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.
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:
- Download or clone this repo.
- Open chrome://extensions.
- Turn on Developer mode, top right.
- Load unpacked → select the
extensionfolder. - 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. |
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.
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.
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.
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.
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.
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.
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.
🔑 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.
- In Google Cloud Console, create a project and enable the Google Search Console API.
- Create a service account and download its JSON key.
- Open the JSON and copy the
client_email— it looks like[email protected]. - In Search Console → your property → Settings → Users and permissions → Add user, paste that email and give it Full or Restricted access.
- Point the server at the key:
"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?”
"env": {
"GSC_CLIENT_ID": "…apps.googleusercontent.com",
"GSC_CLIENT_SECRET": "…",
"GSC_REFRESH_TOKEN": "…",
"GSC_SITE_URL": "https://yoursite.com/"
}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:
SEO_MCP_HTTP=true \
SEO_MCP_HTTP_TOKEN="$(openssl rand -hex 32)" \
SEO_MCP_SITE=https://yoursite.com \
npx -y @soyrageagency/seo-mcp{
"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
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-mcpKeep -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
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.
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.
Set PAGESPEED_API_KEY. The key is free and takes a minute to create; without
one the quota is tiny.
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.
SEO_MCP_DEMO is still true. Remove it, or set it to false.
🧪 Development
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 GIFnpm 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.
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
🖋️ Credits & License
Designed, built and maintained by SoyRage Agency — https://soyrage.es/
Part of a family of self-hosting tools you can talk to: Proxmox MCP · Docker MCP · RageVault
Released under the MIT 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 is appreciated but never required.
© 2026 SoyRage Agency — https://soyrage.es/ · Made with care in Valencia, Spain.
