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.
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.
- 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.
- 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: