diff --git a/analytics_mcp/coordinator.py b/analytics_mcp/coordinator.py index 7d5274b..3528e82 100644 --- a/analytics_mcp/coordinator.py +++ b/analytics_mcp/coordinator.py @@ -45,6 +45,10 @@ from analytics_mcp.tools.reporting.metadata import ( get_custom_dimensions_and_metrics, ) +from analytics_mcp.tools.reporting.pivot import ( + run_pivot_report, + _run_pivot_report_description, +) run_report_with_description = FunctionTool(run_report) run_report_with_description.description = _run_report_description() @@ -52,6 +56,10 @@ run_realtime_report_with_description.description = ( _run_realtime_report_description() ) +run_pivot_report_with_description = FunctionTool(run_pivot_report) +run_pivot_report_with_description.description = ( + _run_pivot_report_description() +) # Instantiate the ADK tools tools = [ @@ -123,6 +131,14 @@ def sanitize_mcp_schema_properties(node: dict) -> None: ] elif tool.name == "run_realtime_report": tool.inputSchema["required"] = ["property_id", "dimensions", "metrics"] + elif tool.name == "run_pivot_report": + tool.inputSchema["required"] = [ + "property_id", + "date_ranges", + "dimensions", + "metrics", + "pivots", + ] @app.list_tools() diff --git a/analytics_mcp/tools/reporting/metadata.py b/analytics_mcp/tools/reporting/metadata.py index 22f5e13..c19ce44 100644 --- a/analytics_mcp/tools/reporting/metadata.py +++ b/analytics_mcp/tools/reporting/metadata.py @@ -237,6 +237,79 @@ def get_dimension_filter_hints(): """ + _FILTER_NOTES +def get_pivot_hints(): + """Returns hints and examples for pivots arguments.""" + pivot_by_country = data_v1beta.Pivot( + field_names=["country"], + limit=5, + order_bys=[ + data_v1beta.OrderBy( + metric=data_v1beta.OrderBy.MetricOrderBy( + metric_name="sessions", + ), + desc=True, + ) + ], + ) + pivot_by_browser = data_v1beta.Pivot( + field_names=["browser"], + limit=3, + offset=0, + ) + pivot_by_source_medium = data_v1beta.Pivot( + field_names=["sessionSource", "sessionMedium"], + limit=10, + order_bys=[ + data_v1beta.OrderBy( + dimension=data_v1beta.OrderBy.DimensionOrderBy( + dimension_name="sessionSource", + order_type=data_v1beta.OrderBy.DimensionOrderBy.OrderType.ALPHANUMERIC, + ), + desc=False, + ) + ], + ) + + return f"""Example pivots arguments: + + Each pivot in the `pivots` list must reference dimensions that are + also present in the report's `dimensions` argument. + + A pivot report can contain multiple pivots. Every dimension + referenced by one of the report's dimensions must be included in + exactly one of the pivots. + + 1. A single pivot by country, returning the top 5 by sessions: + [ {proto_to_json(pivot_by_country)} ] + + 2. A simple pivot by browser, returning the first 3 rows: + [ {proto_to_json(pivot_by_browser)} ] + + 3. A pivot by multiple dimensions (source + medium): + [ {proto_to_json(pivot_by_source_medium)} ] + + 4. Multiple pivots (e.g. rows pivot by country, columns pivot by + browser). Every report dimension must appear in exactly one + pivot: + [ + {proto_to_json(pivot_by_country)}, + {proto_to_json(pivot_by_browser)} + ] + + ### Pivot fields + + - `field_names`: List of dimension names to pivot on. Must be a + subset of the report's `dimensions`. + - `limit`: The number of unique combinations of dimension values + to return in this pivot. Required. The `limit` parameter is + required for each Pivot. + - `offset`: The row count of the start row for this pivot. + Defaults to 0. + - `order_bys`: Specifies how dimensions and metrics in this pivot + are ordered. Uses the same OrderBy format as `run_report`. + """ + + def get_order_bys_hints(): """Returns hints and examples for order_bys arguments.""" dimension_alphanumeric_ascending = data_v1beta.OrderBy( diff --git a/analytics_mcp/tools/reporting/pivot.py b/analytics_mcp/tools/reporting/pivot.py new file mode 100644 index 0000000..f6bafc6 --- /dev/null +++ b/analytics_mcp/tools/reporting/pivot.py @@ -0,0 +1,175 @@ +# Copyright 2025 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tools for running pivot reports using the Data API.""" + +from typing import Any, Dict, List + +from analytics_mcp.tools.reporting.metadata import ( + get_date_ranges_hints, + get_dimension_filter_hints, + get_metric_filter_hints, + get_pivot_hints, +) +from analytics_mcp.tools.utils import ( + construct_property_rn, + create_data_api_client, + proto_to_dict, +) +from google.analytics import data_v1beta + + +def _run_pivot_report_description() -> str: + """Returns the description for the `run_pivot_report` tool.""" + return f""" + {run_pivot_report.__doc__} + + ## 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 `pivots`: + {get_pivot_hints()} + + ### Hints for `date_ranges`: + {get_date_ranges_hints()} + + ### Hints for `dimension_filter`: + {get_dimension_filter_hints()} + + ### Hints for `metric_filter`: + {get_metric_filter_hints()} + + """ + + +async def run_pivot_report( + property_id: int | str, + date_ranges: List[Dict[str, Any]], + dimensions: List[str], + metrics: List[str], + pivots: List[Dict[str, Any]], + dimension_filter: Dict[str, Any] = None, + metric_filter: Dict[str, Any] = None, + currency_code: str = None, + return_property_quota: bool = False, +) -> Dict[str, Any]: + """Runs a Google Analytics Data API pivot report. + + Pivot reports are similar to pivot tables in spreadsheets. Pivots + reorganize the information in the report by rotating (pivoting) + your data on shared dimensions. For example, you can pivot on the + `country` dimension to create a column for each country, then + examine metric values for each country across a single row. + + See + https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/runPivotReport + for more information. + + 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. + pivots: A list of pivot definitions. Each pivot specifies which + dimensions from the report to include in the pivot table, as + well as how many rows and the sort order. The `field_names` + in each pivot must be a subset of the report's `dimensions`. + See + https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/Pivot + for the Pivot schema. + 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. + 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. + """ + request = data_v1beta.RunPivotReportRequest( + property=construct_property_rn(property_id), + dimensions=[ + data_v1beta.Dimension(name=dimension) + for dimension in dimensions + ], + metrics=[ + data_v1beta.Metric(name=metric) for metric in metrics + ], + date_ranges=[ + data_v1beta.DateRange(dr) for dr in date_ranges + ], + pivots=[data_v1beta.Pivot(pivot) for pivot in pivots], + return_property_quota=return_property_quota, + ) + + if dimension_filter: + request.dimension_filter = data_v1beta.FilterExpression( + dimension_filter + ) + + if metric_filter: + request.metric_filter = data_v1beta.FilterExpression( + metric_filter + ) + + if currency_code: + request.currency_code = currency_code + + response = await create_data_api_client().run_pivot_report(request) + + return proto_to_dict(response)