seo-analytics-mcp
Provides tools for Google Search Console, enabling search analytics queries, period comparisons, URL index inspection, and sitemap submission and verification.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@seo-analytics-mcpWhich pages lost the most clicks in the last 28 days versus the 28 before?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Google Search Console, GA4 and IndexNow — as an MCP server.
Ask Claude about your own sites. What's ranking, what changed, what's indexed, what's converting.
"Which pages lost the most clicks in the last 28 days versus the 28 before?"
"How is /pricing doing?"
"Is https://example.com/new-post indexed yet?"
"Which pages rank on page one but get almost no clicks?"
"Top 20 queries for the blog last month, and which of them convert in GA4."You authorise your own Google account against an OAuth client in your own Google Cloud project. Nothing about your access flows through anyone else, this repository contains no credentials, and every Google quota you spend is your own.
Contents
Install · Setup · The seven-day problem · Tools · Response shape · Configuration · Writes · Profiles · Design · Troubleshooting · Development
Related MCP server: GSC Analyst Connector
Install
Requires Python 3.10+ and uv.
uvx seo-analytics-mcp doctor # no install needed — prints your setup steps, in orderdoctor is the whole onboarding experience. It tells you exactly what is missing and what to
run next, at every stage. If you read nothing else here, run that.
Setup
Six clicks in the Google Cloud console, then one command. Ten minutes, once.
Create a Google Cloud project — or reuse one. console.cloud.google.com/projectcreate
Enable the APIs. Search Console is required; the GA4 pair is optional.
searchconsole ·
analyticsdata ·
analyticsadmin
Configure the consent screen, then press Publish app. console.cloud.google.com/auth/overview
Choose External and publish. You are the only user of your own app, so Google's personal-use exception applies and no verification is needed. Workspace users can choose Internal instead.
Do not skip the Publish step — see below.
Create an OAuth client of type Desktop app and download the JSON.
console.cloud.google.com/auth/clients
A Web application client cannot do the loopback redirect this server needs. doctor checks
for this specific mistake, because it is the easy one to make.
Authorise, once, from a terminal:
uvx seo-analytics-mcp auth --client-secret ~/Downloads/client_secret_*.jsonYour browser opens. Google says "Google hasn't verified this app" — expected for your own
client: Advanced → Continue. The token lands in your profile directory at mode 0600.
Check, then connect:
uvx seo-analytics-mcp doctor # eleven checks; exit 0 means it will workConnect it
claude mcp add seo \
-e GSC_DEFAULT_SITE=sc-domain:example.com \
-e GA4_DEFAULT_PROPERTY=properties/123456789 \
-- uvx seo-analytics-mcp{
"mcpServers": {
"seo": {
"command": "uvx",
"args": ["seo-analytics-mcp"],
"env": {
"GSC_DEFAULT_SITE": "sc-domain:example.com",
"GA4_DEFAULT_PROPERTY": "properties/123456789"
}
}
}
}Then quit Claude Desktop completely (⌘Q — closing the window is not enough) and reopen.
There isno credential path in that config. The token lives in the profile directory that
seo-mcp auth wrote, so the whole block is safe to paste into a GitHub issue.
The seven-day problem
If the server works and then stops about a week later, this is why.
Google issues refresh tokens that expire after seven days for any external OAuth app whose publishing status is still Testing. The obvious setup path — create project, create client, add yourself as a test user — leaves you there.
The fix is one click: on the consent screen, set the audience to External and press
Publish app. Then uvx seo-analytics-mcp auth --reauth.
doctor flags a token young enough to still be a Testing token, and every invalid_grant error
from the server explains this in full. It is not a bug in the server — but it will be the most
common issue filed against it.
Tools
Thirteen tools: ten map to upstream operations, two join sources, and one exists purely so the model can tell a confused user what to do.
Tool | What it does | |
🔎 |
| Properties this account can read, with permission level |
🔎 |
| Clicks, impressions, CTR, position by any dimension combination |
🔎 |
| Two windows diffed — biggest movers, both directions |
🔎 |
| Index status, coverage, canonical, last crawl, rich results |
🔎 |
| Submitted sitemaps with warnings and error counts |
✍️ |
| Submits a sitemap — write scope and explicit confirm |
📊 |
| Accounts and properties, to resolve a numeric property ID |
📊 |
| Arbitrary |
📊 |
| Sessions, engagement, conversions by landing page |
⚡ |
| Checks the key file is published correctly |
⚡ |
| Batch submit — dry run by default, token-gated confirm |
🔗 |
| One URL: GSC trend, top queries, GA4 engagement, index status |
🩺 |
| Active profile, scopes, which APIs answer, what to run next |
What a response looks like
Every read tool returns the same four keys. Bounded, self-describing, and carrying its own caveats.
{
"summary": {
"source": "gsc",
"rows_returned": 10, // what you see
"rows_matched": 1847, // what exists upstream
"date_range": "2026-07-29..2026-08-25", // resolved, always echoed
"data_state": "final",
"totals": { "clicks": 4730, "impressions": 512903, "ctr": 0.0092, "position": 12.4 }
},
"rows": [ /* capped at min(row_limit, 1000) */ ],
"notes": [
"Google anonymises rare queries: these rows do NOT sum to property totals.",
"dataState=final excludes the most recent 2-3 days.",
"1837 further rows were not included inline."
],
"export": "~/.../exports/a1b2c3.csv" // only when rows spilled
}Three conventions hold everywhere:
Totals cover every row fetched, not just the rows shown — a model that sees ten rows and a
total for ten cannot tell truncation from reality. Rates are never averaged: ctr is recomputed
from clicks ÷ impressions, position is impression-weighted, engagementRate is
engaged ÷ sessions.
Caveats travel with the data. Whichever layer knows the caveat appends it: the client knows
the query dimension was requested, shape() knows how many rows it dropped, GA4 knows the
response was sampled. Docstrings alone lose them exactly when the model is looking at the numbers.
Errors name the fix. A 403 tells you which grant to check and where — never a raw Google error body.
The authorised Google account has no access to sc-domain:example.com. Confirm the
account you authorised is the one with access — Search Console grants are per-property
under Settings > Users and permissions, GA4 grants are per-property under Admin >
Property access management. If access was added recently, run `seo-mcp auth --reauth`.Configuration
Every variable is optional. Precedence: tool argument → environment → profile config.json.
Variable | Purpose |
| Default property, e.g. |
| Default GA4 property, e.g. |
| Which profile to use (default: |
| Override the profile root directory |
| Required only for IndexNow |
|
|
Dates
Every date argument accepts YYYY-MM-DD, today, yesterday or NdaysAgo. Responses echo the
absolute range they actually used, because a model that guesses today's date wrong produces an
empty result that reads as "traffic went to zero".
Search Console lags 2–3 days and retains ~16 months; ranges outside those bounds are flagged or refused rather than silently returning nothing. GA4 reports in the property's own timezone, so its dates do not line up exactly with Search Console's — the responses say so where it matters.
Writes
Two tools act on the world outside your machine. Both are deliberately awkward.
| Needs the write scope (not granted by default) and |
| Verifies your key file, then returns a |
Aconfirm flag alone is not a safety mechanism — it is an argument the model fills in,
and the same misreading that produces the wrong URLs produces confirm=true beside them.
The token is unforgeable without a dry run, and change one URL and it stops matching. Both
tools also carry destructiveHint annotations, so a client that gates destructive tools behind
its own approval prompt will do so.
Read-only scopes are the default. A stranger installing an SEO tool that immediately asks for permission to modify their Search Console properties will reasonably decline.
Profiles
Several Google accounts on one machine — for agencies holding client properties side by side.
uvx seo-analytics-mcp auth --profile client-a --client-secret ./client-a.json
uvx seo-analytics-mcp auth --profile client-b --client-secret ./client-b.json
uvx seo-analytics-mcp profiles listSet SEO_MCP_PROFILE per MCP server entry. Cache keys include the profile, so two accounts
can never serve each other's data.
A profile is one directory — the first thing you will ever ask a user to delete:
uvx seo-analytics-mcp profiles rm client-a --yesThey live in ~/Library/Application Support/seo-mcp/ (macOS), $XDG_CONFIG_HOME/seo-mcp/
(Linux) or %APPDATA%\seo-mcp\ (Windows).
Design
Four layers, strictly downhill. Get this wrong and the auth flow ends up inside a tool call, which is the failure the whole design exists to prevent.
flowchart TD
subgraph L4["Entry points"]
S[server.py<br/><i>MCPServer, stdio</i>]
C[cli.py<br/><i>auth · doctor · profiles · serve</i>]
end
subgraph L3["Tools — argument surface, docstrings, cache policy"]
T[13 handlers<br/><i>no HTTP, no credentials, no row shaping</i>]
end
subgraph L2["Clients — the only modules that speak HTTP"]
G[gsc.py]
A[ga4.py]
I[indexnow.py]
end
subgraph L1["Leaves — importable by anyone, import nobody"]
LV[shaping · errors · config · cache · auth/store · auth/scopes]
end
F[auth/flow.py<br/><i>loopback + PKCE · opens a browser</i>]
S --> T
C --> T
C -.->|only reachable from here| F
T --> G & A & I
G & A & I --> LVThe browser flow must never run inside a tool call. An MCP tool that blocks on stdio waiting for a human to finish a consent screen looks like a hung server, and the model has no way to help. One CLI command, run once, is the whole difference — and a test walks the AST of every module to enforce it.
Other rules the tests enforce mechanically: shaping.py imports no Google library (which is why
the row logic is fully unit-testable with no credentials), tools import no HTTP library, and
nothing on the server path calls print() — on a stdio transport, stdout carries JSON-RPC and a
single stray print corrupts the stream.
Troubleshooting
Symptom | Cause |
Worked, then stopped after a week | OAuth app still in Testing — see above |
| Create a Desktop app OAuth client instead |
| Wrong Google account, or no grant on that property |
| Enable it on the project that issued your OAuth client, then wait a minute |
GA4 returns a 400 | An incompatible dimension/metric pair — not every GA4 dimension works with every metric |
Server never appears in the client | Run |
Every issue report should include seo-mcp doctor --json. It contains no credentials — only
paths, versions, which checks passed and which APIs answered.
Development
uv sync --extra dev
uv run pytest -q # 147 tests · no credentials · no network
uv run python scripts/smoke.py # drives the server over real stdio JSON-RPC
uv run ruff check src testspython3 -m venv .venv && ./.venv/bin/pip install -e ".[dev]"
./.venv/bin/python -m pytest -q
./.venv/bin/python scripts/smoke.py ./.venv/bin/seo-mcpscripts/smoke.py starts the server as a subprocess, completes the MCP handshake, lists the
tools and calls several — using a throwaway profile directory, so your real token is untouched.
It is the fastest way to confirm the protocol side works before any Google credential exists.
To poke at it by hand, the MCP Inspector needs nothing beyond Node:
npx @modelcontextprotocol/inspector ./.venv/bin/seo-mcp # web UI
npx @modelcontextprotocol/inspector --cli ./.venv/bin/seo-mcp \
--method tools/call --tool-name auth_status # scriptableNot covered by automated tests: the OAuth flow itself and live IndexNow submission. Both need a human and a real domain, and mocking them would only test the mock. They belong in a short manual release checklist.
Two things it will not do
IndexNow does not reach Google. Participants are Bing, Yandex, Naver, Seznam.cz, Yep and
Amazon — one endpoint propagates to all of them. Google does not participate, and Google's own
Indexing API only accepts pages carrying JobPosting or BroadcastEvent structured data. If
you install this expecting faster Google indexing, you will be disappointed.
Query rows never sum to totals. Google anonymises rare queries, so any breakdown by the
query dimension undercounts. Every response carrying that dimension repeats the caveat,
because a model handed those rows will otherwise compute confidently wrong percentages.
Contributing
Issues and pull requests welcome. The credential-free test suite runs on every push across Linux, macOS and Windows on Python 3.10 and 3.13 — if it passes locally it will pass in CI.
Renaming a tool or changing an argument breaks every saved prompt a user has. Those changes go in CHANGELOG.md and are a minor bump before 1.0, a major one after.
Licence
MIT.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseAqualityDmaintenanceIntegrates with Google Search Console to enable querying search analytics, comparing performance periods, generating visual reports, and identifying SEO optimization opportunities through natural language.59
- FlicenseNot gradedqualityBmaintenanceEnables querying Google Search Console data via natural language, providing tools for site traffic analysis, page changes, and optimization opportunities.
- AlicenseNot gradedqualityCmaintenanceEnables querying Google Search Console and Google Analytics 4 through natural language, with tools for SEO analysis like anomaly detection, cannibalization detection, and opportunity scoring.231MIT
- FlicenseBqualityCmaintenanceEnables natural language querying of marketing analytics across Google Search Console, GA4, Google Ads, HubSpot, and Bing. Provides tools for search queries, traffic, campaign performance, and composite cross-platform rollups.79
Related MCP Connectors
Turn Search Console data into SEO actions, content, publishing, indexing, and AI insights.
SEO research, audits, backlinks, GSC, and content workflow tools for AI agents.
Ask AI about your ads — query Meta, TikTok, and Google Ads performance in natural language.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/zainsive/seo-analytics-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server