Skip to content

Commit c4c5c4b

Browse files
committed
feat: add run_funnel_report tool using Data API v1alpha
1 parent 1bd78c0 commit c4c5c4b

5 files changed

Lines changed: 389 additions & 7 deletions

File tree

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ to provide several
3636
### Run core reports 📙
3737

3838
- `run_report`: Runs a Google Analytics report using the Data API.
39+
- `run_funnel_report`: Runs a Google Analytics funnel report using the Data API.
3940
- `get_custom_dimensions_and_metrics`: Retrieves the custom dimensions and
4041
metrics for a specific property.
4142

@@ -67,8 +68,8 @@ Setup involves the following steps:
6768
[Follow the instructions](https://support.google.com/googleapi/answer/6158841)
6869
to enable the following APIs in your Google Cloud project:
6970

70-
* [Google Analytics Admin API](https://console.cloud.google.com/apis/library/analyticsadmin.googleapis.com)
71-
* [Google Analytics Data API](https://console.cloud.google.com/apis/library/analyticsdata.googleapis.com)
71+
- [Google Analytics Admin API](https://console.cloud.google.com/apis/library/analyticsadmin.googleapis.com)
72+
- [Google Analytics Data API](https://console.cloud.google.com/apis/library/analyticsdata.googleapis.com)
7273

7374
### Configure credentials 🔑
7475

@@ -137,10 +138,7 @@ Credentials saved to file: [PATH_TO_CREDENTIALS_JSON]
137138
"mcpServers": {
138139
"analytics-mcp": {
139140
"command": "pipx",
140-
"args": [
141-
"run",
142-
"analytics-mcp"
143-
],
141+
"args": ["run", "analytics-mcp"],
144142
"env": {
145143
"GOOGLE_APPLICATION_CREDENTIALS": "PATH_TO_CREDENTIALS_JSON",
146144
"GOOGLE_PROJECT_ID": "YOUR_PROJECT_ID"

analytics_mcp/coordinator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,21 @@
4545
from analytics_mcp.tools.reporting.metadata import (
4646
get_custom_dimensions_and_metrics,
4747
)
48+
from analytics_mcp.tools.reporting.funnel import (
49+
run_funnel_report,
50+
_run_funnel_report_description,
51+
)
4852

4953
run_report_with_description = FunctionTool(run_report)
5054
run_report_with_description.description = _run_report_description()
5155
run_realtime_report_with_description = FunctionTool(run_realtime_report)
5256
run_realtime_report_with_description.description = (
5357
_run_realtime_report_description()
5458
)
59+
run_funnel_report_with_description = FunctionTool(run_funnel_report)
60+
run_funnel_report_with_description.description = (
61+
_run_funnel_report_description()
62+
)
5563

5664
# Instantiate the ADK tools
5765
tools = [
@@ -62,6 +70,7 @@
6270
FunctionTool(get_custom_dimensions_and_metrics),
6371
run_report_with_description,
6472
run_realtime_report_with_description,
73+
run_funnel_report_with_description,
6574
]
6675

6776
tool_map = {t.name: t for t in tools}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Copyright 2025 Google LLC All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tools for running funnel reports using the Data API (Alpha)."""
16+
17+
from typing import Any, Dict, List
18+
19+
from analytics_mcp.tools.reporting.metadata import (
20+
get_date_ranges_hints,
21+
get_funnel_steps_hints,
22+
)
23+
from analytics_mcp.tools.utils import (
24+
construct_property_rn,
25+
create_data_api_alpha_client,
26+
proto_to_dict,
27+
)
28+
from google.analytics import data_v1alpha
29+
30+
31+
def _run_funnel_report_description() -> str:
32+
"""Returns the description for the `run_funnel_report` tool."""
33+
return f"""
34+
{run_funnel_report.__doc__}
35+
36+
## Hints for arguments
37+
38+
Here are some hints that outline the expected format and requirements
39+
for arguments.
40+
41+
### Hints for `funnel_breakdown`
42+
43+
The `funnel_breakdown` parameter allows you to segment funnel results by a dimension:
44+
```json
45+
{{
46+
"breakdown_dimension": "deviceCategory"
47+
}}
48+
```
49+
Common breakdown dimensions include:
50+
- `deviceCategory` - Desktop, Mobile, Tablet
51+
- `country` - User's country
52+
- `operatingSystem` - User's operating system
53+
- `browser` - User's browser
54+
55+
### Hints for `funnel_next_action`
56+
57+
The `funnel_next_action` parameter analyzes what users do after completing or dropping off from the funnel:
58+
```json
59+
{{
60+
"next_action_dimension": "eventName",
61+
"limit": 5
62+
}}
63+
```
64+
Common next action dimensions include:
65+
- `eventName` - Next events users trigger
66+
- `pagePath` - Next pages users visit
67+
68+
### Hints for `segments`
69+
70+
The `segments` parameter allows you to segment funnel results by user criteria.
71+
Each segment is a dictionary passed directly to `data_v1alpha.Segment()`.
72+
See https://developers.google.com/analytics/devguides/reporting/data/v1/funnels#segments
73+
for details and examples.
74+
75+
### Hints for `date_ranges`:
76+
{get_date_ranges_hints()}
77+
78+
### Hints for `funnel_steps`
79+
{get_funnel_steps_hints()}
80+
81+
"""
82+
83+
84+
async def run_funnel_report(
85+
property_id: int | str,
86+
funnel_steps: List[Dict[str, Any]],
87+
date_ranges: List[Dict[str, str]] = None,
88+
funnel_breakdown: Dict[str, str] = None,
89+
funnel_next_action: Dict[str, str] = None,
90+
segments: List[Dict[str, Any]] = None,
91+
return_property_quota: bool = False,
92+
) -> Dict[str, Any]:
93+
"""Run a Google Analytics Data API funnel report.
94+
95+
See the funnel report guide at
96+
https://developers.google.com/analytics/devguides/reporting/data/v1/funnels
97+
for details and examples.
98+
99+
Args:
100+
property_id: The Google Analytics property ID. Accepted formats are:
101+
- A number
102+
- A string consisting of 'properties/' followed by a number
103+
funnel_steps: A list of funnel steps. Each step should be a dictionary
104+
containing:
105+
- 'name': (str) Display name for the step
106+
- 'filter_expression': (Dict) Complete filter expression for the step
107+
OR for simple event-based steps:
108+
- 'name': (str) Display name for the step
109+
- 'event': (str) Event name to filter on
110+
date_ranges: A list of date ranges
111+
(https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/DateRange)
112+
to include in the report.
113+
funnel_breakdown: Optional breakdown dimension to segment the funnel.
114+
This creates separate funnel results for each value of the dimension.
115+
Example: {"breakdown_dimension": "deviceCategory"}
116+
funnel_next_action: Optional next action analysis configuration.
117+
This analyzes what users do after completing or dropping off from
118+
the funnel.
119+
Example: {"next_action_dimension": "eventName", "limit": 5}
120+
segments: Optional list of segments to apply to the funnel.
121+
return_property_quota: Whether to return current property quota
122+
information.
123+
124+
Returns:
125+
Dict containing the funnel report response with funnel results
126+
including:
127+
- funnel_table: Table showing progression through funnel steps
128+
- funnel_visualization: Data for visualizing the funnel
129+
- property_quota: (if requested) Current quota usage information
130+
131+
Raises:
132+
ValueError: If funnel_steps is empty or contains invalid configurations
133+
Exception: If the API request fails
134+
"""
135+
if not funnel_steps:
136+
raise ValueError("funnel_steps must contain at least one step")
137+
138+
steps = []
139+
for i, step in enumerate(funnel_steps):
140+
if not isinstance(step, dict):
141+
raise ValueError(f"Step {i+1} must be a dictionary")
142+
143+
step_name = step.get("name", f"Step {i+1}")
144+
145+
if "filter_expression" in step:
146+
filter_expr = data_v1alpha.FunnelFilterExpression(
147+
step["filter_expression"]
148+
)
149+
elif "event" in step:
150+
filter_expr = data_v1alpha.FunnelFilterExpression(
151+
funnel_event_filter=data_v1alpha.FunnelEventFilter(
152+
event_name=step["event"]
153+
)
154+
)
155+
else:
156+
raise ValueError(
157+
f"Step {i+1} must contain either 'filter_expression' or"
158+
" 'event' key"
159+
)
160+
161+
funnel_step = data_v1alpha.FunnelStep(
162+
name=step_name, filter_expression=filter_expr
163+
)
164+
steps.append(funnel_step)
165+
166+
request = data_v1alpha.RunFunnelReportRequest(
167+
property=construct_property_rn(property_id),
168+
funnel=data_v1alpha.Funnel(steps=steps),
169+
date_ranges=[data_v1alpha.DateRange(dr) for dr in (date_ranges or [])],
170+
return_property_quota=return_property_quota,
171+
)
172+
173+
if funnel_breakdown and "breakdown_dimension" in funnel_breakdown:
174+
request.funnel_breakdown = data_v1alpha.FunnelBreakdown(
175+
breakdown_dimension=data_v1alpha.Dimension(
176+
name=funnel_breakdown["breakdown_dimension"]
177+
)
178+
)
179+
180+
if funnel_next_action and "next_action_dimension" in funnel_next_action:
181+
next_action_config = data_v1alpha.FunnelNextAction(
182+
next_action_dimension=data_v1alpha.Dimension(
183+
name=funnel_next_action["next_action_dimension"]
184+
)
185+
)
186+
if "limit" in funnel_next_action:
187+
next_action_config.limit = funnel_next_action["limit"]
188+
request.funnel_next_action = next_action_config
189+
190+
if segments:
191+
request.segments = [
192+
data_v1alpha.Segment(segment) for segment in segments
193+
]
194+
195+
response = await create_data_api_alpha_client().run_funnel_report(request)
196+
return proto_to_dict(response)

0 commit comments

Comments
 (0)