How to Build a Google Analytics MCP Server
Try Google's own GA4 MCP server first: it's experimental, runs locally in Python, and covers GA4 on your machine. To build your own, write one run_report tool with the MCP TypeScript SDK and @google-analytics/data, log in with gcloud and the analytics.readonly scope, and register it with claude mcp add. Hosting it for other people is the hard part: you need MCP's OAuth flow, Google's verification for a sensitive scope (unverified apps are capped at 100 users), and caching to stay inside the Data API's per-property quotas.
Every Wednesday. 28,400+ operators. Zero fluff.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
A GA4 MCP server that runs on your laptop is an afternoon’s work. One that other people can sign in to is a much bigger job. This guide gives you the afternoon version, then lists what the bigger one involves, so you can decide which you need.
Table of contents
Open Table of contents
Check the official server first
Google publishes its own Google Analytics MCP server, still marked experimental. It runs on your own computer in Python, and you enable the Google Analytics Admin API and Data API in a Google Cloud project before it works. It already has run_report, run_realtime_report, run_funnel_report and lookups for your accounts, properties, custom dimensions and Google Ads links. If you only need GA4, only on your machine, use it:
gcloud auth application-default login \
--scopes https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform \
--client-id-file=YOUR_CLIENT_JSON_FILE
pipx run analytics-mcpGoogle’s setup page walks through the Cloud project, and the README has the exact claude mcp add line for Claude Code. There’s no hosted version, so it only works where you can run Python.
Build your own when you want tools shaped around your questions, data from more than one source in a single answer, or a server your team or clients can use.
What you need
- A Google Cloud project with the Google Analytics Data API enabled (five minutes).
- An OAuth client ID for a desktop app in that project, downloaded as JSON. That’s the file
--client-id-filepoints to. - Node 22 or newer, which the current
@google-analytics/datapackage requires. - Viewer access to the GA4 property, on the Google account you’ll sign in with.
Step 1: One tool that runs any report
Start with a single run_report tool. Claude already knows GA4’s metric and dimension names, so one flexible tool covers most questions. Split it into narrower tools later, once you can see which questions you actually ask.
Set up the folder and log in once. The second gcloud command matters: Google’s Analytics APIs reject user credentials that don’t name a quota project.
mkdir ga4-mcp && cd ga4-mcp
npm init -y && npm pkg set type=module
npm install @modelcontextprotocol/sdk @google-analytics/data zod
gcloud auth application-default login \
--scopes https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform \
--client-id-file=YOUR_CLIENT_JSON_FILE
gcloud auth application-default set-quota-project YOUR_PROJECT_IDThen save this as ga4.ts:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { BetaAnalyticsDataClient } from '@google-analytics/data';
import { z } from 'zod';
const ga = new BetaAnalyticsDataClient(); // Application Default Credentials
const server = new McpServer({ name: 'ga4', version: '1.0.0' });
server.registerTool(
'run_report',
{
description:
'Run a GA4 report. Uses GA4 Data API metric and dimension names. ' +
'Dates accept YYYY-MM-DD, "28daysAgo", "yesterday", "today".',
inputSchema: {
propertyId: z.string().describe('Numeric GA4 property ID, e.g. 123456789'),
metrics: z.array(z.string()).describe('e.g. ["sessions", "engagedSessions"]'),
dimensions: z.array(z.string()).default([]).describe('e.g. ["sessionDefaultChannelGroup"]'),
startDate: z.string().default('28daysAgo'),
endDate: z.string().default('today'),
},
},
async ({ propertyId, metrics, dimensions, startDate, endDate }) => {
const [res] = await ga.runReport({
property: `properties/${propertyId}`,
dateRanges: [{ startDate, endDate }],
metrics: metrics.map((name) => ({ name })),
dimensions: dimensions.map((name) => ({ name })),
limit: 100,
});
// Turn GA's nested rows into plain { dimension: value, metric: value } objects.
const rows = (res.rows ?? []).map((row) => {
const out: Record<string, string> = {};
row.dimensionValues?.forEach((v, i) => (out[dimensions[i]] = v.value ?? ''));
row.metricValues?.forEach((v, i) => (out[metrics[i]] = v.value ?? ''));
return out;
});
return { content: [{ type: 'text', text: JSON.stringify({ rowCount: res.rowCount, rows }) }] };
},
);
await server.connect(new StdioServerTransport());There’s no key in the file. The client picks up the login gcloud just saved, and every call runs as you, with your Viewer access.
Step 2: Add it to Claude
In Claude Code, one command registers it. Use the full path to the file. --scope user makes it available in every project; leave it off and it only loads in the folder where you ran the command:
claude mcp add --scope user ga4 -- npx -y tsx /full/path/to/ga4-mcp/ga4.tsThen ask: “Using property 123456789, which channels drove the most engaged sessions last month?” Claude picks the metrics, calls the tool, and explains the rows. On Claude Desktop the same server goes in the config file instead; my first MCP server guide shows where that lives.
Step 3: Where it gets hard
Everything above runs as you, on your machine. Here’s what changes when it has to run anywhere else:
- Hosting and OAuth. claude.ai and the mobile app can’t reach a server on your laptop. A hosted server needs MCP’s OAuth flow, so each user signs in to their own Google account, plus somewhere safe to keep their refresh tokens.
- Google’s app verification.
analytics.readonlyis a sensitive scope. Until Google verifies your app, every user sees an “unverified app” warning and you’re capped at 100 users. Verification means a privacy policy on your own domain, a demo video of the sign-in, a written reason for each scope and a review. - Quotas. The Data API meters tokens per property: 40,000 an hour and 200,000 a day on a standard property, with any one Cloud project capped at 14,000 an hour per property and 10 requests at once. Most requests cost 10 tokens or fewer. Long date ranges, many dimensions and busy properties cost more, and an agent fires reports much faster than a person clicking through GA4. Hosted, every call comes out of your project’s share, so add
returnPropertyQuota: trueto see what each request costs, cache what repeats, and keep reports small.
SiteWide takes care of the hosting and the sign-in. Its Google verification is still in progress, so GA4 shows the same unverified-app screen there for now. If you’d rather not build any of this, connect SiteWide instead.
Tell Claude to build it
Paste this into Claude Code in an empty folder:
Build me a TypeScript MCP server for Google Analytics 4 using @modelcontextprotocol/sdk and @google-analytics/data. Give it one tool, run_report, that takes a property id, metrics, dimensions and a date range. Authenticate with Application Default Credentials. Then give me the exact gcloud command to log in with the analytics.readonly scope, and register the server with `claude mcp add`. Don't invent metric names: link me to Google's API schema page so I can check them.Before you trust the result, check every metric and dimension it uses against Google’s API schema page. A made-up name looks fine in code and fails only when the API rejects it.
Every Wednesday. 28,400+ operators. Zero fluff.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
Related posts
The Claude Skill That Writes My Google Ads Copy
I built a Claude skill that pulls Google Ads data via GAQL, flags underperforming RSAs by CTR and CPA, and rewrites headlines inside Google's character limits.
AI AgentsClaude Cowork Scheduled Tasks: The Setup Guide
Claude Cowork's scheduled tasks turn a one-time prompt into a recurring job that fires on its own — no code, no server. Here's how to set one up right.
AI AgentsAI Agent Services Agencies Can Sell in 2026
Six AI agent automations I run in my own business, reframed as service line items agencies can scope, price, and sell to clients.
Get the AI playbook in your inbox
Every Wednesday. 28,400+ operators. Zero fluff.
Check your inbox.
We sent you a confirmation email — click the link inside to complete your subscription. Check spam if you don't see it within a minute.
You're subscribed.
Welcome — the next edition lands in your inbox soon.
You're already on the list — look for it every Wednesday.