Replacing a Paid SEO Data Connector With a Self-Hosted MCP Server

Our weekly SEO agents read Google Search Console and Google Analytics 4 data through a paid third-party connector. We replaced it with a small, self-hosted MCP server that we built and run ourselves. This is how it works, the design decisions behind it and what we would tell anyone building one.

Engineering 7 min read

The problem with the connector we had

Quantic Systems runs scheduled AI agents that report on search performance for our own sites. Each week an agent pulls clicks, impressions and positions from Search Console, sessions and lead events from GA4, compares them with the previous week and writes a digest.

The agents got that data through a paid connector. It worked, but it had three costs.

  • Another subscription. We were paying a vendor to sit between our agents and two free Google APIs.
  • A slower call pattern. Queries were scheduled, then polled until results were ready. Agents spent turns waiting instead of analyzing.
  • Less control. The connector decided what was exposed and how results were shaped. Changing that meant waiting on someone else's roadmap.

The data itself is free. Search Console and the GA4 Data API are public Google APIs. What we needed was a thin, reliable adapter between those APIs and our agents.

What we built

The result is a remote MCP server for Google Search Console and GA4. MCP, the Model Context Protocol, is the open standard AI assistants such as Claude use to call external tools. Our server exposes four of them.

gsc_query

Search Console Search Analytics: clicks, impressions, CTR and position by query, page, country, device, date or search appearance, with filters and an optional previous-period comparison.

ga4_query

GA4 Data API reports: any metrics by any dimensions, with filters and ordering.

list_properties

The sites an agent is allowed to query, by short client key.

get_today

The current UTC date and the latest date with finalized data in each API.

An agent asks for last week's queries with a comparison window in one call:

{
  "client": "quanticsys",
  "date_range": "last_7_days",
  "dimensions": ["query"],
  "compare": true
}

It gets back the resolved date range, normalized rows, totals and, because compare is on, the same data for the previous equal-length period. One request, one response. No schedule, no polling.

Need this kind of access to your own data?

We reply with next steps, not a sales deck.

Book a Meeting

Design decisions

A thin adapter, not an analytics engine

The server owns the how: authentication, request translation, response normalization, input validation, date math and routing a client key to the right property. The agent owns the what: which site, which dates, which metrics and all of the analysis.

Keeping business logic out of the server keeps it small and predictable. When the analysis changes, we change a prompt, not a deployment.

Results the agent can use directly

Raw API responses are not agent-friendly. The server normalizes them into flat rows with named fields and computes totals over the returned rows. CTR is recomputed from clicks and impressions, and average position is weighted by impressions, so the totals are correct rather than an average of averages.

Smaller, cleaner responses mean fewer tokens per run and fewer chances for the model to misread the data.

Honest date ranges

Search Console data takes a few days to finalize. GA4 takes about a day. If an agent asks for "the last 7 days" and the range ends today, the newest days look like a traffic collapse.

So relative ranges end at the last available day, not today: three days back for Search Console, one day back for GA4. The get_today tool reports both dates, and every response echoes the range it actually used. Previous-period comparisons are computed from that resolved range, in UTC, so week-over-week numbers line up.

Stateless by rule

The server uses MCP's Streamable HTTP transport in stateless mode. Each request gets a fresh server instance that is torn down when the response closes. Nothing about a user or a session lives in memory between calls.

That is a hard rule in the codebase, not a preference. Statelessness is what makes the next decision possible.

Scale to zero on Azure Container Apps

The server runs as a container on Azure Container Apps with a minimum of zero replicas. When no agent is calling it, nothing is running and nothing is billed. When a scheduled agent calls it, a replica starts, serves the request and scales back down.

The cost is a cold start of a few seconds on the first call after idle. For an agent that runs weekly, that is the right trade. A stateful server could not do this, because any request has to be servable by a replica that was not running a moment ago.

Deployment is automated. A merge to the main branch builds the image, pushes it to a container registry and deploys to Azure through GitHub Actions, signed in with OIDC so no Azure password is stored in the pipeline.

Two separate auth boundaries

A remote MCP server holding analytics access needs two kinds of authentication, and they should never mix.

  1. 01

    Agent to MCP server: OAuth 2.1. The server is an OAuth resource server. It does not issue tokens. An external authorization server does, using Authorization Code with PKCE. The MCP server publishes standard protected-resource metadata so clients can discover where to sign in, then validates every access token's signature, issuer, expiry and audience before a tool runs. Switching identity providers is a configuration change, not a code change.

  2. 02

    MCP server to Google: a read-only service account. The server reads Google data as one service account, granted Viewer access to each property it serves. Its scopes are read-only for both Search Console and Analytics.

Errors from Google are reduced to a safe message before they reach the agent. Credentials and stack traces never leave the server.

Adding a site is configuration

Each site is a short client key mapped to a Search Console property and a GA4 property ID. Adding one means granting the service account Viewer access and adding an entry to the client map. No code changes and no new tools.

What changed for our agents

One call per question

Week-over-week comparisons come back in the same response.

No connector subscription

Two free Google APIs, one small container and no charge while idle.

Data we can trust

Finalized date ranges, correct totals and no stale cache, because nothing is cached.

Changes on our schedule

When an agent needs a new dimension or filter, we add it.

The first consumer was the weekly SEO agent for TasiPsych, our clinical documentation product. The same server now serves the agent that reports on quanticsys.io.

What we would tell anyone building one

  • Start stateless. If you want scale-to-zero, decide it on day one. Retrofitting statelessness is harder than building for it.
  • Keep the server thin. Put analysis in the agent. Put correctness, security and data shape in the server.
  • Fix the dates in the server. Data lag is the most common source of wrong conclusions in analytics agents. Resolve it once, centrally, and report what range was used.
  • Separate the auth boundaries. Who may call the server and what the server may read are different questions with different answers.
  • Test the pure parts. Our request builders and response normalizers are tested against mocked Google responses, apart from the HTTP and MCP layers.

Stack

We built this server in TypeScript because it fit the job. The language is not the pattern. We build MCP servers in Python and TypeScript, and host them on Azure, AWS or GCP, matched to the systems and team they serve.

This server's stack:

Runtime Node.js 22 · TypeScript
Protocol Model Context Protocol (official TypeScript SDK) · Streamable HTTP, stateless
Google APIs Search Console API · GA4 Data API · Service-account authentication
Auth OAuth 2.1 resource server · Authorization Code with PKCE · JWT validation
Hosting Docker · Azure Container Apps (scale-to-zero) · GitHub Actions with OIDC
FAQ

Frequently asked questions

What is an MCP server for Google Search Console?

It is a service that exposes Search Console data to AI agents through the Model Context Protocol. The agent calls a tool such as gsc_query with a property, date range and dimensions, and gets clicks, impressions, CTR and position back as structured rows it can analyze.

Can one MCP server cover both Search Console and GA4?

Yes. Ours exposes four tools: gsc_query for Search Console, ga4_query for the GA4 Data API, list_properties to show which sites an agent may query, and get_today to report the latest date with finalized data in each API.

Why self-host instead of using a hosted connector?

Control over cost, data access and behavior. A self-hosted server reads Google data with a read-only service account you own, returns results in one call, and costs nothing while idle when it runs on a platform that scales to zero.

How is a remote MCP server secured?

Ours acts as an OAuth 2.1 resource server. Clients sign in through an external authorization server using Authorization Code with PKCE, and the MCP server validates every access token's signature, issuer, expiry and audience before any tool runs. Google access is a separate boundary, handled by a read-only service account.

Does the MCP server store analytics data?

No. It is stateless. Every request is answered live from the Google APIs. There is no warehouse, no ETL job and no per-user session state.

Can Quantic Systems build an MCP server for our internal systems?

Yes. We build custom MCP servers that connect AI assistants and agents to internal systems and data, with scoped access, token validation and logging. Book a call and tell us which systems your agents need to reach.

Need an MCP server for your own systems?

Search Console and GA4 are one example. The same pattern connects agents to CRMs, ERPs, internal databases and line-of-business tools, with scoped access and a clear security boundary. See our AI agents and MCP server development work.