ga4-mcp
Provides read-only access to Google Analytics 4, including account and property summaries, property details, realtime reports, funnel and conversions reports, custom dimensions and metrics, annotations, and Google Ads links.
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., "@ga4-mcp show me sessions by country for the last 7 days"
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.
ga4-mcp — Google Analytics 4 MCP server
A read-only Model Context Protocol server that gives Claude, Cursor, Claude Code and any other MCP client access to Google Analytics 4: core, realtime, funnel and conversions reports, plus account and property information.
It is a TypeScript port of Google's own analytics-mcp. It has the same nine tools, the same argument names and the same filter hints, so prompts written for one work on the other. What differs:
Service-account auth, no gcloud. Point it at a key file, or pass the key inline. Nothing to log in to on the machine that runs the agent.
Streamable HTTP as well as stdio. It can run centrally. This is how Walma serves it to whole organisations through its AI gateway.
Compact results. Report rows come back as one object per row (
{ "country": "Sweden", "sessions": 120 }) instead of parallel value arrays, so the model spends fewer tokens on the same data.Three runtime dependencies. It calls the REST API directly instead of using the gRPC client libraries and Google's agent framework.
Built and used by Walma AI.
Tools
Tool | What it does |
| Accounts and properties the service account can read, with property IDs |
| Time zone, currency, industry and service level of a property |
| Google Ads accounts linked to a property |
| Notes left on dates in GA4 (releases, campaigns, outages) |
| The property's custom definitions, with the |
| Data API report: any dimensions and metrics, filters, ordering, several date ranges, pagination |
| The last ~30 minutes |
| Funnel progression and abandonment, with breakdowns and next actions |
| Ad cost, ROAS and conversions with a chosen attribution model |
Everything is read-only (analytics.readonly scope). The server cannot change anything in Google Analytics.
Related MCP server: google_analytics
Setup
1. Create a service account and key
In Google Cloud Console, pick or create a project.
Enable the Google Analytics Data API and the Google Analytics Admin API.
IAM & Admin → Service accounts → Create. No project roles are needed.
Keys → Add key → JSON. Save the file somewhere outside any repository, for example
~/.config/gcloud/ga4-service-account.json.
2. Give the service account access in Google Analytics
In GA4, open Admin → Property access management (or Account access management to cover every property in the account). Add the service account's email address (…@….iam.gserviceaccount.com) with the Viewer role.
The server sees exactly the properties it was invited to.
3. Add it to your MCP client
Claude Code:
claude mcp add ga4 --scope user \
-e GA4_SERVICE_ACCOUNT_FILE=~/.config/gcloud/ga4-service-account.json \
-- npx -y github:Walma-Labs/ga4-mcpClaude Desktop, Cursor and others (mcpServers in the client's config):
{
"mcpServers": {
"ga4": {
"command": "npx",
"args": ["-y", "github:Walma-Labs/ga4-mcp"],
"env": { "GA4_SERVICE_ACCOUNT_FILE": "/absolute/path/to/ga4-service-account.json" }
}
}
}Environment variables
Variable | Meaning |
| Path to the service-account JSON key ( |
| The key as inline JSON. Wins over the file when both are set |
| Port for the HTTP entrypoint (default |
Running over HTTP
npm install && npm run build
GA4_SERVICE_ACCOUNT_FILE=… npm run start:httpThis starts a stateless streamable-HTTP endpoint on POST /, plus GET /healthz. It has no authentication of its own. Put it behind something that authenticates callers. In Walma's deployment, the gateway relay does that.
Using it as a library
import { createGa4Server, createGa4Client } from "@walma-labs/ga4-mcp";
const client = createGa4Client(); // or createGa4Client(myRequestFn)
const server = createGa4Server(client); // an McpServer; connect any transportcreateGa4Client takes an optional request function, so you can supply your own authorisation or a test double.
Development
npm install
npm test # unit + MCP-wire tests against a mocked client, no network
npm run buildLicense
Apache-2.0. Tool names, argument contracts and hint text derive from Google's analytics-mcp; see NOTICE.
Available Tools
9 toolsget_account_summariesA
List the Google Analytics accounts and properties the service account can read, with property IDs.
Inputs: no named parameters.
get_custom_dimensions_and_metricsA
Return the property's custom dimensions and metrics, with the apiName to use in reports.
Inputs: property_id (required).
get_property_detailsB
Return details about a property: time zone, currency, industry, service level, create time.
Inputs: property_id (required).
list_google_ads_linksC
List the Google Ads accounts linked to a property.
Inputs: property_id (required).
list_property_annotationsA
List a property's annotations: notes left in GA4 on specific dates or periods, typically for releases, campaign launches or changes, and traffic jumps or drops with an external cause.
Inputs: property_id (required).
run_conversions_reportA
Runs a Google Analytics Data API conversions report.
USE THIS TOOL INSTEAD OF run_report WHEN:
You need to report on conversions, ad performance, return on ad spend (ROAS) or attribution.
You need conversion metrics such as advertiserAdCost, returnOnAdSpendByInteractionDate or allConversionsByConversionDate.
You need a specific attribution model (DATA_DRIVEN or LAST_CLICK).
The question is explicitly about conversions, ad clicks, ad costs, or campaigns related to conversions.
See https://developers.google.com/analytics/devguides/reporting/data/v1/conversions-api-basics.
dimensions
Only these standard dimensions: campaignName, continent, country, defaultChannelGroup, deviceCategory, medium, platform, primaryChannelGroup, source, sourceMedium, sourcePlatform, subcontinent.
metrics
Only these standard metrics: advertiserAdClicks, advertiserAdCost, advertiserAdCostPerAllConversionsByConversionDate, advertiserAdCostPerAllConversionsByInteractionDate, advertiserAdCostPerClick, advertiserAdImpressions, allConversionsByConversionDate, allConversionsByInteractionDate, returnOnAdSpendByConversionDate, returnOnAdSpendByInteractionDate, totalRevenueByConversionDate, totalRevenueByInteractionDate.
conversion_spec
Required. Pass an empty conversion_actions list for all conversion events: {"conversion_actions": ["conversionActions/12345"], "attribution_model": "DATA_DRIVEN"} (or [] / "LAST_CLICK")
date_ranges
Example date_ranges arguments:
A single date range: [ {"start_date":"2025-01-01","end_date":"2025-01-31","name":"Jan2025"} ]
A relative date range using 'yesterday' and 'today': [ {"start_date":"yesterday","end_date":"today","name":"YesterdayAndToday"} ]
A relative date range using 'NdaysAgo': [ {"start_date":"30daysAgo","end_date":"yesterday","name":"Previous30Days"} ]
Multiple date ranges (adds a 'dateRange' column to every row): [ {"start_date":"2025-01-01","end_date":"2025-01-31","name":"Jan2025"}, {"start_date":"2025-02-01","end_date":"2025-02-28","name":"Feb2025"} ]
dimension_filter
Example dimension_filter arguments:
A simple filter: {"filter":{"field_name":"eventName","string_filter":{"match_type":"BEGINS_WITH","value":"add"}}}
A NOT filter: {"not_expression":{"filter":{"field_name":"eventName","string_filter":{"match_type":"BEGINS_WITH","value":"add"}}}}
An empty value filter: {"filter":{"field_name":"source","empty_filter":{}}}
An AND group filter: {"and_group":{"expressions":[{"filter":{"field_name":"sourceMedium","string_filter":{"match_type":"EXACT","value":"google / cpc"}}},{"filter":{"field_name":"eventName","in_list_filter":{"case_sensitive":true,"values":["first_visit","purchase","add_to_cart"]}}}]}}
An OR group filter: {"or_group":{"expressions":[{"filter":{"field_name":"sourceMedium","string_filter":{"match_type":"EXACT","value":"google / cpc"}}},{"filter":{"field_name":"eventName","in_list_filter":{"case_sensitive":true,"values":["first_visit","purchase","add_to_cart"]}}}]}}
Notes: The API applies dimension_filter and metric_filter independently, so some combinations are impossible in one request, e.g. ((eventName = "page_view" AND eventCount > 100) OR (eventName = "join_group" AND eventCount < 50)). For conditions shaped ((D1 AND M1) OR (D2 AND M2)) either: a) run one report filtering on the supported subset (eventName in ["page_view","join_group"]) with eventCount included, and apply the metric conditions to the rows yourself; or b) run one report per (dimension condition, metric condition) pair. Prefer (a) — fewer reports — unless it would use excessive quota (https://developers.google.com/analytics/blog/2023/data-api-quota-management).
metric_filter
Example metric_filter arguments:
A simple filter: {"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}}
A NOT filter: {"not_expression":{"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}}}
An empty value filter: {"filter":{"field_name":"purchaseRevenue","empty_filter":{}}}
An AND group filter: {"and_group":{"expressions":[{"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}},{"filter":{"field_name":"purchaseRevenue","between_filter":{"from_value":{"double_value":10},"to_value":{"double_value":25}}}}]}}
An OR group filter: {"or_group":{"expressions":[{"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}},{"filter":{"field_name":"purchaseRevenue","between_filter":{"from_value":{"double_value":10},"to_value":{"double_value":25}}}}]}}
Notes: The API applies dimension_filter and metric_filter independently, so some combinations are impossible in one request, e.g. ((eventName = "page_view" AND eventCount > 100) OR (eventName = "join_group" AND eventCount < 50)). For conditions shaped ((D1 AND M1) OR (D2 AND M2)) either: a) run one report filtering on the supported subset (eventName in ["page_view","join_group"]) with eventCount included, and apply the metric conditions to the rows yourself; or b) run one report per (dimension condition, metric condition) pair. Prefer (a) — fewer reports — unless it would use excessive quota (https://developers.google.com/analytics/blog/2023/data-api-quota-management).
order_bys
Example order_bys arguments:
Ascending 'eventName': [ {"dimension":{"dimension_name":"eventName","order_type":"ALPHANUMERIC"},"desc":false} ]
Descending 'campaignName', ignoring case: [ {"dimension":{"dimension_name":"campaignName","order_type":"CASE_INSENSITIVE_ALPHANUMERIC"},"desc":true} ]
Ascending 'audienceId' (numeric): [ {"dimension":{"dimension_name":"audienceId","order_type":"NUMERIC"},"desc":false} ]
Descending 'eventCount': [ {"metric":{"metric_name":"eventCount"},"desc":true} ]
Ascending 'eventCount': [ {"metric":{"metric_name":"eventCount"},"desc":false} ]
Dimension and metric combined: [ {"dimension":{"dimension_name":"eventName","order_type":"ALPHANUMERIC"},"desc":false}, {"metric":{"metric_name":"eventCount"},"desc":true} ]
Several dimensions and metrics: [ {"dimension":{"dimension_name":"eventName","order_type":"ALPHANUMERIC"},"desc":false}, {"dimension":{"dimension_name":"audienceId","order_type":"NUMERIC"},"desc":false}, {"metric":{"metric_name":"eventCount"},"desc":true} ] The dimensions and metrics in order_bys must also be in the request's dimensions and metrics.
Inputs: metrics (required), dimensions (required), date_ranges (required), property_id (required), +8 more.
run_funnel_reportA
Runs a Google Analytics Data API funnel report (https://developers.google.com/analytics/devguides/reporting/data/v1/funnels).
Returns funnel_table (progression through the steps) and funnel_visualization, both flattened to one object per row.
funnel_steps
Each step is {name, filter_expression} or the shorthand {name, event}. Example funnel_steps configurations:
Shorthand event step: {"name":"Session start","event":"session_start"}
Several events with OR: {"name":"First open/visit","filter_expression":{"or_group":{"expressions":[{"funnel_event_filter":{"event_name":"first_open"}},{"funnel_event_filter":{"event_name":"first_visit"}}]}}}
Field filter for organic traffic: {"name":"Organic visitors","filter_expression":{"funnel_field_filter":{"field_name":"firstUserMedium","string_filter":{"match_type":"CONTAINS","case_sensitive":false,"value":"organic"}}}}
Event with a parameter filter (value > 50): {"name":"Add to cart (value > 50)","filter_expression":{"funnel_event_filter":{"event_name":"add_to_cart","funnel_parameter_filter_expression":{"funnel_parameter_filter":{"event_parameter_name":"value","numeric_filter":{"operation":"GREATER_THAN","value":{"double_value":50}}}}}}}
AND condition (page view on a path): {"name":"Home page view","filter_expression":{"and_group":{"expressions":[{"funnel_event_filter":{"event_name":"page_view"}},{"funnel_field_filter":{"field_name":"pageLocation","string_filter":{"match_type":"CONTAINS","value":"/"}}}]}}}
A typical e-commerce funnel: [ {"name":"First open/visit","filter_expression":{"or_group":{"expressions":[{"funnel_event_filter":{"event_name":"first_open"}},{"funnel_event_filter":{"event_name":"first_visit"}}]}}}, {"name":"Organic visitors","filter_expression":{"funnel_field_filter":{"field_name":"firstUserMedium","string_filter":{"match_type":"CONTAINS","case_sensitive":false,"value":"organic"}}}}, {"name":"Session start","filter_expression":{"funnel_event_filter":{"event_name":"session_start"}}}, {"name":"Screen/Page view","filter_expression":{"or_group":{"expressions":[{"funnel_event_filter":{"event_name":"screen_view"}},{"funnel_event_filter":{"event_name":"page_view"}}]}}}, {"name":"Purchase","filter_expression":{"or_group":{"expressions":[{"funnel_event_filter":{"event_name":"purchase"}},{"funnel_event_filter":{"event_name":"in_app_purchase"}}]}}} ]
funnel_breakdown
Segment the funnel by a dimension: {"breakdown_dimension": "deviceCategory"}. Common: deviceCategory, country, operatingSystem, browser.
funnel_next_action
What users do after completing or dropping off: {"next_action_dimension": "eventName", "limit": 5}. Common: eventName, pagePath.
segments
Data API Segment objects (https://developers.google.com/analytics/devguides/reporting/data/v1/funnels#segments).
date_ranges
Example date_ranges arguments:
A single date range: [ {"start_date":"2025-01-01","end_date":"2025-01-31","name":"Jan2025"} ]
A relative date range using 'yesterday' and 'today': [ {"start_date":"yesterday","end_date":"today","name":"YesterdayAndToday"} ]
A relative date range using 'NdaysAgo': [ {"start_date":"30daysAgo","end_date":"yesterday","name":"Previous30Days"} ]
Multiple date ranges (adds a 'dateRange' column to every row): [ {"start_date":"2025-01-01","end_date":"2025-01-31","name":"Jan2025"}, {"start_date":"2025-02-01","end_date":"2025-02-28","name":"Feb2025"} ]
Inputs: property_id (required), funnel_steps (required), segments, date_ranges, +3 more.
run_realtime_reportA
Runs a Google Analytics Data API realtime report covering roughly the last 30 minutes (https://developers.google.com/analytics/devguides/reporting/data/v1/realtime-basics).
dimensions
Realtime dimensions (https://developers.google.com/analytics/devguides/reporting/data/v1/realtime-api-schema#dimensions), or user-scoped custom dimensions: from get_custom_dimensions_and_metrics, those whose apiName begins with "customUser:".
metrics
Realtime metrics only (https://developers.google.com/analytics/devguides/reporting/data/v1/realtime-api-schema#metrics). Realtime reports can't use custom metrics.
dimension_filter
Example dimension_filter arguments:
A simple filter: {"filter":{"field_name":"eventName","string_filter":{"match_type":"BEGINS_WITH","value":"add"}}}
A NOT filter: {"not_expression":{"filter":{"field_name":"eventName","string_filter":{"match_type":"BEGINS_WITH","value":"add"}}}}
An empty value filter: {"filter":{"field_name":"source","empty_filter":{}}}
An AND group filter: {"and_group":{"expressions":[{"filter":{"field_name":"sourceMedium","string_filter":{"match_type":"EXACT","value":"google / cpc"}}},{"filter":{"field_name":"eventName","in_list_filter":{"case_sensitive":true,"values":["first_visit","purchase","add_to_cart"]}}}]}}
An OR group filter: {"or_group":{"expressions":[{"filter":{"field_name":"sourceMedium","string_filter":{"match_type":"EXACT","value":"google / cpc"}}},{"filter":{"field_name":"eventName","in_list_filter":{"case_sensitive":true,"values":["first_visit","purchase","add_to_cart"]}}}]}}
Notes: The API applies dimension_filter and metric_filter independently, so some combinations are impossible in one request, e.g. ((eventName = "page_view" AND eventCount > 100) OR (eventName = "join_group" AND eventCount < 50)). For conditions shaped ((D1 AND M1) OR (D2 AND M2)) either: a) run one report filtering on the supported subset (eventName in ["page_view","join_group"]) with eventCount included, and apply the metric conditions to the rows yourself; or b) run one report per (dimension condition, metric condition) pair. Prefer (a) — fewer reports — unless it would use excessive quota (https://developers.google.com/analytics/blog/2023/data-api-quota-management).
metric_filter
Example metric_filter arguments:
A simple filter: {"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}}
A NOT filter: {"not_expression":{"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}}}
An empty value filter: {"filter":{"field_name":"purchaseRevenue","empty_filter":{}}}
An AND group filter: {"and_group":{"expressions":[{"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}},{"filter":{"field_name":"purchaseRevenue","between_filter":{"from_value":{"double_value":10},"to_value":{"double_value":25}}}}]}}
An OR group filter: {"or_group":{"expressions":[{"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}},{"filter":{"field_name":"purchaseRevenue","between_filter":{"from_value":{"double_value":10},"to_value":{"double_value":25}}}}]}}
Notes: The API applies dimension_filter and metric_filter independently, so some combinations are impossible in one request, e.g. ((eventName = "page_view" AND eventCount > 100) OR (eventName = "join_group" AND eventCount < 50)). For conditions shaped ((D1 AND M1) OR (D2 AND M2)) either: a) run one report filtering on the supported subset (eventName in ["page_view","join_group"]) with eventCount included, and apply the metric conditions to the rows yourself; or b) run one report per (dimension condition, metric condition) pair. Prefer (a) — fewer reports — unless it would use excessive quota (https://developers.google.com/analytics/blog/2023/data-api-quota-management).
order_bys
Example order_bys arguments:
Ascending 'eventName': [ {"dimension":{"dimension_name":"eventName","order_type":"ALPHANUMERIC"},"desc":false} ]
Descending 'campaignName', ignoring case: [ {"dimension":{"dimension_name":"campaignName","order_type":"CASE_INSENSITIVE_ALPHANUMERIC"},"desc":true} ]
Ascending 'audienceId' (numeric): [ {"dimension":{"dimension_name":"audienceId","order_type":"NUMERIC"},"desc":false} ]
Descending 'eventCount': [ {"metric":{"metric_name":"eventCount"},"desc":true} ]
Ascending 'eventCount': [ {"metric":{"metric_name":"eventCount"},"desc":false} ]
Dimension and metric combined: [ {"dimension":{"dimension_name":"eventName","order_type":"ALPHANUMERIC"},"desc":false}, {"metric":{"metric_name":"eventCount"},"desc":true} ]
Several dimensions and metrics: [ {"dimension":{"dimension_name":"eventName","order_type":"ALPHANUMERIC"},"desc":false}, {"dimension":{"dimension_name":"audienceId","order_type":"NUMERIC"},"desc":false}, {"metric":{"metric_name":"eventCount"},"desc":true} ] The dimensions and metrics in order_bys must also be in the request's dimensions and metrics.
Inputs: metrics (required), dimensions (required), property_id (required), limit, +5 more.
run_reportA
Runs a Google Analytics Data API report (sessions, users, page views, events, revenue, and so on).
Rows come back flattened: one object per row keyed by dimension and metric name, metrics as numbers.
dimensions
Standard dimensions (https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema#dimensions) available to every property, or the property's custom dimensions from get_custom_dimensions_and_metrics.
metrics
Standard metrics (https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema#metrics) available to every property, or the property's custom metrics from get_custom_dimensions_and_metrics.
date_ranges
Example date_ranges arguments:
A single date range: [ {"start_date":"2025-01-01","end_date":"2025-01-31","name":"Jan2025"} ]
A relative date range using 'yesterday' and 'today': [ {"start_date":"yesterday","end_date":"today","name":"YesterdayAndToday"} ]
A relative date range using 'NdaysAgo': [ {"start_date":"30daysAgo","end_date":"yesterday","name":"Previous30Days"} ]
Multiple date ranges (adds a 'dateRange' column to every row): [ {"start_date":"2025-01-01","end_date":"2025-01-31","name":"Jan2025"}, {"start_date":"2025-02-01","end_date":"2025-02-28","name":"Feb2025"} ]
dimension_filter
Example dimension_filter arguments:
A simple filter: {"filter":{"field_name":"eventName","string_filter":{"match_type":"BEGINS_WITH","value":"add"}}}
A NOT filter: {"not_expression":{"filter":{"field_name":"eventName","string_filter":{"match_type":"BEGINS_WITH","value":"add"}}}}
An empty value filter: {"filter":{"field_name":"source","empty_filter":{}}}
An AND group filter: {"and_group":{"expressions":[{"filter":{"field_name":"sourceMedium","string_filter":{"match_type":"EXACT","value":"google / cpc"}}},{"filter":{"field_name":"eventName","in_list_filter":{"case_sensitive":true,"values":["first_visit","purchase","add_to_cart"]}}}]}}
An OR group filter: {"or_group":{"expressions":[{"filter":{"field_name":"sourceMedium","string_filter":{"match_type":"EXACT","value":"google / cpc"}}},{"filter":{"field_name":"eventName","in_list_filter":{"case_sensitive":true,"values":["first_visit","purchase","add_to_cart"]}}}]}}
Notes: The API applies dimension_filter and metric_filter independently, so some combinations are impossible in one request, e.g. ((eventName = "page_view" AND eventCount > 100) OR (eventName = "join_group" AND eventCount < 50)). For conditions shaped ((D1 AND M1) OR (D2 AND M2)) either: a) run one report filtering on the supported subset (eventName in ["page_view","join_group"]) with eventCount included, and apply the metric conditions to the rows yourself; or b) run one report per (dimension condition, metric condition) pair. Prefer (a) — fewer reports — unless it would use excessive quota (https://developers.google.com/analytics/blog/2023/data-api-quota-management).
metric_filter
Example metric_filter arguments:
A simple filter: {"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}}
A NOT filter: {"not_expression":{"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}}}
An empty value filter: {"filter":{"field_name":"purchaseRevenue","empty_filter":{}}}
An AND group filter: {"and_group":{"expressions":[{"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}},{"filter":{"field_name":"purchaseRevenue","between_filter":{"from_value":{"double_value":10},"to_value":{"double_value":25}}}}]}}
An OR group filter: {"or_group":{"expressions":[{"filter":{"field_name":"eventCount","numeric_filter":{"operation":"GREATER_THAN","value":{"int64_value":"10"}}}},{"filter":{"field_name":"purchaseRevenue","between_filter":{"from_value":{"double_value":10},"to_value":{"double_value":25}}}}]}}
Notes: The API applies dimension_filter and metric_filter independently, so some combinations are impossible in one request, e.g. ((eventName = "page_view" AND eventCount > 100) OR (eventName = "join_group" AND eventCount < 50)). For conditions shaped ((D1 AND M1) OR (D2 AND M2)) either: a) run one report filtering on the supported subset (eventName in ["page_view","join_group"]) with eventCount included, and apply the metric conditions to the rows yourself; or b) run one report per (dimension condition, metric condition) pair. Prefer (a) — fewer reports — unless it would use excessive quota (https://developers.google.com/analytics/blog/2023/data-api-quota-management).
order_bys
Example order_bys arguments:
Ascending 'eventName': [ {"dimension":{"dimension_name":"eventName","order_type":"ALPHANUMERIC"},"desc":false} ]
Descending 'campaignName', ignoring case: [ {"dimension":{"dimension_name":"campaignName","order_type":"CASE_INSENSITIVE_ALPHANUMERIC"},"desc":true} ]
Ascending 'audienceId' (numeric): [ {"dimension":{"dimension_name":"audienceId","order_type":"NUMERIC"},"desc":false} ]
Descending 'eventCount': [ {"metric":{"metric_name":"eventCount"},"desc":true} ]
Ascending 'eventCount': [ {"metric":{"metric_name":"eventCount"},"desc":false} ]
Dimension and metric combined: [ {"dimension":{"dimension_name":"eventName","order_type":"ALPHANUMERIC"},"desc":false}, {"metric":{"metric_name":"eventCount"},"desc":true} ]
Several dimensions and metrics: [ {"dimension":{"dimension_name":"eventName","order_type":"ALPHANUMERIC"},"desc":false}, {"dimension":{"dimension_name":"audienceId","order_type":"NUMERIC"},"desc":false}, {"metric":{"metric_name":"eventCount"},"desc":true} ] The dimensions and metrics in order_bys must also be in the request's dimensions and metrics.
Inputs: metrics (required), dimensions (required), date_ranges (required), property_id (required), +7 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 9 tools
Most tools are clearly distinct, but run_report vs run_conversions_report overlap in scope and the description has to explicitly say 'USE THIS TOOL INSTEAD OF run_report WHEN...' to disambiguate. run_realtime_report, run_funnel_report, and the get_/list_ discovery tools are cleanly separated, so only one boundary is fuzzy and it is well-documented.
All tools follow a predictable verb_noun pattern (get_*, list_*, run_*), which is readable and consistent. The only weakness is that the generic run_report lacks a qualifier like run_standard_report, making it slightly ambiguous against run_realtime_report/run_funnel_report/run_conversions_report.
Nine tools is well-scoped for a GA4 reporting server: five read/discovery tools plus four report runners covering the main Data API report shapes. No tool feels redundant or padded.
The surface covers discovery (accounts, property, ads links, annotations, custom dims/metrics) and all major report types (standard, realtime, funnel, conversions), so core analytical workflows are fully reachable. Minor gaps remain: no pivot or batch-run report equivalents and no annotation write operations.
Maintenance
Related MCP Connectors
Read-only Google Analytics 4 MCP server for AI assistants.
Hosted, read-only Google Analytics 4 MCP: reports, period comparisons, realtime, custom fields.
Hosted MCP server for GA4, Google Ads and Search Console. Google OAuth, nothing to install.
Read and edit GA4, Search Console and Google Tag Manager from any MCP client. 29 tools.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables querying Google Analytics 4 properties using natural language through MCP clients. Supports customizable reports with any dimensions and metrics, listing properties, and real-time data.4MIT
- AlicenseNot gradedqualityCmaintenanceEnables querying Google Analytics 4 data through MCP tools for running reports, listing properties, getting real-time metrics, and exploring metadata.462 npmMIT
- AlicenseBqualityBmaintenanceEnables read-only Google Analytics 4 reporting through MCP, including daily summaries, top pages, acquisition, events, realtime activity, metadata, and custom reports with CSV/JSON output.8MIT
- AlicenseAqualityBmaintenanceEnables read-only access to Google Analytics 4 data through seventeen tools covering reports, realtime data, funnels, pivots, metadata, and admin lists. It runs locally with service account credentials and lets users query properties via standard MCP clients.1723 PyPIMIT