Google Search Console MCP Server connects AI clients (Windsurf, Devin, Factory Droid) to the Google Search Console API. An agent can conversationally ask about traffic, sitemaps, properties, and URL indexing status - the server returns raw GSC data enriched with contextual observations (hints).
Built on the streamable-mcp-server-template by Adam Gospodarczyk (AI_DEVS, overment) and extended into a complete, deployment-ready MCP server across five epics.
📋 Project metrics
- Start: July 2026
- Status: MVP v1 complete and deployment-ready (experimental project, further development as v2)
- Role: System Architect / Full-Stack Developer
- Goal: Expose Google Search Console data to LLM agents through typed MCP tools
- Validation: 753 tests (0 fail), lint + typecheck clean, CI 17 s, Docker smoke (cold start 0.418 s, RAM 27 MB idle, volume persistence)
🚀 Product journey
- Architecture: Layered architecture with Tool Registry (AD-1..AD-14) - brownfield ratification of the template, consistent rules for 4 tools.
- Foundation: MCP SDK upgrade to ^1.29.0 (Streamable HTTP), full Cloudflare Workers cleanup, migration from
better-sqlite3tobun:sqlite+ drizzle-orm. - Auth:
GscAuthService- Service Account JWT RS256 (RFC 7523), token exchange, 1h cache with auto-renew and fail-fast startup. - API client:
GscApiClient- single interface to GSC, siteUrl encoding, smart retry (exponential backoff, daily-limit aware), unified error model. - Tools: Four
gsc__tools with actions as a discriminated union -sites(list/get/add/delete),analytics(query with auto-pagination),sitemaps(list/get/submit/delete),inspect(URL Inspection). Write Operations guarded by an elicitation gate. - Caching:
UrlInspectionCache- SQLite 24h TTL protecting the 2,000 daily URL Inspection quota, with graceful degradation. - Production: Multi-stage Dockerfile (pure bundle, USER bun, HEALTHCHECK), GitHub Actions CI, graceful shutdown with drain phase (bounded cleanup, deterministic exit 0/1).
🎯 Business problem
Google Search Console data is available, but the barrier between the data and the ability to analyze it is high. A person with years of SEO experience has intuition, but manually browsing the GSC UI is tedious enough that analyses simply never get done. An LLM agent can sift through large amounts of data, but has no access to GSC.
The MCP server closes this gap: the agent becomes the analyst, the human the strategist.
❌ Pain points and operational challenges
- Tedious UI: Browsing the GSC panel across 4-6 properties is repetitive manual work.
- API quotas: URL Inspection allows 2,000 queries/day/property, Search Analytics 1,200/minute - easy to exhaust.
- siteUrl formats:
sc-domain:example.comandhttps://example.com/require correct path encoding. - Write Operations: Adding/removing properties and sitemaps is destructive - the API has no granular permission control.
- Delayed data: Search Analytics lags 2-3 days, which without context leads to wrong conclusions.
- Diagnostics: Checking the indexing status of a specific URL requires a separate tool and tedious navigation.
💡 Why it works (product approach)
- 4 tools instead of 10 endpoints: Consolidated into
gsc__sites,gsc__analytics,gsc__sitemaps,gsc__inspect- fewer schemas in the LLM context, fewer mistakes, lower token usage. - Hints as observations, not directives: The server describes the data (e.g., "7 queries at positions 5-10 with CTR < 3% - optimization potential"), the agent forms its own hypothesis.
- Auto-discovery fan-out: When the agent omits
siteUrl, the server finds the properties itself, fans out with rate limiting, and labels results per property. - Smart retry: Distinguishes per-minute limits (retry with backoff) from daily limits (immediate error with recovery hints) - no wasted API calls.
- Protective caching: URL Inspection cache with 24h TTL eliminates repeated queries for the same URL (2,000 QPD quota).
- Elicitation gate: Write Operations require user confirmation (kill switch
WRITE_OPS_ENABLED+ elicitation) - the only protection against unauthorized changes. - Zero-interaction auth: Service Account JWT works headless - no OAuth popups, token auto-renews every 5 minutes.
- Graceful shutdown with drain phase: Safe redeploys - in-flight requests finish, data (sessions, cache, tokens) is flushed before exit.
📈 Impact on work (ROI)
| Area | Before Google Search Console MCP | With Google Search Console MCP | Effect |
|---|---|---|---|
| GSC data analysis | Manual panel browsing | Conversational agent queries | Less context switching |
| Finding striking-distance queries | Manual CSV export and sorting | Query with query/page dimensions + hints | Ready candidates for content plan |
| SEO problem diagnosis | Manual panel digging | gsc__inspect with 24h cache | Faster indexing diagnosis |
| Write Operations | Risk of costly mistakes | Elicitation gate + kill switch | Safe change management |
„Instead of drowning in the GSC panel, you can simply ask the agent: what is going on with domain X? You get the data with context for making decisions.”
🛠️ Architecture and tech stack
- Runtime: Bun 1.3.0 (strict TypeScript, ESM)
- Protocol: MCP Streamable HTTP (@modelcontextprotocol/sdk ^1.29.0)
- HTTP: Hono + @hono/node-server
- Schema: Zod ^3.23 (JSON Schema for MCP,
.describe()on every field) - JWT/JWS: jose (Service Account RS256)
- Storage: bun:sqlite (sessions + cache), drizzle-orm/bun-sqlite, encrypted token store (AES-256-GCM)
- Tests: bun test (753 tests, 17 files)
- CI/CD: GitHub Actions (lint + typecheck + test, 17 s, bun 1.3.0 pin)
- Deployment: Multi-stage Docker (
oven/bun:1.3.0-alpine, pure bundle 111 MB, USER bun) + Dokploy/Traefik
🔒 Security and operational decisions
- Service Account JSON only in env/Docker secrets - never in git.
- Server access protected by Bearer token (AUTH_STRATEGY: none/bearer/oauth/api_key/custom).
- Client tokens encrypted with AES-256-GCM (RS_TOKENS_ENC_KEY).
- Logger sanitizes sensitive values - secrets never reach logs.
- Fail-fast startup in production: no valid SA JSON = no server start.
- Write Operations blocked by elicitation gate + WRITE_OPS_ENABLED kill switch.
- Graceful shutdown: drain (30 s) + force-close grace (2 s) + cleanup bound (15 s), deterministic exit 0/1,
tokenStore.flush()awaited before exit. - Container runs as non-root (USER bun), persistent data in VOLUME /app/.data.
✅ Validation
The project includes:
- 753 unit and integration tests (0 fail, 1,718 expect)
- lint (Biome) + typecheck (tsc) clean, GitHub Actions CI green in 17 s
- drain phase tests (single-close, timeout, resilience, re-entry guard, exit policy)
- cache tests (TTL, graceful degradation, corrupted-row resilience)
- Docker smoke: build, /health 200, cold start 0.418 s (< 2 s NFR), RAM 27 MB idle (< 80 MB NFR), volume persistence across redeploys
- graceful shutdown smoke: SIGTERM →
Graceful shutdown complete+ tokens.json written + exit 0 - E2E flow through MCP Inspector against real GSC data
🚀 Next steps
- Triage ~20 deferred debt items from deferred-work.md (fix vs consciously keep).
- OAuth user flow / multi-user - many users with their own properties (oauth4webapi already in deps).
- Production hardening: atomic token write, volume monitoring, .data backup.
- Extending the analytics toolkit - more MCP servers per the PRD vision.
- Public distribution - the project is marked experimental, not all roadmap features are implemented yet.
Artifacts
- Source code: RafalWojciechRolsky/mcp-server-gsc
- MCP server:
mcp-server-gsc - Template: streamable-mcp-server-template (Adam Gospodarczyk / overment / AI_DEVS)
- API: Google Search Console (webmasters/v3 + searchconsole.googleapis.com/v1)
