SEO MCP
SEO 증거를 위한 MCP 서버 — 온페이지 감사, 크롤링, GSC, PageSpeed, 선택적 SERP
문서
mcp-server-seo
Rust MCP server for SEO evidence used by Grok Build and Claude Code: on-page audit, robots/sitemap, polite crawl, PageSpeed Insights, Google Search Console, optional paid SERP.
stdio + JSON-RPC via rmcp. No Docker.
Two binaries share the same engine:
| Binary | Role |
|---|---|
seo | Headless CLI (doctor, tool, audit daily|weekly, mail) |
mcp-server-seo | MCP stdio server for AI agents |
Build
cargo build --release
install -Dm755 target/release/seo ~/.local/bin/seo
install -Dm755 target/release/mcp-server-seo ~/.local/bin/mcp-servers/mcp-server-seo
Cron / SeoAuditBot uses seo. Agents keep using mcp-server-seo.
Config
Optional: ~/.config/mcp-server-seo/config.toml (see config.example.toml).
mkdir -p ~/.config/mcp-server-seo
cp config.example.toml ~/.config/mcp-server-seo/config.toml
chmod 600 ~/.config/mcp-server-seo/config.toml
Phase 1 tools work with zero keys. GSC needs a service-account JSON; PageSpeed needs a free API key; SERP needs Serper (optional).
Tools (summary)
| Group | Tools |
|---|---|
| Foundation | status, sites_list, site_resolve |
| On-page | page_audit (mode=quick|full, target_keyword), page_text, redirect_trace, robots_check, sitemap_*, extract_structured_data, schema_validate, link_check, entity_coverage |
| Crawl / graph | crawl_run (resumable, seed_sitemap), crawl_report, crawl_results, crawl_diff, sitemap_coverage, sitemap_build, internal_link_graph (true orphans) |
| Local / architecture | url_architecture_plan, nap_consistency (schema-first), citation_prospects, gbp_audit, gbp_reviews_snapshot, local_audit |
| Content / KW | content_brief (SERP-grounded), keyword_research (ranked), keyword_volumes, keyword_intent, keyword_suggest |
| Authority | authority_snapshot (free, Open PageRank), backlink_overview (paid, gated) |
| Performance | pagespeed_audit |
| GSC | gsc_*, gsc_opportunities, gsc_trends, log_event |
| SERP / rank | serp_fetch (num, local pack), rank_history (series), rank_watch |
| Composites | page_snapshot, page_history, audit_page, audit_site, verify_live, rank_diagnosis |
| Fix loop | findings_list, findings_diff (default latest two runs), url_to_source |
Recommended free-first agent playbook (v0.7)
page_auditmode=full(+target_keyword) on home + key templatespage_texton any page whose copy you need to judge — the audit returns counts, this returns the actual sectionscrawl_runwithseed_sitemap=true→internal_link_graph; ifcoverage.truncated, re-call withresume_crawl_iduntil the frontier drains, then readtrue_orphanslocal_auditfor NAP/GBP/authority/reviews in one passauthority_snapshotbefore concluding a clean page "just needs more on-page" — a deficit verdict means links, not polishserp_fetchonce per target query (~$0.001,confirm=YES-PAY) →content_briefreads the stored SERP for PAA questions and competitor-backed H2srank_diagnosiswhen a specific keyword underperforms — ranks indexation vs relevance vs authority vs technicalverify_liveafter deploy, thenlog_eventso the movement has a cause attached- Next session:
findings_diff(no run ids) —fixedvsnew/regressed.url_to_sourcemaps a URL to the file under[[sites]].source_root
Weekly loop (schedule from your MCP client — this server runs no daemon)
gsc_trends site=<property> refresh=true # stores today's snapshot, returns movers
rank_watch action=run confirm=YES-PAY # re-checks tracked queries
Read movers_up / movers_down next to the events you logged.
New site, no GSC history (~$0.10)
keyword_research seed=<service> geo=<city> mode=all
keyword_volumes keywords=[...] confirm=YES-PAY # one call, cached 90 days
serp_fetch <top terms> confirm=YES-PAY # unlocks difficulty + SERP clustering
keyword_research ... # now returns a ranked shortlist
url_architecture_plan / content_brief # human approval before publish
Content publish stays in the CMS — this server is evidence + briefs, not a writer.
Safety
- SSRF: public http(s) only; private/reserved IPv4+IPv6 blocked; redirect hops re-checked
sitemap_build: writes only understorage.data_dir(relativeout_path)- GSC submit:
confirm=YES-SUBMIT(sitemaps API — not URL Inspection) - Paid SERP:
dry_run=truedefault;confirm=YES-PAY+ budget ledger - Paid authority (
backlink_overview): same gate, separate[authority]budget ledger; dry run makes zero provider calls and the ledger records the provider's actual reported cost - Static HTML:
rendered: false— useagent-browserfor JS
Optional keys (v0.7)
| Key | Unlocks | Cost |
|---|---|---|
authority.open_pagerank_api_key | authority_snapshot, authority step in local_audit, rank_diagnosis verdicts, keyword difficulty | free (domcop) |
authority.dataforseo_login / _password | backlink_overview link gap, keyword_volumes | pay-as-you-go, budget-capped |
serp.api_key | serp_fetch / rank_watch → grounds content_brief, keyword difficulty, citation presence checks | ~$0.001/query |
local.places_api_key | gbp_reviews_snapshot rating, review velocity, competitor gap | free tier + monthly call cap |
gsc.credentials_file | gsc_* incl. gsc_trends history and gsc_opportunities | free |
Verify before trusting the numbers: your SERP provider's credit cost for num > 10
(serp.num_100_multiplier), the DataForSEO keywords.location_code for your country,
and current Places API field pricing. All three are config values, not hardcoded.
Do not reimplement
Use sibling MCPs: google-analytics, google-adwords, cloudflare, namecheap, agent-browser.
Clients
Already registered as seo in Grok Build / Claude Code (restart clients after rebuild).
Tests
cargo test --release
./tests/test-stdio.sh
Plan
See PLAN.md (merged Grok + Fable + Kimi + critique amendments).
Module layout (v0.7)
src/
bin/seo.rs # CLI (doctor, tool, audit daily|weekly, mail)
bin/mcp_server_seo.rs
server/ # thin MCP tool-router only (no SEO logic)
params.rs # all tool input schemas
dispatch/ # named-tool router + one exec file per handler group
playbooks.rs # daily/weekly audits; ok=false on step errors
mod.rs # SeoServer, shared helpers, chained tool_router()
handlers/ # #[tool] wrappers → run_named; one sub-router per file
# (site, page, crawl, pagespeed, gsc, serp, audit,
# findings, content, authority, local)
tools/ # business logic per concern
page/ # parse, og probe, structured data, text extraction, audit
crawl/ # BFS crawl + report builder
local/ # nap, citations, gbp, local_audit
gsc/ # client (JWT) + ops (analytics/inspect/sitemaps)
authority/ # open pagerank (free) + dataforseo (paid) adapters
audit/ # composites: page, site, verify_live, rank_diagnosis
source_map.rs # URL → repo file (convention presets, sandbox)
serp_evidence.rs# stored-SERP parser shared by briefs/keywords
html_dom.rs # shared HTML extract helpers incl. main-text extraction
…
rules/ # pure rules (checklist, schema, patterns, intent, relevance, issue)
storage/ # SQLite split by domain (crawls, snapshots, spend, authority,
# keywords, gsc, rank, places, events,
# findings/{persist,extract,query,family}) over one shared conn
http.rs / config.rs / site.rs / util.rs
Rule: no God files — logic lives under tools/* / rules/*; server/ does not implement SEO algorithms. Shared issue builders live in rules/issue.rs.