mcp-programmatic-seo
v0.1.0
Published
MCP server for auditing large programmatic SEO sites: near-duplicate clustering, keyword-phrasing detection, sitemap-vs-reality checks, and redirect tracing.
Maintainers
Readme
mcp-programmatic-seo
An MCP server for auditing large programmatic SEO sites — the kind with a page per keyword × location, where the question is not "is this page optimised" but "why are four thousand of these pages unindexed".
Existing SEO MCP servers audit one URL at a time: metas, schema, Core Web Vitals. This one works across a set of pages, because at scale the defects are relational — pages duplicating each other, sitemaps drifting from reality, redirect chains left behind by a consolidation.
Why
A site with 4,000 templated pages usually has a handful of problems that only show up when you compare pages to each other:
- Phrasing duplication —
best-x-in-y,hire-x-in-y,x-services-in-yandneed-x-in-yare one search with four URLs. Search engines resolve them to one intent, index one, and file the rest as duplicates. - Template thinness — a 1,700-word page that shares 90% of its text with its siblings carries less unique substance than a 600-word page that doesn't.
- Sitemap drift — after any consolidation, the sitemap keeps advertising URLs that now redirect. Every one spends crawl budget to learn nothing.
- Redirect chains — multi-hop chains, and temporary redirects (302/307) left where permanent ones (301/308) belong.
Install
npm install -g mcp-programmatic-seoClaude Code
claude mcp add programmatic-seo -- npx -y mcp-programmatic-seoClaude Desktop
{
"mcpServers": {
"programmatic-seo": {
"command": "npx",
"args": ["-y", "mcp-programmatic-seo"]
}
}
}Tools
| Tool | Network | What it answers |
|---|---|---|
| analyze_url_patterns | none | What share of these URLs are phrasings of the same query? |
| audit_sitemap | yes | Do the sitemap's URLs actually return 200? |
| find_duplicate_content | yes | Which of these pages are near-duplicates, and how much unique text does each carry? |
| compare_pages | yes | What exactly differs between these two pages? |
| trace_redirects | yes | How many hops, what status codes, any loops? |
| audit_canonicals | yes | Canonical present, self-referencing, cross-host? Any noindex? |
Start with analyze_url_patterns. It needs only a URL list — no fetching — and on a
page-per-keyword site it is usually diagnostic on its own.
Worked example
Pointed at a real container-pool retailer running 6,209 sitemap URLs:
analyze_url_patterns (90 URLs: 18 page types × 5 cities)
distinct queries : 60
clustered : 45 of 90 (50%)
worst cluster (5 URLs, one query):
/pools/best-container-pools/dallas-tx
/pools/container-pool/dallas-tx
/pools/container-pools-near-me/dallas-tx
/pools/container-pools/dallas-tx
/pools/pool-containers/dallas-txaudit_sitemap (60 sampled from 6,209)
healthy : 31
redirecting : 29 (48.3%)
308 /pools/container-swimming-pools/boca-raton-fl -> /pools/container-pools/boca-raton-fl
308 /pools/container-pools-near-me/conroe-tx -> /pools/container-pools/conroe-txThe site had already consolidated roughly half its pages onto canonical URLs and left every retired URL in the sitemap. Three of the five URLs in that worst cluster are ones the site itself now redirects — the tool's read matched the owner's own judgement.
Interpreting the numbers
Similarity is Jaccard over 5-shingles (sliding windows of five consecutive words), not a word bag. Two templated pages share nearly every individual word; what separates them is word order.
| Similarity | Reading | |---|---| | < 0.35 | Genuinely different pages | | 0.35 – 0.50 | Shared template, meaningful unique content | | 0.50 – 0.70 | Thin — one page's worth of content across several URLs | | > 0.70 | Duplicates. Expect one to be indexed and the rest dropped |
Unique words per page — meanWords × (1 - meanSimilarity) — is usually the more
useful figure than raw word count. Doubling a page's length while holding its template
constant raises word count and lowers unique share; only the latter tracks with whether
the page gets kept.
Notes
- Concurrency is capped (default 6, max 12). These tools point at whole sitemaps, and an unbounded fan-out is hard to distinguish from an attack. Keep it low against sites you do not own.
find_duplicate_contentis pairwise, so it takes at most 60 URLs. Pass a targeted set of suspected siblings, not a whole site.analyze_url_patternswill not guess that a domain word is noise. If your site treats "swimming" incontainer-swimming-poolsas redundant, pass it inextraPrefixes— the tool declines to infer that on its own because on another site it could be the distinction that matters.
Development
npm install
npm test # 33 unit tests, no network
npm run buildThe pure logic (src/text.ts, src/slugs.ts) is tested offline; the network layer is
thin by design so the parts worth testing don't need a server.
License
MIT
