ga4-mcp
Lists Google Ads links associated with GA4 properties.
Provides access to Google Analytics 4 data, including property metadata, custom dimensions and metrics, standard and real-time reports, property annotations, and account summaries.
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 what were my top pages by views in 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
Production-grade MCP server for Google Analytics 4 — self-hosted, token-secured, Claude-connector-ready.
A Model Context Protocol server that gives Claude, ChatGPT, Cursor, and any other MCP client direct access to your GA4 data — property metadata, custom dimensions and metrics, run_report (the full Data API surface), real-time reporting, property annotations, and Google Ads link inventories.
Built for remote deployment behind a reverse proxy: streamable-HTTP transport, token-gated auth, CORS, the path-rewrite fix that makes the Claude web connector actually load, and explicit prompts/resources capability declarations in the initialize response.
Highlights
Three transports —
stdio(Claude Desktop),sse(legacy remote), andstreamable-httpat/mcp(current spec, what the Claude web connector expects)Bare
/mcpworks — no 307 →http://redirect that Claude's connector refuses to followToken auth via 4 paths —
?token=,Authorization: Bearer,X-Api-Key,Mcp-Auth-TokenCORS for browser-based MCP clients — wildcard origins by default, restrict via env
No-compress middleware — Claude's web connector breaks on brotli-compressed event streams
prompts+resourcescapabilities declared in initialize — without this Claude's validator rejects the server/healthzendpoint for liveness probes.envloading viapython-dotenvproxy_headers=Trueso X-Forwarded-Proto is respected when behind Traefik/CaddyPure-ASGI path rewriter (bare
/mcp→/mcp/) — solved at scope level, before routing
The non-obvious bugs this server already solves
Bare
/mcpreturned 307 tohttp://. Starlette'sMount("/mcp", X)regex requires a trailing slash, so a bare POST/mcpfalls through to a 307 redirect. Worse, Traefik terminating TLS doesn't always rewrite the Location header back tohttps://— so Claude's connector validator got anhttp://redirect and rejected it with "Couldn't reach the MCP server". Fix: pure-ASGI middleware rewritesscope["path"]from/mcpto/mcp/before the router sees it. Alsoproxy_headers=Trueon uvicorn so any future redirect stays HTTPS.Missing capability blocks in initialize. Claude's connector validator rejects servers whose initialize response omits the
promptsorresourcescapability — even when the server has nothing to offer in those slots. FastMCP-based servers escape this because their wrapper registers default handlers, but a baremcp.server.lowlevel.Serveronly declares a capability when at least one handler is registered for it. Fix: register emptylist_prompts/list_resources/list_resource_templateshandlers so the initialize response declares all four blocks (experimental,prompts,resources,tools).
If you've ever spent an afternoon debugging "Couldn't reach the MCP server" with a working curl, these are the two fixes you needed.
Related MCP server: ga-mcp-full
Tool surface — 7 tools
get_account_summaries · list_google_ads_links · get_property_details · list_property_annotations · get_custom_dimensions_and_metrics · run_report · run_realtime_report
Tool surface mirrors the public Google Analytics MCP spec — the work here is in transport, auth, and deploy-ability, not in changing tool behavior.
Quick start — local (Claude Desktop)
git clone https://github.com/thesyedyahya/ga4-mcp.git
cd ga4-mcp
uv venv .venv
uv pip install -e .
export GOOGLE_APPLICATION_CREDENTIALS=/abs/path/to/service-account.json
analytics-mcpIn ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"ga4": {
"command": "/abs/path/to/.venv/bin/analytics-mcp",
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "/abs/path/to/service-account.json"
}
}
}
}Quick start — remote (Docker / Dokploy / any VPS)
docker build -t ga4-mcp .
docker run -d -p 8080:8080 \
-e GOOGLE_APPLICATION_CREDENTIALS_JSON="$(cat service-account.json | python3 -c 'import json,sys;print(json.dumps(json.load(sys.stdin)))')" \
-e MCP_AUTH_TOKEN="$(openssl rand -hex 32)" \
-e MCP_ALLOWED_ORIGINS="*" \
ga4-mcpPut Caddy/Traefik in front for HTTPS, point a domain at it, then add the connector in Claude:
https://mcp-ga4.example.com/mcp?token=<your-token>Auth setup
The service account must be added as a Viewer (or higher) on each GA4 property you want to query — not just at the account level. If get_account_summaries returns 0 properties, the SA is on the account but not on any properties.
GCP Console → IAM & Admin → Service Accounts → Create + JSON key
Google Analytics → Admin → Property access management → Add user → enter the SA email → Viewer
Repeat for each property the MCP should access
Set
GOOGLE_APPLICATION_CREDENTIALS(local) orGOOGLE_APPLICATION_CREDENTIALS_JSON(Docker)
Environment variables
Variable | Required | Default | Description |
| local | — | Absolute path to SA JSON |
| container | — | Single-line SA JSON. Entrypoint decodes to a file. |
| No |
|
|
| No |
| Bind host |
| No |
| Bind port |
| Public deploy | — | If unset, server runs open |
| No |
| CORS allowlist (token gates access) |
Sample prompts
After connecting:
"List my GA4 properties and their key timezones."
"Run a 28-day report on property
properties/XXXXX— sessions, conversions, average engagement time, broken down by source/medium.""Show me the last hour of users on property
properties/XXXXX, broken down by country.""List custom dimensions and metrics on
properties/XXXXX. Which are unused in the last 30 days?"
License
Apache 2.0. See LICENSE.
About
Maintained by @thesyedyahya — I build production MCP servers for Claude, ChatGPT, and Cursor. If you need a custom MCP for your API or help deploying one, open an issue or reach me on Upwork or Fiverr.
Available Tools
7 toolsget_account_summariesGet Account SummariesB
Retrieves information about the user's Google Analytics accounts and properties.
Inputs: no named parameters. Returns: result
get_custom_dimensions_and_metricsGet Custom Dimensions And MetricsB
Returns the property's custom dimensions and metrics.
Inputs: property_id (required). Returns: object
get_property_detailsGet Property DetailsA
Returns details about a property. Args: property_id: The Google Analytics property ID. Accepted formats are: - A number - A string consisting of 'properties/' followed by a number
Inputs: property_id (required). Returns: object
list_google_ads_linksList Google Ads LinksA
Returns a list of links to Google Ads accounts for a property.
Inputs: property_id (required). Returns: result
list_property_annotationsList Property AnnotationsA
Returns annotations for a property.
Annotations are a feature that allows you to leave notes on GA4 for specific dates or periods. They are typically used to record service releases, marketing campaign launches or changes, and rapid traffic increases or decreases due to external factors.
Inputs: property_id (required). Returns: result
run_realtime_reportRun Realtime ReportA
Runs a Google Analytics Data API realtime report.See https://developers.google.com/analytics/devguides/reporting/data/v1/realtime-basics for more information.
Args:
property_id: The Google Analytics property ID. Accepted formats are:
- A number
- A string consisting of 'properties/' followed by a number
dimensions: A list of dimensions to include in the report. Dimensions must be realtime dimensions.
metrics: A list of metrics to include in the report. Metrics must be realtime metrics.
dimension_filter: A Data API FilterExpression
(https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/FilterExpression)
to apply to the dimensions. Don't use this for filtering metrics. Use
metric_filter instead. The field_name in a dimension_filter must
be a dimension, as defined in the get_standard_dimensions and
get_dimensions tools.
For more information about the expected format of this argument, see
the run_report_dimension_filter_hints tool.
metric_filter: A Data API FilterExpression
(https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/FilterExpression)
to apply to the metrics. Don't use this for filtering dimensions. Use
dimension_filter instead. The field_name in a metric_filter must
be a metric, as defined in the get_standard_metrics and
get_metrics tools.
For more information about the expected format of this argument, see
the run_report_metric_filter_hints tool.
order_bys: A list of Data API OrderBy
(https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/OrderBy)
objects to apply to the dimensions and metrics.
For more information about the expected format of this argument, see
the run_report_order_bys_hints tool.
limit: The maximum number of rows to return in each response. Value must
be a positive integer <= 250,000. Used to paginate through large
reports, following the guide at
https://developers.google.com/analytics/devguides/reporting/data/v1/basics#pagination.
offset: The row count of the start row. The first row is counted as row
0. Used to paginate through large
reports, following the guide at
https://developers.google.com/analytics/devguides/reporting/data/v1/basics#pagination.
return_property_quota: Whether to return realtime property quota in the response.
## Hints for arguments
Here are some hints that outline the expected format and requirements
for arguments.
### Hints for `dimensions`
The `dimensions` list must consist solely of either of the following:
1. Realtime standard dimensions defined in the HTML table at
https://developers.google.com/analytics/devguides/reporting/data/v1/realtime-api-schema#dimensions.
These dimensions are available to *every* property.
2. User-scoped custom dimensions for the `property_id`. Use the
`get_custom_dimensions_and_metrics` tool to retrieve the list of
custom dimensions for a property, and look for the custom
dimensions with an `apiName` that begins with "customUser:".
### Hints for `metrics`
The `metrics` list must consist solely of the Realtime standard
metrics defined in the HTML table at
https://developers.google.com/analytics/devguides/reporting/data/v1/realtime-api-schema#metrics.
These metrics are available to *every* property.
Realtime reports can't use custom metrics.
### Hints for `date_ranges`:
Example date_range arguments:
1. A single date range:
[ {"start_date": "2025-01-01", "end_date": "2025-01-31", "name": "Jan2025"} ]
2. A relative date range using 'yesterday' and 'today':
[ {"start_date": "yesterday", "end_date": "today", "name": "YesterdayAndToday"} ]
3. A relative date range using 'NdaysAgo' and 'today':
[ {"start_date": "30daysAgo", "end_date": "yesterday", "name": "Previous30Days"}]
4. Multiple date ranges:
[ {"start_date": "2025-01-01", "end_date": "2025-01-31", "name": "Jan2025"}, {"start_date": "2025-02-01", "end_date": "2025-02-28", "name": "Feb2025"} ]
### Hints for `dimension_filter`:
Example dimension_filter arguments:
1. A simple filter:
{"filter": {"field_name": "eventName", "string_filter": {"match_type": 2, "value": "add", "case_sensitive": false}}}
2. A NOT filter:
{"not_expression": {"filter": {"field_name": "eventName", "string_filter": {"match_type": 2, "value": "add", "case_sensitive": false}}}}
3. An empty value filter:
{"filter": {"field_name": "source", "empty_filter": {}}}
4. An AND group filter:
{"and_group": {"expressions": [{"filter": {"field_name": "sourceMedium", "string_filter": {"match_type": 1, "value": "google / cpc", "case_sensitive": false}}}, {"filter": {"field_name": "eventName", "in_list_filter": {"values": ["first_visit", "purchase", "add_to_cart"], "case_sensitive": true}}}]}}
5. An OR group filter:
{"or_group": {"expressions": [{"filter": {"field_name": "sourceMedium", "string_filter": {"match_type": 1, "value": "google / cpc", "case_sensitive": false}}}, {"filter": {"field_name": "eventName", "in_list_filter": {"values": ["first_visit", "purchase", "add_to_cart"], "case_sensitive": true}}}]}} Notes:
The API applies the dimension_filter and metric_filter
independently. As a result, some complex combinations of dimension and
metric filters are not possible in a single report request.
For example, you can't create a `dimension_filter` and `metric_filter`
combination for the following condition:
(
(eventName = "page_view" AND eventCount > 100)
OR
(eventName = "join_group" AND eventCount < 50)
)
This isn't possible because there's no way to apply the condition
"eventCount > 100" only to the data with eventName of "page_view", and
the condition "eventCount < 50" only to the data with eventName of
"join_group".
More generally, you can't define a `dimension_filter` and `metric_filter`
for:
(
((dimension condition D1) AND (metric condition M1))
OR
((dimension condition D2) AND (metric condition M2))
)
If you have complex conditions like this, either:
a) Run a single report that applies a subset of the conditions that
the API supports as well as the data needed to perform filtering of the
API response on the client side. For example, for the condition:
(
(eventName = "page_view" AND eventCount > 100)
OR
(eventName = "join_group" AND eventCount < 50)
)
You could run a report that filters only on:
eventName one of "page_view" or "join_group"
and include the eventCount metric, then filter the API response on the
client side to apply the different metric filters for the different
events.
or
b) Run a separate report for each combination of dimension condition and
metric condition. For the example above, you'd run one report for the
combination of (D1 AND M1), and another report for the combination of
(D2 AND M2).
Try to run fewer reports (option a) if possible. However, if running
fewer reports results in excessive quota usage for the API, use option
b. More information on quota usage is at
https://developers.google.com/analytics/blog/2023/data-api-quota-management.
### Hints for `metric_filter`:
Example metric_filter arguments:
1. A simple filter:
{"filter": {"field_name": "eventCount", "numeric_filter": {"operation": 4, "value": {"int64_value": "10"}}}}
2. A NOT filter:
{"not_expression": {"filter": {"field_name": "eventCount", "numeric_filter": {"operation": 4, "value": {"int64_value": "10"}}}}}
3. An empty value filter:
{"filter": {"field_name": "purchaseRevenue", "empty_filter": {}}}
4. An AND group filter:
{"and_group": {"expressions": [{"filter": {"field_name": "eventCount", "numeric_filter": {"operation": 4, "value": {"int64_value": "10"}}}}, {"filter": {"field_name": "purchaseRevenue", "between_filter": {"from_value": {"double_value": 10.0}, "to_value": {"double_value": 25.0}}}}]}}
5. An OR group filter:
{"or_group": {"expressions": [{"filter": {"field_name": "eventCount", "numeric_filter": {"operation": 4, "value": {"int64_value": "10"}}}}, {"filter": {"field_name": "purchaseRevenue", "between_filter": {"from_value": {"double_value": 10.0}, "to_value": {"double_value": 25.0}}}}]}} Notes:
The API applies the dimension_filter and metric_filter
independently. As a result, some complex combinations of dimension and
metric filters are not possible in a single report request.
For example, you can't create a `dimension_filter` and `metric_filter`
combination for the following condition:
(
(eventName = "page_view" AND eventCount > 100)
OR
(eventName = "join_group" AND eventCount < 50)
)
This isn't possible because there's no way to apply the condition
"eventCount > 100" only to the data with eventName of "page_view", and
the condition "eventCount < 50" only to the data with eventName of
"join_group".
More generally, you can't define a `dimension_filter` and `metric_filter`
for:
(
((dimension condition D1) AND (metric condition M1))
OR
((dimension condition D2) AND (metric condition M2))
)
If you have complex conditions like this, either:
a) Run a single report that applies a subset of the conditions that
the API supports as well as the data needed to perform filtering of the
API response on the client side. For example, for the condition:
(
(eventName = "page_view" AND eventCount > 100)
OR
(eventName = "join_group" AND eventCount < 50)
)
You could run a report that filters only on:
eventName one of "page_view" or "join_group"
and include the eventCount metric, then filter the API response on the
client side to apply the different metric filters for the different
events.
or
b) Run a separate report for each combination of dimension condition and
metric condition. For the example above, you'd run one report for the
combination of (D1 AND M1), and another report for the combination of
(D2 AND M2).
Try to run fewer reports (option a) if possible. However, if running
fewer reports results in excessive quota usage for the API, use option
b. More information on quota usage is at
https://developers.google.com/analytics/blog/2023/data-api-quota-management.
### Hints for `order_bys`:
Example order_bys arguments:
1. Order by ascending 'eventName':
[ {"dimension": {"dimension_name": "eventName", "order_type": 1}, "desc": false} ]
2. Order by descending 'eventName', ignoring case:
[ {"dimension": {"dimension_name": "campaignName", "order_type": 2}, "desc": true} ]
3. Order by ascending 'audienceId':
[ {"dimension": {"dimension_name": "audienceId", "order_type": 3}, "desc": false} ]
4. Order by descending 'eventCount':
[ {"metric": {"metric_name": "eventValue"}, "desc": true} ]
5. Order by ascending 'eventCount':
[ {"metric": {"metric_name": "eventCount"}, "desc": false} ]
6. Combination of dimension and metric order bys:
[
{"dimension": {"dimension_name": "eventName", "order_type": 1}, "desc": false},
{"metric": {"metric_name": "eventValue"}, "desc": true},
]
7. Order by multiple dimensions and metrics:
[
{"dimension": {"dimension_name": "eventName", "order_type": 1}, "desc": false},
{"dimension": {"dimension_name": "audienceId", "order_type": 3}, "desc": false},
{"metric": {"metric_name": "eventValue"}, "desc": true},
]
The dimensions and metrics in order_bys must also be present in the report
request's "dimensions" and "metrics" arguments, respectively.Inputs: metrics (required), dimensions (required), property_id (required), limit, +5 more. Returns: object
run_reportRun ReportA
Runs a Google Analytics Data API report.Note that the reference docs at https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta all use camelCase field names, but field names passed to this method should be in snake_case since the tool is using the protocol buffers (protobuf) format. The protocol buffers for the Data API are available at https://github.com/googleapis/googleapis/tree/master/google/analytics/data/v1beta.
Args:
property_id: The Google Analytics property ID. Accepted formats are:
- A number
- A string consisting of 'properties/' followed by a number
date_ranges: A list of date ranges
(https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/DateRange)
to include in the report.
dimensions: A list of dimensions to include in the report.
metrics: A list of metrics to include in the report.
dimension_filter: A Data API FilterExpression
(https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/FilterExpression)
to apply to the dimensions. Don't use this for filtering metrics. Use
metric_filter instead. The field_name in a dimension_filter must
be a dimension, as defined in the get_standard_dimensions and
get_dimensions tools.
metric_filter: A Data API FilterExpression
(https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/FilterExpression)
to apply to the metrics. Don't use this for filtering dimensions. Use
dimension_filter instead. The field_name in a metric_filter must
be a metric, as defined in the get_standard_metrics and
get_metrics tools.
order_bys: A list of Data API OrderBy
(https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/OrderBy)
objects to apply to the dimensions and metrics.
limit: The maximum number of rows to return in each response. Value must
be a positive integer <= 250,000. Used to paginate through large
reports, following the guide at
https://developers.google.com/analytics/devguides/reporting/data/v1/basics#pagination.
offset: The row count of the start row. The first row is counted as row
0. Used to paginate through large
reports, following the guide at
https://developers.google.com/analytics/devguides/reporting/data/v1/basics#pagination.
currency_code: The currency code to use for currency values. Must be in
ISO4217 format, such as "AED", "USD", "JPY". If the field is empty, the
report uses the property's default currency.
return_property_quota: Whether to return property quota in the response.
## Hints for arguments
Here are some hints that outline the expected format and requirements
for arguments.
### Hints for `dimensions`
The `dimensions` list must consist solely of either of the following:
1. Standard dimensions defined in the HTML table at
https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema#dimensions.
These dimensions are available to *every* property.
2. Custom dimensions for the `property_id`. Use the
`get_custom_dimensions_and_metrics` tool to retrieve the list of
custom dimensions for a property.
### Hints for `metrics`
The `metrics` list must consist solely of either of the following:
1. Standard metrics defined in the HTML table at
https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema#metrics.
These metrics are available to *every* property.
2. Custom metrics for the `property_id`. Use the
`get_custom_dimensions_and_metrics` tool to retrieve the list of
custom metrics for a property.
### Hints for `date_ranges`:
Example date_range arguments:
1. A single date range:
[ {"start_date": "2025-01-01", "end_date": "2025-01-31", "name": "Jan2025"} ]
2. A relative date range using 'yesterday' and 'today':
[ {"start_date": "yesterday", "end_date": "today", "name": "YesterdayAndToday"} ]
3. A relative date range using 'NdaysAgo' and 'today':
[ {"start_date": "30daysAgo", "end_date": "yesterday", "name": "Previous30Days"}]
4. Multiple date ranges:
[ {"start_date": "2025-01-01", "end_date": "2025-01-31", "name": "Jan2025"}, {"start_date": "2025-02-01", "end_date": "2025-02-28", "name": "Feb2025"} ]
### Hints for `dimension_filter`:
Example dimension_filter arguments:
1. A simple filter:
{"filter": {"field_name": "eventName", "string_filter": {"match_type": 2, "value": "add", "case_sensitive": false}}}
2. A NOT filter:
{"not_expression": {"filter": {"field_name": "eventName", "string_filter": {"match_type": 2, "value": "add", "case_sensitive": false}}}}
3. An empty value filter:
{"filter": {"field_name": "source", "empty_filter": {}}}
4. An AND group filter:
{"and_group": {"expressions": [{"filter": {"field_name": "sourceMedium", "string_filter": {"match_type": 1, "value": "google / cpc", "case_sensitive": false}}}, {"filter": {"field_name": "eventName", "in_list_filter": {"values": ["first_visit", "purchase", "add_to_cart"], "case_sensitive": true}}}]}}
5. An OR group filter:
{"or_group": {"expressions": [{"filter": {"field_name": "sourceMedium", "string_filter": {"match_type": 1, "value": "google / cpc", "case_sensitive": false}}}, {"filter": {"field_name": "eventName", "in_list_filter": {"values": ["first_visit", "purchase", "add_to_cart"], "case_sensitive": true}}}]}} Notes:
The API applies the dimension_filter and metric_filter
independently. As a result, some complex combinations of dimension and
metric filters are not possible in a single report request.
For example, you can't create a `dimension_filter` and `metric_filter`
combination for the following condition:
(
(eventName = "page_view" AND eventCount > 100)
OR
(eventName = "join_group" AND eventCount < 50)
)
This isn't possible because there's no way to apply the condition
"eventCount > 100" only to the data with eventName of "page_view", and
the condition "eventCount < 50" only to the data with eventName of
"join_group".
More generally, you can't define a `dimension_filter` and `metric_filter`
for:
(
((dimension condition D1) AND (metric condition M1))
OR
((dimension condition D2) AND (metric condition M2))
)
If you have complex conditions like this, either:
a) Run a single report that applies a subset of the conditions that
the API supports as well as the data needed to perform filtering of the
API response on the client side. For example, for the condition:
(
(eventName = "page_view" AND eventCount > 100)
OR
(eventName = "join_group" AND eventCount < 50)
)
You could run a report that filters only on:
eventName one of "page_view" or "join_group"
and include the eventCount metric, then filter the API response on the
client side to apply the different metric filters for the different
events.
or
b) Run a separate report for each combination of dimension condition and
metric condition. For the example above, you'd run one report for the
combination of (D1 AND M1), and another report for the combination of
(D2 AND M2).
Try to run fewer reports (option a) if possible. However, if running
fewer reports results in excessive quota usage for the API, use option
b. More information on quota usage is at
https://developers.google.com/analytics/blog/2023/data-api-quota-management.
### Hints for `metric_filter`:
Example metric_filter arguments:
1. A simple filter:
{"filter": {"field_name": "eventCount", "numeric_filter": {"operation": 4, "value": {"int64_value": "10"}}}}
2. A NOT filter:
{"not_expression": {"filter": {"field_name": "eventCount", "numeric_filter": {"operation": 4, "value": {"int64_value": "10"}}}}}
3. An empty value filter:
{"filter": {"field_name": "purchaseRevenue", "empty_filter": {}}}
4. An AND group filter:
{"and_group": {"expressions": [{"filter": {"field_name": "eventCount", "numeric_filter": {"operation": 4, "value": {"int64_value": "10"}}}}, {"filter": {"field_name": "purchaseRevenue", "between_filter": {"from_value": {"double_value": 10.0}, "to_value": {"double_value": 25.0}}}}]}}
5. An OR group filter:
{"or_group": {"expressions": [{"filter": {"field_name": "eventCount", "numeric_filter": {"operation": 4, "value": {"int64_value": "10"}}}}, {"filter": {"field_name": "purchaseRevenue", "between_filter": {"from_value": {"double_value": 10.0}, "to_value": {"double_value": 25.0}}}}]}} Notes:
The API applies the dimension_filter and metric_filter
independently. As a result, some complex combinations of dimension and
metric filters are not possible in a single report request.
For example, you can't create a `dimension_filter` and `metric_filter`
combination for the following condition:
(
(eventName = "page_view" AND eventCount > 100)
OR
(eventName = "join_group" AND eventCount < 50)
)
This isn't possible because there's no way to apply the condition
"eventCount > 100" only to the data with eventName of "page_view", and
the condition "eventCount < 50" only to the data with eventName of
"join_group".
More generally, you can't define a `dimension_filter` and `metric_filter`
for:
(
((dimension condition D1) AND (metric condition M1))
OR
((dimension condition D2) AND (metric condition M2))
)
If you have complex conditions like this, either:
a) Run a single report that applies a subset of the conditions that
the API supports as well as the data needed to perform filtering of the
API response on the client side. For example, for the condition:
(
(eventName = "page_view" AND eventCount > 100)
OR
(eventName = "join_group" AND eventCount < 50)
)
You could run a report that filters only on:
eventName one of "page_view" or "join_group"
and include the eventCount metric, then filter the API response on the
client side to apply the different metric filters for the different
events.
or
b) Run a separate report for each combination of dimension condition and
metric condition. For the example above, you'd run one report for the
combination of (D1 AND M1), and another report for the combination of
(D2 AND M2).
Try to run fewer reports (option a) if possible. However, if running
fewer reports results in excessive quota usage for the API, use option
b. More information on quota usage is at
https://developers.google.com/analytics/blog/2023/data-api-quota-management.
### Hints for `order_bys`:
Example order_bys arguments:
1. Order by ascending 'eventName':
[ {"dimension": {"dimension_name": "eventName", "order_type": 1}, "desc": false} ]
2. Order by descending 'eventName', ignoring case:
[ {"dimension": {"dimension_name": "campaignName", "order_type": 2}, "desc": true} ]
3. Order by ascending 'audienceId':
[ {"dimension": {"dimension_name": "audienceId", "order_type": 3}, "desc": false} ]
4. Order by descending 'eventCount':
[ {"metric": {"metric_name": "eventValue"}, "desc": true} ]
5. Order by ascending 'eventCount':
[ {"metric": {"metric_name": "eventCount"}, "desc": false} ]
6. Combination of dimension and metric order bys:
[
{"dimension": {"dimension_name": "eventName", "order_type": 1}, "desc": false},
{"metric": {"metric_name": "eventValue"}, "desc": true},
]
7. Order by multiple dimensions and metrics:
[
{"dimension": {"dimension_name": "eventName", "order_type": 1}, "desc": false},
{"dimension": {"dimension_name": "audienceId", "order_type": 3}, "desc": false},
{"metric": {"metric_name": "eventValue"}, "desc": true},
]
The dimensions and metrics in order_bys must also be present in the report
request's "dimensions" and "metrics" arguments, respectively.Inputs: metrics (required), dimensions (required), date_ranges (required), property_id (required), +7 more. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 7 tools
Each tool has a distinct purpose (account summaries, standard reports, realtime reports, ads links, property details, annotations, custom schema), with only minor overlap between run_report and run_realtime_report. The 'realtime' qualifier makes the difference clear, so misselection is unlikely.
All tool names follow a consistent snake_case verb_noun pattern (get_, list_, run_). Retrieval verbs are appropriately differentiated: 'get' for singular objects and 'list' for plural collections, with no mixed conventions.
Seven tools is a well-scoped count for a GA4 data-access server. It covers account discovery, reporting, realtime reporting, schema introspection, annotations, and ad links without feeling bloated or unduly thin.
The server has significant gaps: run_report and run_realtime_report reference non-existent helper tools like get_standard_dimensions, get_dimensions, get_standard_metrics, and hint tools, which will cause agent failures when trying to discover valid fields. Additionally, annotations are read-only (list only), so the surface is incomplete for the domain it claims to support.
Maintenance
Related MCP Connectors
Read-only Google Analytics 4 MCP server for AI assistants.
Read and edit GA4, Search Console and Google Tag Manager from any MCP client. 29 tools.
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.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceManages Google Analytics 4 accounts, properties, annotations, audiences, and custom dimensions through the MCP protocol.16 npm1ISC
- AlicenseNot gradedqualityCmaintenanceProvides full Google Analytics 4 management and reporting capabilities via MCP, including property management, custom dimensions, audiences, events, data streams, and real-time reporting.MIT
- 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.149 npmMIT