marketing-mcp
v1.1.20
Published
Read-only agency Marketing MCP — Google Ads, GA4, GSC, Meta, Ahrefs
Maintainers
Readme
marketing-mcp
Read-only Marketing MCP server for agency workflows — Google Ads, GA4, Search Console, Meta Ads, and Ahrefs.
Current state
As of [email protected], the MCP is now:
- discovery-first for Google clients
- Gmail-backed for Search Console and GA4 when a Google user account is connected
- service-account fallback for Google reads when user OAuth is unavailable
- optimized with multi-layer caching for faster startup and first client-directory access
In practice, this means:
- users no longer need to manually create Firestore client rows before switching to discovered clients
agency_set_active_clientcan resolve discovered clients directly- Google Search Console and GA4 can prefer the connected Google account (for example
[email protected]) over the service account - local
npxMCP launches can reuse:- cached secrets in
~/.marketing-mcp/ - cached Google user OAuth from secrets or local file
- local disk discovery snapshots
- shared Firestore discovery snapshots
- cached secrets in
What changed
1. Discovery-first client model
The old registry-first model has been replaced with a discovery-first directory:
- Google Ads accounts
- GA4 properties
- GSC sites
are treated as first-class clients even if they have never been saved in Firestore.
Firestore is now an optional alias / override layer for:
- preferred display names
- canonical keys
- notes / tags
- Ahrefs domain overrides
2. Google auth model
Google platform access now works in this order:
- connected Google user account (preferred)
- service account fallback
This is especially important for Search Console, where many teams only have access through a shared Gmail / Workspace user and cannot add the service account to every property.
3. Shared token export/import
The admin UI can connect a Google account on the hosted app, and the resulting refresh token can be exported into the secrets .env so local npx MCP users inherit the same Google visibility through MOTHER_SECRET only.
4. Performance work
The MCP now includes several performance optimizations:
- cached secrets reuse instead of refetching on every stdio launch
- parallel tool imports
- parallel Google discovery queries where safe
- per-process Google discovery caches
- persistent local disk client-directory snapshots
- shared Firestore discovery snapshot cache keyed by auth identity
- startup prewarm of the client directory
- compact MCP JSON responses to reduce stdio payload size
5. Better matching
Brand-name matching now prefers the most canonical discovered entry, usually:
- production GSC / website entry
- main production site
over secondary variants such as:
- filtered / unfiltered GA4 properties
- forum / prismic / staging variants
So phrases like Bolsover Cruise Club now resolve to the main website/GSC client instead of throwing ambiguity in common cases.
Install (Cursor / Claude Desktop)
Only one env var is required. The server fetches .env, service-account.json, and fb-service-account.json from the secrets server at startup.
{
"mcpServers": {
"marketing-mcp": {
"command": "npx",
"args": ["-y", "marketing-mcp@latest"],
"env": {
"MOTHER_SECRET": "your-secret-here"
}
}
}
}Secrets are cached under ~/.marketing-mcp/ on first run.
Recommended behavior
The MCP can still be launched with:
{
"mcpServers": {
"marketing-mcp": {
"command": "npx",
"args": ["-y", "marketing-mcp@latest"],
"env": {
"MOTHER_SECRET": "your-secret-here"
}
}
}
}Using @latest adds some unavoidable npm startup overhead, but the MCP now hides most of the heavy Google discovery cost behind local + shared caches.
Remote MCP (Claude Cloud / Teams)
Claude Cloud uses OAuth — not API keys. Configure in secrets .env:
MCP_OAUTH_CLIENT_ID=marketing-mcp-claude
MCP_OAUTH_CLIENT_SECRET=generate-a-long-random-stringLegacy MCP_API_KEY / x-api-key still works for curl and scripts.
Claude custom connector form
| Field | Value |
|-------|--------|
| Name | Marketing MCP |
| Remote MCP server URL | https://marketing-mcp.vg-stuff.com/mcp |
| OAuth Client ID | same as MCP_OAUTH_CLIENT_ID in secrets |
| OAuth Client Secret | same as MCP_OAUTH_CLIENT_SECRET in secrets |
Claude will OAuth → consent screen → connect. Callback: https://claude.ai/api/mcp/auth_callback.
Cursor / npx (stdio) still works with MOTHER_SECRET only — unchanged.
Verify OAuth metadata (after deploy)
curl https://marketing-mcp.vg-stuff.com/.well-known/oauth-authorization-server
curl https://marketing-mcp.vg-stuff.com/.well-known/oauth-protected-resource/mcp
curl -I -X POST https://marketing-mcp.vg-stuff.com/mcp # should 401 with WWW-AuthenticateFirst steps in chat
agency_list_clients— see discovered clients + any saved alias/override dataagency_set_active_client— e.g."Bolsover Cruise Club","https://www.bolsovercruiseclub.com/", a GA4 property ID, or a client key- Platform read tools default to the active client
Local development
Create .env.local with MOTHER_SECRET=... and run pnpm install (postinstall pulls secrets) or pnpm start.
Google user OAuth via secrets
If you want local stdio MCP users to inherit the same Google user access as the hosted admin, add these values to the secrets .env fetched by MOTHER_SECRET:
GOOGLE_USER_OAUTH_REFRESH_TOKEN=
GOOGLE_USER_OAUTH_EMAIL=
GOOGLE_USER_OAUTH_SCOPES=
GOOGLE_USER_OAUTH_UPDATED_AT=Then the local npx MCP can use the connected Google account without any extra local config beyond MOTHER_SECRET.
GA4 bulk access grant (Andrew / team)
Grant the MCP service account Viewer on every GA4 property in one go:
Admin UI (recommended):
pnpm adminOpen http://localhost:3001 → GA4 service account access → sign in with Google → done.
CLI:
pnpm ga4:grant-access # live
pnpm ga4:grant-access:dry # preview onlyOne-time Google Cloud setup:
- Enable Google Analytics Admin API
- Add this OAuth redirect URI to your existing OAuth client (same one used for Google Ads):
http://localhost:3001/oauth2callback(local)https://marketing-mcp.vg-stuff.com/oauth2callback(production)
Andrew must sign in with a Google account that has Manage Users on the client GA4 accounts. Grants are at account level — one click covers all properties in each account.
Deploy (Docker — admin + MCP)
One container serves both the admin UI and the remote MCP on the same host:
| Host | Purpose |
|------|---------|
| marketing-mcp.vg-stuff.com | Admin dashboard and remote MCP at /mcp |
cp .env.docker.example .env
# Set MOTHER_SECRET, ADMIN_PASSWORD, MCP_API_KEY
pnpm docker:build
pnpm docker:upThe container listens on 3001 and exposes:
/— client registry admin UI/mcp— Streamable HTTP MCP (requiresx-api-key)/api/ga4-grant/*— bulk GA4 service account grant/api/health— health check/api/config— public URLs (fromsrc/config/app-url.ts)
Required env vars:
| Variable | Purpose |
|----------|---------|
| MOTHER_SECRET | Fetches .env, service accounts from secrets server |
| ADMIN_PASSWORD | Login password for the dashboard |
| MCP_API_KEY | Auth key for remote MCP clients |
| NODE_ENV=production | Admin + MCP → marketing-mcp.vg-stuff.com |
Reverse proxy (Caddy):
marketing-mcp.vg-stuff.com {
reverse_proxy localhost:3001
}Google OAuth — production redirect URI:
https://marketing-mcp.vg-stuff.com/oauth2callback
All URLs derive from src/config/app-url.ts (getAppBaseUrl(), getMcpEndpointUrl()).
GA4 & Search Console access
GA4 — bulk grant (recommended)
Google does not offer org-wide service account access. The workaround:
- Use the admin “Grant Viewer on all my GA4 accounts” button (or
pnpm ga4:grant-access). - Sign in with a human Google account that has Manage Users on client GA4 accounts.
- The tool adds the MCP service account as Viewer at the account level — that covers every property in each account, including new ones.
You still need one grant per GA4 account (usually one per client). You cannot skip this entirely — Google requires an explicit invite.
Search Console — preferred user OAuth model
Search Console now works best through the connected Google user account.
If the team only has access through a Gmail / Workspace user and cannot add the service account to many client properties, connect that Google account in the admin UI and export its refresh token into secrets. The MCP will then prefer that Google account automatically for:
gsc_list_sitesgsc_search_analytics- GSC discovery used by
agency_list_clients
Search Console — no bulk user-management API
GSC has no API to add users to properties. There is no org-level service account access. Each site must add the service account manually:
- GSC → Settings → Users and permissions → Add user → service account email → Full access.
- Direct link:
https://search.google.com/search-console/users?resource_id=<site_url>
There is no Google-supported way to automate this across all client sites without the property owner adding the email.
Caching model
Client discovery now uses multiple cache layers:
- in-memory cache inside the running MCP process
- local disk snapshot at
~/.marketing-mcp/.client-directory-cache.json - shared Firestore snapshot cache keyed by Google auth identity
- live Google discovery as the final fallback / refresh source
Behavior:
- return cached snapshot fast
- refresh stale snapshots in the background
- update local disk + shared Firestore after live discovery completes
This makes repeated usage and even fresh npx launches much faster after the first heavy Google discovery run.
Notes
- Google Ads: use a client account customer ID in the registry, not your MCC/manager ID, for campaign and performance reads.
- GA4 / GSC: prefer connected Google user auth when available; service account remains fallback.
- Current package version at the time of this update:
1.1.13.
