analytics-selfhosted-mcp

analytics-selfhosted-mcp

Self-hosted MCP server for Google Analytics over HTTP, providing URL-addressable access with API key auth. It enables clients to run reports, list accounts/properties, and use compact paginated reports for token efficiency.

Category
Visit Server

README

analytics-selfhosted-mcp

Unofficial / community project. This is not an official Google product.
Official Google Analytics MCP (local stdio only):
googleanalytics/google-analytics-mcp (PyPI: analytics-mcp).

Self-host the same GA Admin/Data tools over Streamable HTTP on Modal, so clients that only accept a public URL (Cursor remote MCP, agents, etc.) can call Google Analytics with an API key — plus token-efficient report compaction that upstream does not ship.

Official Google MCP This project
Status Official Unofficial wrapper / host
Transport stdio Streamable HTTP (https://…/mcp)
Runs on Your laptop Modal (or any host you deploy)
Auth to MCP Process trust Shared API key
Extra — Compact reports + paged rows

When to use official: one machine, Cursor/Claude Desktop via command + stdio.
When to use this: you need a URL-addressable Analytics MCP.

ClickUp App Center auth works but tools do not load yet (client never calls tools/list). Details: docs/CLICKUP_ISSUE.md.


Quick path (what “done” looks like)

Step Done when…
0. Prerequisites uv, modal, GCP APIs enabled
1. Env file .env exists, keys set, not in git
2. Google credentials SA (or ADC) can list GA accounts
3. Modal secret Secret analytics-mcp-creds created
4. Deploy /healthz returns JSON status: ok
5. MCP smoke tools/list returns 15 tool names
6. GA smoke get_account_summaries returns accounts/properties
7. Cursor Chat can call ping → pong

Do not skip the Done when checks — each later step depends on the previous one.


Use cases and suggested queries

Ask these in Cursor (or any MCP client) after step 7. Complexity increases downward.

Level 1 — Connectivity & inventory

Use case Suggested prompt
Prove MCP works “Call ping on the Analytics MCP.”
List what I can access “Use get_account_summaries and list my GA accounts and property IDs.”
Inspect one property “Call get_property_details for property PROPERTY_ID and summarize timezone, currency, and industry.”

Pass: you get real account/property names (not 401 / empty / credential errors).

Level 2 — Simple reporting

Use case Suggested prompt
Last 7 days traffic “For property PROPERTY_ID, run a report for the last 7 days with dimension date and metrics sessions, totalUsers. Prefer run_report_compact.”
Top channels “Same property, last 28 days: dimension sessionDefaultChannelGroup, metrics sessions and conversions. Summarize the top 5.”
Realtime “Call run_realtime_report for PROPERTY_ID with metric activeUsers and tell me what’s live now.”

Pass: summary includes a report_id (compact) or clear row values; numbers look plausible vs GA UI.

Level 3 — Metadata & quality

Use case Suggested prompt
Custom definitions “Use get_custom_dimensions_and_metrics on PROPERTY_ID and list custom metrics I might use in reports.”
Annotations “Call list_property_annotations for PROPERTY_ID for the last 90 days and relate them to traffic dips.”
Ads links “List Google Ads links for PROPERTY_ID with list_google_ads_links.”

Pass: tool returns structured data (or a clear empty list if none configured).

Level 4 — Funnels, conversions, large data

Use case Suggested prompt
Conversions “Use run_conversions_report for PROPERTY_ID last 30 days and rank conversion events.”
Funnel “Build a run_funnel_report for signup: page / → /pricing → /signup (adjust paths to my site).”
Large report without blowing context “Run a high-cardinality report with run_report_compact, then page with get_report_rows (offset 0, limit 50), then discard_report.”

Pass: compact path returns small summary + report_id; paging returns slices; discard succeeds.

Level 5 — Analysis workflows

Use case Suggested prompt
Week-over-week “Compare this week vs last week sessions and users for PROPERTY_ID; call out biggest day-over-day changes.”
Landing page triage “Top landing pages by sessions last 14 days; flag pages with high sessions and low engagement (use engagement metrics available on the property).”
Incident narrative “Combine annotations + daily sessions for the last 60 days into a short incident timeline.”

Pass: answers cite tool results (property id, dates, metrics), not invented UI screenshots.


Step-by-step setup

Step 0 — Prerequisites

Install:

# Python 3.12+ recommended
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install modal
modal setup   # browser login

In Google Cloud (same project you will put in .env):

  1. Enable Google Analytics Admin API
  2. Enable Google Analytics Data API

Done when:

uv --version          # prints a version
modal --version       # prints a version
modal profile current # shows your logged-in profile / workspace

If it fails: re-run modal setup; confirm the GCP project has billing/API enablement as required by Google.


Step 1 — Clone and create .env

git clone https://github.com/YOUR_GITHUB_USER/analytics-selfhosted-mcp.git
cd analytics-selfhosted-mcp
cp .env.example .env

Edit .env:

GOOGLE_PROJECT_ID=your-gcp-project-id
MCP_API_KEY=$(openssl rand -hex 24)
ANALYTICS_MCP_API_KEY=$MCP_API_KEY   # must match MCP_API_KEY

Done when:

set -a && source .env && set +a
test -n "$GOOGLE_PROJECT_ID" && test -n "$MCP_API_KEY" && test "$MCP_API_KEY" = "$ANALYTICS_MCP_API_KEY" && echo "env ok"
git check-ignore -v .env   # must show .gitignore rule

If it fails: keys empty or mismatched — fix .env. If .env is not ignored, stop and fix .gitignore before any commit.


Step 2 — Google credentials (service account recommended)

  1. GCP → IAM → Service Accounts → create SA in GOOGLE_PROJECT_ID
  2. Create a JSON key → save as e.g. ~/secrets/ga-mcp-sa.json (outside this repo)
  3. GA Admin → Account/Property access → add SA email as Viewer on every property you need

Done when (local check):

export GOOGLE_APPLICATION_CREDENTIALS=~/secrets/ga-mcp-sa.json
# Optional: quick Admin API sanity via gcloud / any GA client you prefer.
# On Modal, success is verified in Step 6 (get_account_summaries).
ls -la "$GOOGLE_APPLICATION_CREDENTIALS"   # file exists, contains "client_email"

If it fails later with empty accounts / 403: SA missing Viewer on the GA property, or wrong JSON in the Modal secret.

Temporary alternative: user ADC (gcloud auth application-default login with Analytics readonly scopes). Prefer SA for cloud.


Step 3 — Modal secret

Secret name must be exactly analytics-mcp-creds:

set -a && source .env && set +a

modal secret create analytics-mcp-creds \
  MCP_API_KEY="$MCP_API_KEY" \
  GOOGLE_PROJECT_ID="$GOOGLE_PROJECT_ID" \
  GOOGLE_APPLICATION_CREDENTIALS_JSON="$(cat ~/secrets/ga-mcp-sa.json)" \
  --force

Done when:

modal secret list | grep analytics-mcp-creds

If it fails: path to JSON wrong; or JSON not pasted as file contents (must start with {, not a filesystem path string).


Step 4 — Deploy

modal deploy modal_ga_mcp.py

Note the printed web URL:

https://YOUR_WORKSPACE--analytics-mcp-ga-web.modal.run

(The Modal app id remains analytics-mcp-ga so existing deployments keep a stable hostname.)

Done when:

curl -sS "https://YOUR_WORKSPACE--analytics-mcp-ga-web.modal.run/healthz"
# Expect: {"status":"ok","app":"analytics-mcp-ga"}

If it fails: check Modal dashboard logs for import/boot errors; confirm secret name spelling.

Optional: set in .env:

MODAL_MCP_URL=https://YOUR_WORKSPACE--analytics-mcp-ga-web.modal.run/mcp

Step 5 — Verify MCP protocol (auth + tools)

set -a && source .env && set +a
URL="${MODAL_MCP_URL:-https://YOUR_WORKSPACE--analytics-mcp-ga-web.modal.run/mcp}"
KEY="$MCP_API_KEY"

curl -sS -D /tmp/mcp.hdr -o /tmp/mcp.init -X POST "$URL" \
  -H "Authorization: $KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"setup","version":"0"}}}'

# Expect HTTP 200 and a mcp-session-id header
grep -i mcp-session-id /tmp/mcp.hdr
cat /tmp/mcp.init

SID=$(awk 'BEGIN{IGNORECASE=1} /^mcp-session-id:/{print $2}' /tmp/mcp.hdr | tr -d '\r')

curl -sS -X POST "$URL" \
  -H "Authorization: $KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
  | tee /tmp/mcp.tools

Done when: /tmp/mcp.tools lists 15 tools, including:

  • Upstream (9): get_account_summaries, list_google_ads_links, get_property_details, list_property_annotations, get_custom_dimensions_and_metrics, run_report, run_realtime_report, run_funnel_report, run_conversions_report
  • Host (6): ping, run_report_compact, get_report_rows, get_report_summary, get_report_full, discard_report

If it fails:

Symptom Likely cause Fix
HTTP 401 Wrong key Match .env ↔ Modal secret; recreate secret with --force
HTTP 307 loop Old client hitting wrong slash handling Use /mcp (no slash) or /mcp/ (both should work on current deploy)
HTTP 406 Accept header Send Accept: application/json on POST
Empty / error body on initialize App crash on boot Modal logs for the web function
Fewer than 15 tools Stale deploy Re-run modal deploy modal_ga_mcp.py

Step 6 — Verify Google Analytics access

set -a && source .env && set +a
export MODAL_MCP_URL="${MODAL_MCP_URL:-https://YOUR_WORKSPACE--analytics-mcp-ga-web.modal.run/mcp}"

modal run modal_ga_mcp.py::test_account_summaries
# Then pick a property id from the output:
modal run modal_ga_mcp.py::test_run_report --property-id=YOUR_GA4_PROPERTY_ID
modal run modal_ga_mcp.py::test_run_report_compact --property-id=YOUR_GA4_PROPERTY_ID

Done when: account summaries show your properties; report/compact tests print row data / a report_id.

If it fails:

Symptom Likely cause Fix
Empty [] accounts SA not granted on GA Add Viewer on account/property
403 / permission errors Wrong project or APIs off Enable Admin + Data APIs; fix GOOGLE_PROJECT_ID
Credential JSON errors Bad secret payload Secret value must be raw JSON object

Step 7 — Connect Cursor

cp .cursor/mcp.json.example .cursor/mcp.json
# Edit url → your Modal /mcp URL
{
  "mcpServers": {
    "analytics-selfhosted-mcp": {
      "url": "https://YOUR_WORKSPACE--analytics-mcp-ga-web.modal.run/mcp",
      "headers": {
        "x-api-key": "${env:ANALYTICS_MCP_API_KEY}"
      }
    }
  }
}

Cursor does not load project .env for remote MCP headers. Export before launching Cursor:

set -a && source .env && set +a
export ANALYTICS_MCP_API_KEY
# launch Cursor from this shell, or put ANALYTICS_MCP_API_KEY in your shell profile / direnv

Reload MCP servers in Cursor.

Done when: MCP shows tools; in chat: “Call ping” → pong.

If it fails:

Symptom Fix
Unauthorized / tools missing Env var not visible to Cursor — launch from a shell where echo $ANALYTICS_MCP_API_KEY is non-empty
Wrong URL Must end with /mcp; copy from Modal deploy output
Stale config Restart Cursor or reload MCP after editing mcp.json

Some hosts prefer Authorization instead of x-api-key (raw key or Bearer …). This server accepts both.


How data moves (read once)

Client  --tools/call-->  Modal MCP  --GA API-->  Google
                ^                         |
                +---- tool result JSON ---+

Tool results still enter the model context. Large run_report payloads can blow the context window.

Prefer for big reports:

  1. run_report_compact → summary + report_id (full payload stored server-side)
  2. get_report_rows → page rows
  3. discard_report when finished
  4. get_report_full(..., confirm=true) only if you intentionally want everything in-context

Troubleshooting (cheat sheet)

Problem Check
401 on /mcp Key mismatch .env vs Modal secret
Healthz OK, MCP fails Auth headers; path /mcp
Tools list OK, GA empty SA Viewer on property; APIs enabled
Cursor can’t auth ANALYTICS_MCP_API_KEY not in Cursor’s environment
ClickUp “no tools” Known client gap — docs/CLICKUP_ISSUE.md
High Modal bill min_containers=1 keeps a warm replica; change only if you accept cold starts

Still stuck: Modal function logs for analytics-mcp-ga / web, plus the curl transcript from Step 5.


Security

Never commit: .env, ADC/SA JSON, client_secret_*.json, .cursor/mcp.json.

Checklist: docs/PUBLISHING.md.

openssl rand -hex 24   # rotate MCP_API_KEY → update .env + Modal secret + Cursor

Repo layout

Path Role
modal_ga_mcp.py Production MCP (parity + compaction)
report_compact.py Summary / paging helpers
modal_mcp_ping.py Optional ping-only deploy
.env.example Env template
.cursor/mcp.json.example Cursor template
docs/CLICKUP_ISSUE.md ClickUp tools/list issue
docs/PUBLISHING.md Public-repo security checklist

Optional ping-only: modal deploy modal_mcp_ping.py (same analytics-mcp-creds secret).


License

MIT. GA tool callables come from Google’s analytics-mcp; this repo hosts them remotely and adds compaction. Not affiliated with Google.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured