GA4 MCP Server
README.md
# GA4 MCP Server
Production-ready, multi-tenant **Google Analytics 4** MCP server for Claude.
This is the first connector in a larger platform. Shared infrastructure (auth, clients, Google OAuth token storage, authorization, logging, errors) is reusable. GA4-specific code stays under `src/connectors/ga4` and `src/mcp/ga4`. Do not treat this as a monolithic Google Marketing MCP.
## Architecture
```text
Claude
| MCP OAuth 2.1 + Streamable HTTP
v
Next.js / Vercel (/mcp)
|-- MCP tools (ga4_*)
|-- Authorization (client_members only)
|-- GA4 service
|-- Google OAuth token manager
v
Supabase
|-- clients
|-- client_members (application identity)
|-- google_connections (Google credential, not app identity)
|-- ga4_properties
```
Application users are Supabase Auth users. Tenant access is **only** `client_members(user_id, client_id, role)`. A Google-connected account is a client-owned credential and is never the application user.
## Local setup
```bash
npm install
cp .env.example .env.local
# fill in the values documented below
npx supabase start # or link a remote project
npx supabase db reset --local
npm run dev
```
Open [http://localhost:3000](http://localhost:3000), create an application account, create a client, then connect Google Analytics.
## Google Cloud setup
1. Create or select a Google Cloud project.
2. Enable **Google Analytics Data API** and **Google Analytics Admin API**.
3. Configure the OAuth consent screen (External or Internal).
4. Create **OAuth 2.0 Client ID** credentials (Web application).
5. Authorized redirect URI:
- Local: `http://localhost:3000/api/auth/google/callback`
- Production: `https://<your-domain>/api/auth/google/callback`
6. Request only GA4 scopes:
- `https://www.googleapis.com/auth/analytics.readonly`
- `openid`
- `email`
Do not add DV360 or Google Ads scopes to this connector.
## GA4 setup
1. Sign in to this app with email/password (application identity).
2. Create a client (you become `owner` in `client_members`).
3. Optionally add other application users as `owner` or `member`.
4. Click **Connect Google Analytics**. This attaches a Google credential to the **client**.
5. Link one or more GA4 properties discovered from that Google account.
A member who did not click Connect can still query the client if they have a `client_members` row.
## Supabase setup
This repo includes SQL migrations in `supabase/migrations`. Create them with the CLI; do not invent filenames.
Local:
```bash
npx supabase start
npx supabase db reset --local
```
Remote:
```bash
npx supabase link --project-ref <project-ref>
npx supabase db push
```
The service role key is server-only. Never expose `SUPABASE_SERVICE_ROLE_KEY` or Google refresh tokens to the browser.
## Environment variables
See [`.env.example`](.env.example). Required:
| Variable | Purpose |
| --- | --- |
| `NEXT_PUBLIC_SUPABASE_URL` | Supabase project URL |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Browser/server user client |
| `SUPABASE_SERVICE_ROLE_KEY` | Server-side admin (never `NEXT_PUBLIC_`) |
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | Google OAuth app |
| `GOOGLE_REDIRECT_URI` | Must match Google Cloud |
| `GOOGLE_OAUTH_SCOPES` | GA4-only scopes |
| `MCP_BASE_URL` | Public origin, e.g. `https://your-app.vercel.app` |
| `MCP_TOKEN_SIGNING_SECRET` | Signs MCP access tokens |
| `TOKEN_ENCRYPTION_KEY` | 64 hex chars (32 bytes) for Google token encryption |
Do not put a GA4 property ID in `.env`. Properties belong to clients in Supabase.
Generate a token encryption key:
```bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
## MCP setup (Claude)
Remote endpoint:
```text
https://<your-domain>/mcp
```
This server implements MCP OAuth 2.1:
- Protected resource metadata: `/.well-known/oauth-protected-resource`
- Authorization server metadata: `/.well-known/oauth-authorization-server`
- Authorize: `/oauth/authorize` (application login + consent)
- Token: `/oauth/token` (authorization_code + refresh_token, PKCE S256)
- Register: `/oauth/register` (DCR fallback)
- CIMD is advertised (`client_id_metadata_document_supported`)
In Claude: **Settings → Connectors → Add custom connector** → paste `https://<your-domain>/mcp`. Claude will run OAuth. After you approve, tools are available.
Local inspector:
```bash
npx @modelcontextprotocol/inspector
```
Connect with Streamable HTTP to `http://localhost:3000/mcp`. You still need a valid MCP Bearer token from the OAuth flow.
## Tools
| Tool | Purpose |
| --- | --- |
| `ga4_run_report` | Primary report (dates, metrics, dimensions, limit, order, basic filter) |
| `ga4_realtime` | Realtime active users / traffic breakdown |
| `ga4_metadata` | Discover metrics and dimensions for a property |
| `ga4_list_properties` | Properties visible to the client's Google connection |
| `ga4_compare_periods` | Current vs previous period with abs/% change |
Every tool requires `client_id`. Property-scoped tools also require a linked `property_id`. Authorization always checks `client_members` before Google is called.
### Example: `ga4_run_report`
```json
{
"client_id": "11111111-1111-1111-1111-111111111111",
"property_id": "123456789",
"date_start": "2026-08-01",
"date_end": "2026-08-15",
"dimensions": ["country"],
"metrics": ["activeUsers", "sessions", "totalRevenue"],
"limit": 10
}
```
### Example: `ga4_compare_periods`
```json
{
"client_id": "11111111-1111-1111-1111-111111111111",
"property_id": "123456789",
"current_start": "2026-08-01",
"current_end": "2026-08-15",
"previous_start": "2026-07-17",
"previous_end": "2026-07-31",
"metrics": ["activeUsers", "sessions", "totalRevenue"]
}
```
Claude can then answer questions such as:
- How many users did we have yesterday?
- Top 10 countries this month
- Compare this month with last month
- How many active users are on the site right now?
## Testing
```bash
npm test
```
Tests use mocked Google and Supabase. They never use production OAuth tokens.
## Vercel
Deploy the Next.js app and set the same environment variables. `MCP_BASE_URL` and `GOOGLE_REDIRECT_URI` must use the production domain. The `/mcp` route runs on the Node.js runtime with a 60s max duration.
## Project layout
```text
src/
auth/ application authentication
authorization/ authorizeClient via client_members
oauth/ Google OAuth + token manager
mcp-oauth/ MCP OAuth 2.1 authorization server
connectors/ga4/ Google Analytics APIs
mcp/ga4/ MCP tools
repositories/ Supabase data access
app/mcp/ Streamable HTTP transport
```
Future connectors (DV360, Google Ads) should add `src/connectors/<name>` and `src/mcp/<name>` and reuse the shared core. Do not create a generic Google API dumping ground.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues