Google Analytics MCP Server
Click on "Deploy 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., "@Google Analytics MCP ServerWhat were our top traffic sources for last week?"
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 Analytics MCP Server
MCP (Model Context Protocol) server that integrates with the Google Analytics 4 Data API. Lets AI agents (Claude, Cursor, etc.) query traffic, acquisition sources, top pages, audience breakdowns, geographic performance, events, conversions, and realtime data — all through natural language.
Two operating modes
Mode | Who uses it | Auth | Google refresh token |
stdio | Just you (Claude Desktop / Cursor local) | None — local subprocess | Single, in |
HTTP multi-tenant | Team / remote access | Native MCP OAuth 2.1 — each user authenticates with their own Google account | One per user, encrypted on disk |
Pick stdio for personal use (simpler). Pick HTTP when multiple users (with different Google accounts) need to share the same server.
Related MCP server: Google Analytics MCP Server
Available tools
Tool | Description |
| Main KPIs (sessions, users, pageviews, bounce, engagement, conversions), optionally compared to the previous period |
| Acquisition grouped by channel, source/medium or campaign |
| Most-visited pages with engagement metrics; optional path-prefix filter |
| Sessions/conversions by country, region or city, with highlights |
| Audience breakdown by device, browser or operating system |
| Top events by count; optional filter to specific event names |
| Conversion events with revenue, per channel and event |
| Users active right now, by page, source, country and device |
All accept: flexible date ranges (last_7_days, last_14_days, last_30_days, this_month, last_month, custom YYYY-MM-DD), output format (markdown or json), and optional property_id.
Setup A — stdio mode (single-tenant)
1. Prerequisites
Python 3.12+
A GA4 property you have access to
Project in Google Cloud Console with the Google Analytics Data API enabled
2. Installation
git clone https://github.com/minholi/google-analytics-mcp.git
cd google-analytics-mcp
uv sync3. "Desktop" OAuth Client in GCP
APIs & Services → Credentials → Create Credentials → OAuth client IDApplication type: Desktop app
Save the
Client IDandClient secret
4. Refresh token
Interactive wizard:
uv run python scripts/get_refresh_token.pyPaste the Client ID/Client secret when prompted; it opens the browser, you log in, and copy the resulting refresh_token.
5. .env
GOOGLE_ANALYTICS_CLIENT_ID=...apps.googleusercontent.com
GOOGLE_ANALYTICS_CLIENT_SECRET=...
GOOGLE_ANALYTICS_REFRESH_TOKEN=...
GOOGLE_ANALYTICS_PROPERTY_ID=123456789Find the numeric property ID in Analytics Admin → Property details.
6. Client configuration
Claude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"google-analytics": {
"command": "uv",
"args": ["run", "python", "main.py", "--transport", "stdio"],
"cwd": "/path/to/google-analytics-mcp"
}
}
}Cursor — .cursor/mcp.json:
{
"mcpServers": {
"google-analytics": {
"command": "uv",
"args": ["run", "python", "main.py", "--transport", "stdio"],
"cwd": "/path/to/google-analytics-mcp"
}
}
}uv run loads the .env automatically via python-dotenv.
Setup B — HTTP multi-tenant mode
How it works
Claude Desktop ──(MCP OAuth: tokens issued by us)──▶ our MCP (AS+RS)
│
└──(Google OAuth: user's refresh_token)──▶ GA4 Data APIThere are two chained OAuth flows: the server is simultaneously an Authorization Server (issues its own JWTs to Claude Desktop) and a Google OAuth Client (holds the encrypted refresh_token for the user's Google account). Desktop never sees the Google refresh_token.
UX in Claude Desktop:
User adds the MCP URL under Settings → Connectors (once).
Clicks "Connect" → browser opens → Google login →
analytics.readonlyconsent → returns connected.From then on, all calls are authenticated. Refresh is silent.
1. Prerequisites
Public domain with TLS (e.g.,
mcp.your-domain.com)Caddy (or Nginx) running on the host — will terminate TLS and reverse-proxy to
127.0.0.1:8000Docker + Docker Compose
2. "Web application" OAuth Client in GCP
⚠️ Separate client from the Desktop one used in Setup A.
Enable the Google Analytics Data API if you haven't already.
Configure the OAuth consent screen:
User type: External
Add the scope
https://www.googleapis.com/auth/analytics.readonlyUnder "Test users" add the emails that will test (or click Publish).
Create Credentials → OAuth client ID:
Application type: Web application
Authorized redirect URIs:
https://mcp.your-domain.com/auth/callback(must match theMCP_PUBLIC_URLyou configure exactly)
Save the
Client IDandClient secret.
3. (Optional) Generate a JWT signing key
# Only if you want to pin the key (e.g., multiple replicas).
# Without this, GoogleProvider derives the key from the client_secret.
uv run python -c "import secrets; print(secrets.token_urlsafe(48))"4. .env
# Web OAuth Client (step 2)
GOOGLE_OAUTH_WEB_CLIENT_ID=123456789-abc.apps.googleusercontent.com
GOOGLE_OAUTH_WEB_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxx
# Public URL — no trailing slash, with https
MCP_PUBLIC_URL=https://mcp.your-domain.com
# Optional: pinned key for signing MCP JWTs (step 3)
# OAUTH_JWT_SIGNING_KEY=Z9K1u7c-paste-the-output-of-secrets-token-urlsafe-48
# Server
MCP_TRANSPORT=http
MCP_HOST=0.0.0.0
MCP_PORT=8000Setup A variables (GOOGLE_ANALYTICS_CLIENT_ID, GOOGLE_ANALYTICS_CLIENT_SECRET, GOOGLE_ANALYTICS_REFRESH_TOKEN) are not used in HTTP mode — you can remove or comment them out. GOOGLE_ANALYTICS_PROPERTY_ID is still handy as a default that individual users can override per tool call.
5. Host Caddy
mcp.your-domain.com {
reverse_proxy 127.0.0.1:8000
}6. Start
mkdir -p data
docker compose up -d --build
docker compose logs -f google-analytics-mcp7. Verification
curl https://mcp.your-domain.com/.well-known/oauth-authorization-serverShould return JSON with issuer, authorization_endpoint, etc.
8. Connect from Claude Desktop
Settings → Connectors → Add custom connector → paste https://mcp.your-domain.com/mcp → click Connect → Google flow opens → log in → property authorized.
Done. Other users can do the same on their machines.
Environment variables
Common (both modes)
Variable | Description |
| Default numeric GA4 property ID (optional; can be passed per call) |
|
|
| HTTP port (default |
| HTTP host (default |
stdio mode
Variable | Required | Description |
| ✅ | Desktop OAuth Client ID |
| ✅ | Desktop OAuth Client Secret |
| ✅ | Refresh token generated by |
HTTP multi-tenant mode
Variable | Required | Description |
| ✅ | Public URL, no trailing |
| ✅ | Web OAuth Client ID |
| ✅ | Web OAuth Client Secret |
| — | Optional. If absent, |
Architecture
google-analytics-mcp/
├── main.py # Entry point — stdio or HTTP (mcp.http_app)
├── src/
│ ├── server.py # 8 MCP tools (fastmcp) + conditional GoogleProvider
│ ├── client.py # GA4 Data API v1beta client (httpx + REST)
│ ├── auth.py # GoogleAnalyticsAuth — env vars (stdio) or injected access_token (HTTP)
│ └── formatters.py # Markdown / JSON
├── scripts/
│ └── get_refresh_token.py # OAuth wizard (stdio mode)
├── Dockerfile
├── docker-compose.yml # Exposes 127.0.0.1:8000 (host Caddy handles TLS)
├── pyproject.toml
└── .env.exampleData flow (HTTP mode): tool call → JWT verified by GoogleProvider (token-swap JTI → decrypted upstream Google access_token) → get_access_token().token → GoogleAnalyticsAuth.for_access_token → GA4 Data API REST v1beta → formatter. The Google refresh_token stays encrypted in GoogleProvider's internal key-value store; refresh is transparent when the access_token expires.
Usage examples (natural language)
"Show the overview for the last 7 days, comparing with the previous week"
"Which channels are driving the most sessions in the last 30 days?"
"Which pages have the highest bounce rate this month?"
"Where are my users coming from geographically?"
"How is the audience split between mobile and desktop?"
"Which events are firing most in the last 14 days?"
"How many conversions did I get last month, and where did the revenue come from?"
"How many users are on the site right now?"See USAGE_GUIDE.md for scenario-driven playbooks and a full tool reference.
Operations
Logs
docker compose logs -f google-analytics-mcpBackup
GoogleProviderpersists DCR clients and encrypted Google refresh_tokens in./data/(mounted from the host). Back this directory up if user reconnection would be disruptive.OAUTH_JWT_SIGNING_KEY— if you change it (or if you're using the default derivation and changeGOOGLE_OAUTH_WEB_CLIENT_SECRET), all users must reconnect.
Rebuild after code changes
docker compose up -d --buildTroubleshooting
Symptom | Likely cause | Fix |
| URI in GCP ≠ | Check character by character (https, no trailing slash in |
"Google did not return a refresh_token" in the callback | User previously authorized the app (Google only returns | Ask them to revoke at myaccount.google.com/permissions and reconnect |
| MCP JWT expired or | Desktop refreshes on its own; if it persists, reconnect from Connectors |
Tool returns | User doesn't have access to the requested property, or the property_id is wrong | Confirm the account has at least Viewer access in GA4 |
Docker healthcheck failing | Missing |
|
| Broken DNS/TLS or Caddy not routing |
|
Security
Credentials never in code — always in
.env(already in.gitignore).Host Caddy — terminates TLS. The container only listens on
127.0.0.1:8000, not reachable directly from the internet.Rate limiting — configure on the host Caddy, not in the app.
Container runs as a non-root user.
Google refresh_tokens encrypted at rest by
GoogleProvider's internal key-value store (persisted in./data/).Short-lived MCP JWTs with transparent upstream Google refresh and MCP refresh token rotation (OAuth 2.1) — all managed by
GoogleProvider.Treat
OAUTH_JWT_SIGNING_KEY(if set) andGOOGLE_OAUTH_WEB_CLIENT_SECRETas critical secrets (vault/secret manager in production).
License
MIT — see LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Connect Google Analytics to ChatGPT. Query GA4 data in plain English and get instant insights.
Query your Betterlytics web analytics from AI agents: traffic, funnels, journeys, errors, uptime.
GA4, Google Ads and Search Console in Claude. Read-only OAuth, multi-account for agencies.
Real-time web analytics for AI agents: query traffic, funnels, revenue, and manage your sites.
Related MCP Servers
- AlicenseAqualityBmaintenanceConnects Google Analytics 4 data to Claude, Cursor and other MCP clients, enabling natural language queries of website traffic, user behavior, and analytics data with access to 200+ GA4 dimensions and metrics.101,020 PyPI241MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with Google Analytics 4 data, providing tools for historical reporting, real-time activity monitoring, and property management. It supports secure service account authentication to access metrics like traffic summaries, user acquisition, and custom dimensions.MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to query Google Analytics 4 data, including listing accounts and properties, running historical and real-time reports with customizable metrics and dimensions.-
- FlicenseNot gradedqualityCmaintenanceEnables querying Google Analytics 4 data through natural language, including running reports, comparing periods, and exploring realtime metrics across multi-tenant properties with OAuth-based authentication.-