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-mcpshow 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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden, and it does disclose the key behavioral fact that results are limited to what the service account can read (a permission boundary). However, it never states that it is a read-only, side-effect-free operation, and says nothing about pagination, rate limits, or result volume.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with no filler, front-loading the resource and scoping constraint before the returned identifier detail. Nothing extraneous and nothing buried.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description must sketch the return; it does so at a high level (accounts, properties, property IDs). It stops short of clarifying the account→property nesting or whether results are paginated, which an agent would need to parse the response confidently.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there are no parameter semantics to document and the baseline of 4 applies. The description correctly adds no spurious parameter guidance.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb ("List") plus specific resource (Google Analytics accounts and properties) and a scope qualifier ("the service account can read"), plus the notable payload detail "with property IDs". It is distinguishable from siblings like get_property_details or run_report by being the account/property discovery call, though no sibling is named explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: because it returns property IDs, an agent can infer this is the discovery step before run_report or get_property_details. There is no explicit statement of when to call it, when not to, or which sibling to use instead.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_custom_dimensions_and_metricsA
Return the property's custom dimensions and metrics, with the apiName to use in reports.
| Name | Required | Description | Default |
|---|---|---|---|
| property_id | Yes | The Google Analytics property ID: a number, or 'properties/' followed by a number. Use get_account_summaries to find it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. 'Return' implies a read-only lookup and it discloses the returned content (dimensions, metrics, apiName), but it says nothing about authentication/permission needs, empty-result behavior, or possible errors for an invalid property.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with no filler, and the core resource is front-loaded before the apiName purpose. Nothing could be trimmed without losing information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read tool with one fully documented parameter this is close to adequate, and the mention of apiName hints at the payload. With no output schema, however, the description should at least sketch the shape of the returned dimensions/metrics rather than leaving the agent to guess.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single property_id parameter is fully documented in the schema, including how to find it. The description adds no parameter-level meaning beyond that, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Return') and resource ('the property's custom dimensions and metrics') with the added detail that it supplies the apiName for reports. That distinguishes it from siblings like get_property_details or get_account_summaries, though it never names a sibling explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'with the apiName to use in reports' implies the tool is a lookup used to build report requests, which is useful inferred context. There is no explicit when-to-use statement, no exclusion of alternatives, and no guidance on what to do if the property has no custom dimensions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_property_detailsB
Return details about a property: time zone, currency, industry, service level, create time.
| Name | Required | Description | Default |
|---|---|---|---|
| property_id | Yes | The Google Analytics property ID: a number, or 'properties/' followed by a number. Use get_account_summaries to find it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description must carry behavioral disclosure. It implicitly signals a read-only lookup and usefully enumerates the fields returned, which is real behavioral value. However, it says nothing about error behavior for an invalid/missing property, permission requirements, or rate limits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, front-loaded sentence with the verb and the returned-field list; every word earns its place with no preamble or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description usefully compensates by listing the returned fields. Combined with a fully documented single parameter, this is nearly complete for a simple read tool; only edge-case behavior (missing property, auth) is unaddressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and there is only one parameter, whose accepted formats and discovery path are fully documented in the schema. The description adds no additional meaning about property_id, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Return details about a property') and enumerates the returned fields (time zone, currency, industry, service level, create time), so the agent knows exactly what this yields. It doesn't explicitly differentiate itself from siblings like get_account_summaries, but those operate on a different resource, so confusion risk is low.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No when-to-use guidance, no prerequisites, and no alternatives named. The useful routing hint ('Use get_account_summaries to find it') lives in the schema's property_id description, not in the tool description, so the description itself offers no selection help.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_google_ads_linksC
List the Google Ads accounts linked to a property.
| Name | Required | Description | Default |
|---|---|---|---|
| property_id | Yes | The Google Analytics property ID: a number, or 'properties/' followed by a number. Use get_account_summaries to find it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are supplied, so the description carries the full behavioral burden, yet it discloses nothing beyond the core action. It does not state auth/permission requirements, whether the result is paginated, how links are ordered, or what happens for a property with no linked accounts.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no filler, which suits a simple one-parameter list tool. It is efficient, though perhaps too terse given the absence of annotations and output schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple listing tool with full schema coverage this is minimally adequate, but with no annotations and no output schema it leaves an agent without any behavioral context (safety, pagination, or return shape). The gaps are real but modest given the tool's low complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the property_id field is already well documented (including a pointer to get_account_summaries). The description adds no additional meaning about the parameter, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (List) and a well-scoped resource (Google Ads accounts linked to a property), which an agent can distinguish from the get_* and run_* siblings. It does not explicitly name or differentiate from any sibling, keeping it short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description contains no when-to-use, when-not-to-use, or alternative guidance. It never states that a property_id is required or where to obtain one (the schema does that), nor distinguishes this from other listing tools. Usage is only faintly implied by the word 'linked to a property'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| property_id | Yes | The Google Analytics property ID: a number, or 'properties/' followed by a number. Use get_account_summaries to find it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. 'List' plus the note-like nature of annotations conveys that this is a non-mutating read, but it says nothing about permissions, rate limits, result volume, or ordering. It adds useful domain context (what annotations contain) without covering the operational traits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence that front-loads the verb and resource, then spends the remainder clarifying the domain term 'annotation'. Every clause earns its place and nothing is repeated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema and no annotations, so the description is the only source of behavioral information. It conveys the concept of annotations but not the shape of the response (fields, ordering, pagination), which for a list tool leaves a gap despite the simple one-parameter surface.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single property_id parameter is thoroughly documented in the schema, including the accepted formats and how to discover it via get_account_summaries. The description adds no parameter detail, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb ('List') and a precisely scoped resource ('a property's annotations'), then explains what an annotation is in GA4. No sibling tool touches annotations, so an agent can tell this apart from the report/get siblings without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explains what annotations are and when they typically exist ('for releases, campaign launches or changes'), but never states when to call this tool, what prerequisite (a valid property_id) is needed, or how it relates to alternatives. That context is about the data, not about tool selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows per response, 1-250000. Paginate with offset. | |
| offset | No | Row count of the start row (first row is 0). | |
| metrics | Yes | ||
| order_bys | No | Data API OrderBy objects; every field named must be in dimensions/metrics. | |
| dimensions | Yes | ||
| date_ranges | Yes | Date ranges: [{start_date, end_date, name?}]. Dates are YYYY-MM-DD, 'today', 'yesterday' or 'NdaysAgo'. | |
| property_id | Yes | The Google Analytics property ID: a number, or 'properties/' followed by a number. Use get_account_summaries to find it. | |
| currency_code | No | ||
| metric_filter | No | Data API FilterExpression on metrics. Don't use it for dimensions; use dimension_filter. | |
| conversion_spec | Yes | {conversion_actions: string[], attribution_model: 'DATA_DRIVEN' | 'LAST_CLICK'} | |
| dimension_filter | No | Data API FilterExpression on dimensions. Don't use it for metrics; use metric_filter. | |
| return_property_quota | No | Include the property's quota usage in the response. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses the conversion/dimension restriction and links to quota documentation plus the max-row/pagination limit, which is useful. However it never states this is a read-only report (no mutation side effects), nor auth/permission requirements for the property, leaving the safety profile implicit.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads purpose and the run_report alternative well, but the long 'Notes: The API applies dimension_filter and metric_filter independently...' block is duplicated verbatim under both filter sections, adding significant redundancy. The reference-example format is otherwise scannable but bloats length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex 12-parameter tool with nested filters and no output schema, the description supplies the allowed dimensions/metrics enumerations, required conversion_spec, and example payloads for every structured param. An agent has enough to call it correctly; only return-value shape is unstated, which is acceptable for a report tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 75%, but the description goes well beyond it: worked examples for date_ranges (relative dates, multiple ranges), dimension_filter and metric_filter (all filter shapes including AND/OR/NOT/empty), order_bys, and a required conversion_spec format with empty-list semantics. This meaningfully reduces invocation errors.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Runs a Google Analytics Data API conversions report') and explicitly distinguishes itself from the sibling run_report, listing the exact metrics and dimensions that are conversions-only. An agent can select it over run_report without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides an explicit 'USE THIS TOOL INSTEAD OF run_report WHEN' list with four concrete triggering conditions (conversions, ROAS, attribution model, ad metrics). This is a textbook when-to-use/when-to-prefer-alternative block naming the sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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"} ]
| Name | Required | Description | Default |
|---|---|---|---|
| segments | No | ||
| date_ranges | No | Date ranges: [{start_date, end_date, name?}]. Dates are YYYY-MM-DD, 'today', 'yesterday' or 'NdaysAgo'. | |
| property_id | Yes | The Google Analytics property ID: a number, or 'properties/' followed by a number. Use get_account_summaries to find it. | |
| funnel_steps | Yes | ||
| funnel_breakdown | No | ||
| funnel_next_action | No | ||
| return_property_quota | No | Include the property's quota usage in the response. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It discloses that results are flattened to one object per row and that multiple date_ranges add a dateRange column, but it does not describe permissions, quota behavior, error conditions, or whether the operation is read-only.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well organized with purpose and return values front-loaded, followed by parameter-specific sections. It is long and includes multiple verbose examples, but for a complex nested funnel_steps schema this length is largely justified.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complex nested input, lack of annotations, and absence of an output schema, the description provides strong invocation guidance and partial return-value context. It still omits deeper behavioral details such as auth requirements, quota limits, and error handling.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 43%, so the description must compensate and does so extensively. It provides detailed examples for funnel_steps, funnel_breakdown, funnel_next_action, segments, and date_ranges, including shorthand event steps, filter_expression shapes, AND/OR groups, and relative date syntax.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Runs a Google Analytics Data API funnel report.' It clearly identifies the report type and mentions the returned artifacts, funnel_table and funnel_visualization, but it does not explicitly distinguish this tool from sibling tools such as run_report or run_conversions_report.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the funnel-specific examples and configuration sections, but the description never states when to choose this tool over run_report or run_conversions_report. There are no explicit when-to-use or when-not-to-use rules.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows per response, 1-250000. Paginate with offset. | |
| offset | No | Row count of the start row (first row is 0). | |
| metrics | Yes | ||
| order_bys | No | Data API OrderBy objects; every field named must be in dimensions/metrics. | |
| dimensions | Yes | ||
| property_id | Yes | The Google Analytics property ID: a number, or 'properties/' followed by a number. Use get_account_summaries to find it. | |
| metric_filter | No | Data API FilterExpression on metrics. Don't use it for dimensions; use dimension_filter. | |
| dimension_filter | No | Data API FilterExpression on dimensions. Don't use it for metrics; use metric_filter. | |
| return_property_quota | No | Include the property's quota usage in the response. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does substantial work: it discloses the 30-minute window, mutual exclusivity of custom metrics, the independence of dimension_filter and metric_filter, and quota implications with a link. It stops short of describing response shape or pagination behavior, which matters for a tool with no output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Headings front-load the important material and the examples are genuinely useful, but the Notes block under dimension_filter is reproduced verbatim under metric_filter, wasting roughly a third of the text. An agent must read past duplicated prose to reach order_bys.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 9-parameter, nested-object tool with no output schema and no annotations, the description covers the hard parts (filter semantics, ordering rules, realtime restrictions). Remaining gaps are pagination and response format, both of which the schema only partially covers.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 78%, yet the description adds real meaning for the four complex parameters (dimensions, metrics, dimension_filter, metric_filter, order_bys) via five worked examples each and the constraint that order_bys fields must also appear in dimensions/metrics. It adds nothing for limit, offset, or return_property_quota, which the schema already documents.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Runs a Google Analytics Data API realtime report') plus the defining scope ('roughly the last 30 minutes'), which cleanly separates it from the sibling run_report (historical). An agent can pick it without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear operational constraints: realtime dimensions only (plus 'customUser:' custom dimensions from get_custom_dimensions_and_metrics), realtime metrics only, and no custom metrics. It also explains how to handle impossible filter combinations and which workaround to prefer. It never explicitly contrasts with run_report, so the alternative-selection guidance is implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows per response, 1-250000. Paginate with offset. | |
| offset | No | Row count of the start row (first row is 0). | |
| metrics | Yes | ||
| order_bys | No | Data API OrderBy objects; every field named must be in dimensions/metrics. | |
| dimensions | Yes | ||
| date_ranges | Yes | Date ranges: [{start_date, end_date, name?}]. Dates are YYYY-MM-DD, 'today', 'yesterday' or 'NdaysAgo'. | |
| property_id | Yes | The Google Analytics property ID: a number, or 'properties/' followed by a number. Use get_account_summaries to find it. | |
| currency_code | No | ISO 4217, e.g. SEK. Defaults to the property's currency. | |
| metric_filter | No | Data API FilterExpression on metrics. Don't use it for dimensions; use dimension_filter. | |
| dimension_filter | No | Data API FilterExpression on dimensions. Don't use it for metrics; use metric_filter. | |
| return_property_quota | No | Include the property's quota usage in the response. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the flattened row shape ('one object per row keyed by dimension and metric name, metrics as numbers'), the multi-date-range behavior (adds a 'dateRange' column), the independent-application quirk of the two filters, and a quota warning with a link. It does not state auth/permission requirements explicitly, though the read-only nature is strongly implied by 'runs a report'.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with a one-line purpose and organized under headers, which is good. However, the identical multi-line 'Notes:' block about independent dimension_filter/metric_filter application is duplicated verbatim under both sections, wasting roughly ten lines, and the sheer length is disproportionate for routine calls.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 11-parameter tool with no output schema, the description covers return shape, the complex filter/ordering/date semantics, quota implications, and pagination (via the schema's limit/offset). What is missing is comparatively minor: auth/permission expectations and any note on maximum result scaling beyond the quota link.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 82%, so the baseline would be 3, but the description goes well beyond the schema with concrete worked examples for the four hardest parameters (date_ranges, dimension_filter, metric_filter, order_bys), including relative dates, AND/OR/NOT groups, and numeric/between filters. It also warns that order_bys fields must also appear in dimensions/metrics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Runs a Google Analytics Data API report', with illustrative metrics (sessions, users, page views, events, revenue). It does not distinguish itself from the siblings run_realtime_report, run_funnel_report, or run_conversions_report, so an agent must infer that this is the general-purpose reporting tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit routing guidance against the sibling report tools, only an implied 'general report' role. The description does give real operational guidance for a tricky case (that dimension_filter and metric_filter apply independently, with two workarounds and a preference for option (a) to conserve quota), which raises it above a bare 2.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
9 tool updates
v0.1.0- First observed
get_account_summaries - First observed
get_custom_dimensions_and_metrics - First observed
get_property_details - First observed
list_google_ads_links - First observed
list_property_annotations - First observed
run_conversions_report - First observed
run_funnel_report - First observed
run_realtime_report - First observed
run_report
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 gradedqualityBmaintenanceEnables querying Google Analytics 4 data through MCP tools for running reports, listing properties, getting real-time metrics, and exploring metadata.14 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
- AlicenseAqualityAmaintenanceEnables 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.1735 PyPIMIT