adloop 0.19.2__tar.gz → 0.19.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {adloop-0.19.2 → adloop-0.19.4}/PKG-INFO +1 -1
  2. {adloop-0.19.2 → adloop-0.19.4}/pyproject.toml +1 -1
  3. {adloop-0.19.2 → adloop-0.19.4}/pyproject.toml.orig +1 -1
  4. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/server.py +170 -109
  5. {adloop-0.19.2 → adloop-0.19.4}/README.md +0 -0
  6. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/__init__.py +0 -0
  7. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/__main__.py +0 -0
  8. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/_mcp_patches.py +0 -0
  9. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/__init__.py +0 -0
  10. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/ai_max.py +0 -0
  11. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/brands.py +0 -0
  12. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/client.py +0 -0
  13. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/conversion_actions.py +0 -0
  14. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/currency.py +0 -0
  15. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/custom_conversion_goals.py +0 -0
  16. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/enums.py +0 -0
  17. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/forecast.py +0 -0
  18. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/gaql.py +0 -0
  19. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/image_fetch.py +0 -0
  20. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/pmax.py +0 -0
  21. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/read.py +0 -0
  22. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/validate_only.py +0 -0
  23. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ads/write.py +0 -0
  24. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/auth.py +0 -0
  25. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/cli.py +0 -0
  26. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/config.py +0 -0
  27. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/crossref.py +0 -0
  28. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/diagnostics.py +0 -0
  29. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ga4/__init__.py +0 -0
  30. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ga4/client.py +0 -0
  31. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ga4/reports.py +0 -0
  32. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ga4/tracking.py +0 -0
  33. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/ga4/write.py +0 -0
  34. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/gsc/__init__.py +0 -0
  35. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/gsc/client.py +0 -0
  36. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/gsc/reports.py +0 -0
  37. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/gtm/__init__.py +0 -0
  38. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/gtm/client.py +0 -0
  39. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/gtm/read.py +0 -0
  40. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/merchant/__init__.py +0 -0
  41. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/merchant/client.py +0 -0
  42. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/merchant/read.py +0 -0
  43. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/pagespeed.py +0 -0
  44. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/reddit/__init__.py +0 -0
  45. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/reddit/auth.py +0 -0
  46. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/reddit/client.py +0 -0
  47. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/reddit/read.py +0 -0
  48. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/reddit/schedule.py +0 -0
  49. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/reddit/write.py +0 -0
  50. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/rules/__init__.py +0 -0
  51. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/rules/adloop.md +0 -0
  52. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/rules/commands/analyze-performance.md +0 -0
  53. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/rules/commands/budget-plan.md +0 -0
  54. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/rules/commands/create-ad.md +0 -0
  55. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/rules/commands/create-campaign.md +0 -0
  56. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
  57. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/rules/commands/optimize-campaign.md +0 -0
  58. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/rules_install.py +0 -0
  59. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/runtime.py +0 -0
  60. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/safety/__init__.py +0 -0
  61. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/safety/audit.py +0 -0
  62. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/safety/guards.py +0 -0
  63. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/safety/preview.py +0 -0
  64. {adloop-0.19.2 → adloop-0.19.4}/src/adloop/tracking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: adloop
3
- Version: 0.19.2
3
+ Version: 0.19.4
4
4
  Summary: The AI command center for Google Ads, Reddit Ads, GA4, and tracking code.
5
5
  Keywords: mcp,google-ads,reddit-ads,google-analytics,ga4,cursor,marketing
6
6
  Author: Daniel Klose
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.19.2"
3
+ version = "0.19.4"
4
4
  description = "The AI command center for Google Ads, Reddit Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.19.2"
3
+ version = "0.19.4"
4
4
  description = "The AI command center for Google Ads, Reddit Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -3,10 +3,12 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import functools
6
+ import inspect
6
7
  import json
7
8
  from typing import Annotated, Callable
8
9
 
9
10
  from fastmcp import FastMCP
11
+ from fastmcp.utilities.docstring_parsing import parse_docstring
10
12
  from mcp.types import ToolAnnotations
11
13
  from pydantic import BeforeValidator
12
14
 
@@ -166,6 +168,65 @@ mcp = FastMCP(
166
168
  )
167
169
 
168
170
 
171
+ # The API each toolset calls, linked at the end of every tool description:
172
+ # directory reviews ask descriptions to reference the API they target.
173
+ _API_DOCS: dict[str, str] = {
174
+ "ads": "Google Ads API: https://developers.google.com/google-ads/api/docs/start",
175
+ "ga4": (
176
+ "Google Analytics Data API: "
177
+ "https://developers.google.com/analytics/devguides/reporting/data/v1 "
178
+ "and Admin API: https://developers.google.com/analytics/devguides/config/admin/v1"
179
+ ),
180
+ "tracking": (
181
+ "Google Analytics Data API: "
182
+ "https://developers.google.com/analytics/devguides/reporting/data/v1 "
183
+ "and Google Ads API: https://developers.google.com/google-ads/api/docs/start"
184
+ ),
185
+ "gtm": "Tag Manager API: https://developers.google.com/tag-platform/tag-manager/api/v2",
186
+ "gsc": "Search Console API: https://developers.google.com/webmaster-tools",
187
+ "web": "PageSpeed Insights API: https://developers.google.com/speed/docs/insights/v5/about",
188
+ "merchant": "Merchant API: https://developers.google.com/merchant/api/overview",
189
+ "reddit": "Reddit Ads API: https://ads-api.reddit.com/docs/v3/",
190
+ # health_check and confirm_and_apply act on whichever platform is set up.
191
+ "core": (
192
+ "Google Ads API: https://developers.google.com/google-ads/api/docs/start, "
193
+ "Google Analytics Data API: "
194
+ "https://developers.google.com/analytics/devguides/reporting/data/v1 "
195
+ "and Reddit Ads API: https://ads-api.reddit.com/docs/v3/"
196
+ ),
197
+ }
198
+
199
+
200
+ def _tool(*, title: str, annotations: ToolAnnotations, tags: set[str], **kwargs):
201
+ """Register a tool with its title in both places MCP defines one, and
202
+ with a link to the API it calls.
203
+
204
+ The tool's own ``title`` is what clients display; ``annotations.title``
205
+ is what directory reviews (Claude's connector portal) check. One title,
206
+ copied into the shared annotation preset, keeps them from drifting.
207
+ Descriptions that already link their API (the free-form query tools,
208
+ which point at the query reference) keep their own link.
209
+ """
210
+ annotations = annotations.model_copy(update={"title": title})
211
+ docs = " ".join(_API_DOCS[tag] for tag in sorted(tags) if tag in _API_DOCS)
212
+
213
+ def register(fn):
214
+ # FastMCP's own parser: it moves an Args: section into the
215
+ # parameter schema, so the description must not repeat it.
216
+ description = parse_docstring(fn).description or inspect.cleandoc(fn.__doc__ or "")
217
+ if docs and "https://" not in description:
218
+ description = f"{description}\n\n{docs}"
219
+ return mcp.tool(
220
+ title=title,
221
+ annotations=annotations,
222
+ tags=tags,
223
+ description=description,
224
+ **kwargs,
225
+ )(fn)
226
+
227
+ return register
228
+
229
+
169
230
  def _reddit_structured_error(exc: Exception) -> dict | None:
170
231
  """Reddit-specific translations; keyed on exception type so Reddit's
171
232
  ``invalid_grant`` never gets the "Reconnect Google" hint below."""
@@ -400,7 +461,7 @@ def _safe(fn: Callable) -> Callable:
400
461
  # ---------------------------------------------------------------------------
401
462
 
402
463
 
403
- @mcp.tool(title="Connection health check", annotations=_READONLY, tags={"core"})
464
+ @_tool(title="Connection health check", annotations=_READONLY, tags={"core"})
404
465
  @_safe
405
466
  def health_check() -> dict:
406
467
  """Test AdLoop connectivity — checks OAuth token, GA4 API, Google Ads API,
@@ -531,7 +592,7 @@ def health_check() -> dict:
531
592
  # ---------------------------------------------------------------------------
532
593
 
533
594
 
534
- @mcp.tool(title="List Analytics properties", annotations=_READONLY, tags={"ga4"})
595
+ @_tool(title="List Analytics properties", annotations=_READONLY, tags={"ga4"})
535
596
  @_safe
536
597
  def get_account_summaries() -> dict:
537
598
  """List all GA4 accounts and properties accessible by the authenticated user.
@@ -544,7 +605,7 @@ def get_account_summaries() -> dict:
544
605
  return _impl(current_config())
545
606
 
546
607
 
547
- @mcp.tool(title="Analytics report", annotations=_READONLY, tags={"ga4"})
608
+ @_tool(title="Analytics report", annotations=_READONLY, tags={"ga4"})
548
609
  @_safe
549
610
  def run_ga4_report(
550
611
  dimensions: _StrListOpt = None,
@@ -577,7 +638,7 @@ def run_ga4_report(
577
638
  )
578
639
 
579
640
 
580
- @mcp.tool(title="Analytics realtime report", annotations=_READONLY, tags={"ga4"})
641
+ @_tool(title="Analytics realtime report", annotations=_READONLY, tags={"ga4"})
581
642
  @_safe
582
643
  def run_realtime_report(
583
644
  dimensions: _StrListOpt = None,
@@ -600,7 +661,7 @@ def run_realtime_report(
600
661
  )
601
662
 
602
663
 
603
- @mcp.tool(title="List Analytics events", annotations=_READONLY, tags={"ga4"})
664
+ @_tool(title="List Analytics events", annotations=_READONLY, tags={"ga4"})
604
665
  @_safe
605
666
  def get_tracking_events(
606
667
  date_range_start: str = "28daysAgo",
@@ -627,7 +688,7 @@ def get_tracking_events(
627
688
  # ---------------------------------------------------------------------------
628
689
 
629
690
 
630
- @mcp.tool(title="List Search Console properties", annotations=_READONLY, tags={"gsc"})
691
+ @_tool(title="List Search Console properties", annotations=_READONLY, tags={"gsc"})
631
692
  @_safe
632
693
  def list_gsc_sites() -> dict:
633
694
  """List all Google Search Console properties the authenticated user can access.
@@ -641,7 +702,7 @@ def list_gsc_sites() -> dict:
641
702
  return _impl(current_config())
642
703
 
643
704
 
644
- @mcp.tool(title="Search Console report", annotations=_READONLY, tags={"gsc"})
705
+ @_tool(title="Search Console report", annotations=_READONLY, tags={"gsc"})
645
706
  @_safe
646
707
  def run_gsc_report(
647
708
  site_url: str = "",
@@ -693,7 +754,7 @@ def run_gsc_report(
693
754
  # ---------------------------------------------------------------------------
694
755
 
695
756
 
696
- @mcp.tool(title="PageSpeed analysis", annotations=_READONLY, tags={"web"})
757
+ @_tool(title="PageSpeed analysis", annotations=_READONLY, tags={"web"})
697
758
  @_safe
698
759
  def analyze_page_speed(url: str, strategy: str = "mobile") -> dict:
699
760
  """Run PageSpeed Insights for a landing page — Lighthouse + real-user data.
@@ -712,7 +773,7 @@ def analyze_page_speed(url: str, strategy: str = "mobile") -> dict:
712
773
  return _impl(current_config(), url=url, strategy=strategy)
713
774
 
714
775
 
715
- @mcp.tool(
776
+ @_tool(
716
777
  title="List Merchant Center accounts",
717
778
  annotations=_READONLY,
718
779
  tags={"merchant"},
@@ -730,7 +791,7 @@ def list_merchant_accounts() -> dict:
730
791
  return _impl(current_config())
731
792
 
732
793
 
733
- @mcp.tool(title="Merchant Center feed health", annotations=_READONLY, tags={"merchant"})
794
+ @_tool(title="Merchant Center feed health", annotations=_READONLY, tags={"merchant"})
734
795
  @_safe
735
796
  def get_merchant_feed_health(account_id: str = "") -> dict:
736
797
  """Merchant Center feed health — disapproved products + account issues.
@@ -750,7 +811,7 @@ def get_merchant_feed_health(account_id: str = "") -> dict:
750
811
  return _impl(current_config(), account_id=account_id)
751
812
 
752
813
 
753
- @mcp.tool(title="List Google Ads accounts", annotations=_READONLY, tags={"ads"})
814
+ @_tool(title="List Google Ads accounts", annotations=_READONLY, tags={"ads"})
754
815
  @_safe
755
816
  def list_accounts(limit: int = 200) -> dict:
756
817
  """List accessible Google Ads accounts.
@@ -769,7 +830,7 @@ def list_accounts(limit: int = 200) -> dict:
769
830
  return _impl(current_config(), limit=limit)
770
831
 
771
832
 
772
- @mcp.tool(title="Campaign performance", annotations=_READONLY, tags={"ads"})
833
+ @_tool(title="Campaign performance", annotations=_READONLY, tags={"ads"})
773
834
  @_safe
774
835
  def get_campaign_performance(
775
836
  customer_id: str = "",
@@ -798,7 +859,7 @@ def get_campaign_performance(
798
859
  )
799
860
 
800
861
 
801
- @mcp.tool(title="Ad performance", annotations=_READONLY, tags={"ads"})
862
+ @_tool(title="Ad performance", annotations=_READONLY, tags={"ads"})
802
863
  @_safe
803
864
  def get_ad_performance(
804
865
  customer_id: str = "",
@@ -826,7 +887,7 @@ def get_ad_performance(
826
887
  )
827
888
 
828
889
 
829
- @mcp.tool(title="Keyword performance", annotations=_READONLY, tags={"ads"})
890
+ @_tool(title="Keyword performance", annotations=_READONLY, tags={"ads"})
830
891
  @_safe
831
892
  def get_keyword_performance(
832
893
  customer_id: str = "",
@@ -854,7 +915,7 @@ def get_keyword_performance(
854
915
  )
855
916
 
856
917
 
857
- @mcp.tool(title="Search terms report", annotations=_READONLY, tags={"ads"})
918
+ @_tool(title="Search terms report", annotations=_READONLY, tags={"ads"})
858
919
  @_safe
859
920
  def get_search_terms(
860
921
  customer_id: str = "",
@@ -882,7 +943,7 @@ def get_search_terms(
882
943
  )
883
944
 
884
945
 
885
- @mcp.tool(title="List negative keywords", annotations=_READONLY, tags={"ads"})
946
+ @_tool(title="List negative keywords", annotations=_READONLY, tags={"ads"})
886
947
  @_safe
887
948
  def get_negative_keywords(
888
949
  customer_id: str = "",
@@ -902,7 +963,7 @@ def get_negative_keywords(
902
963
  )
903
964
 
904
965
 
905
- @mcp.tool(
966
+ @_tool(
906
967
  title="List shared negative keyword lists",
907
968
  annotations=_READONLY,
908
969
  tags={"ads"},
@@ -922,7 +983,7 @@ def get_negative_keyword_lists(
922
983
  return _impl(current_config(), customer_id=customer_id or current_config().ads.customer_id)
923
984
 
924
985
 
925
- @mcp.tool(
986
+ @_tool(
926
987
  title="Keywords in a negative keyword list",
927
988
  annotations=_READONLY,
928
989
  tags={"ads"},
@@ -945,7 +1006,7 @@ def get_negative_keyword_list_keywords(
945
1006
  )
946
1007
 
947
1008
 
948
- @mcp.tool(
1009
+ @_tool(
949
1010
  title="Campaigns using a negative keyword list",
950
1011
  annotations=_READONLY,
951
1012
  tags={"ads"},
@@ -974,7 +1035,7 @@ def get_negative_keyword_list_campaigns(
974
1035
  # ---------------------------------------------------------------------------
975
1036
 
976
1037
 
977
- @mcp.tool(title="Google Ads recommendations", annotations=_READONLY, tags={"ads"})
1038
+ @_tool(title="Google Ads recommendations", annotations=_READONLY, tags={"ads"})
978
1039
  @_safe
979
1040
  def get_recommendations(
980
1041
  customer_id: str = "",
@@ -1003,7 +1064,7 @@ def get_recommendations(
1003
1064
  )
1004
1065
 
1005
1066
 
1006
- @mcp.tool(title="Performance Max performance", annotations=_READONLY, tags={"ads"})
1067
+ @_tool(title="Performance Max performance", annotations=_READONLY, tags={"ads"})
1007
1068
  @_safe
1008
1069
  def get_pmax_performance(
1009
1070
  customer_id: str = "",
@@ -1034,7 +1095,7 @@ def get_pmax_performance(
1034
1095
  )
1035
1096
 
1036
1097
 
1037
- @mcp.tool(
1098
+ @_tool(
1038
1099
  title="Performance Max asset performance",
1039
1100
  annotations=_READONLY,
1040
1101
  tags={"ads"},
@@ -1067,7 +1128,7 @@ def get_asset_performance(
1067
1128
  )
1068
1129
 
1069
1130
 
1070
- @mcp.tool(
1131
+ @_tool(
1071
1132
  title="Performance Max asset combinations",
1072
1133
  annotations=_READONLY,
1073
1134
  tags={"ads"},
@@ -1094,7 +1155,7 @@ def get_detailed_asset_performance(
1094
1155
  )
1095
1156
 
1096
1157
 
1097
- @mcp.tool(title="Audience performance", annotations=_READONLY, tags={"ads"})
1158
+ @_tool(title="Audience performance", annotations=_READONLY, tags={"ads"})
1098
1159
  @_safe
1099
1160
  def get_audience_performance(
1100
1161
  customer_id: str = "",
@@ -1125,7 +1186,7 @@ def get_audience_performance(
1125
1186
  )
1126
1187
 
1127
1188
 
1128
- @mcp.tool(title="Demographic targeting", annotations=_READONLY, tags={"ads"})
1189
+ @_tool(title="Demographic targeting", annotations=_READONLY, tags={"ads"})
1129
1190
  @_safe
1130
1191
  def get_demographic_targeting(
1131
1192
  ad_group_id: str = "",
@@ -1158,7 +1219,7 @@ def get_demographic_targeting(
1158
1219
  # ---------------------------------------------------------------------------
1159
1220
 
1160
1221
 
1161
- @mcp.tool(
1222
+ @_tool(
1162
1223
  title="Campaign conversions against Analytics",
1163
1224
  annotations=_READONLY,
1164
1225
  tags={"tracking"},
@@ -1192,7 +1253,7 @@ def analyze_campaign_conversions(
1192
1253
  )
1193
1254
 
1194
1255
 
1195
- @mcp.tool(title="Landing page analysis", annotations=_READONLY, tags={"tracking"})
1256
+ @_tool(title="Landing page analysis", annotations=_READONLY, tags={"tracking"})
1196
1257
  @_safe
1197
1258
  def landing_page_analysis(
1198
1259
  date_range_start: str = "",
@@ -1218,7 +1279,7 @@ def landing_page_analysis(
1218
1279
  )
1219
1280
 
1220
1281
 
1221
- @mcp.tool(title="Attribution check", annotations=_READONLY, tags={"tracking"})
1282
+ @_tool(title="Attribution check", annotations=_READONLY, tags={"tracking"})
1222
1283
  @_safe
1223
1284
  def attribution_check(
1224
1285
  date_range_start: str = "",
@@ -1249,7 +1310,7 @@ def attribution_check(
1249
1310
  )
1250
1311
 
1251
1312
 
1252
- @mcp.tool(title="Tracking coverage audit", annotations=_READONLY, tags={"gtm"})
1313
+ @_tool(title="Tracking coverage audit", annotations=_READONLY, tags={"gtm"})
1253
1314
  @_safe
1254
1315
  def audit_event_coverage(
1255
1316
  expected_events: list[str],
@@ -1302,7 +1363,7 @@ def audit_event_coverage(
1302
1363
  )
1303
1364
 
1304
1365
 
1305
- @mcp.tool(title="List Tag Manager accounts", annotations=_READONLY, tags={"gtm"})
1366
+ @_tool(title="List Tag Manager accounts", annotations=_READONLY, tags={"gtm"})
1306
1367
  @_safe
1307
1368
  def list_gtm_accounts() -> dict:
1308
1369
  """List all GTM accounts the AdLoop service account / OAuth user can read.
@@ -1317,7 +1378,7 @@ def list_gtm_accounts() -> dict:
1317
1378
  return _impl(current_config())
1318
1379
 
1319
1380
 
1320
- @mcp.tool(title="List Tag Manager containers", annotations=_READONLY, tags={"gtm"})
1381
+ @_tool(title="List Tag Manager containers", annotations=_READONLY, tags={"gtm"})
1321
1382
  @_safe
1322
1383
  def list_gtm_containers(gtm_account_id: str = "") -> dict:
1323
1384
  """List all containers under a GTM account.
@@ -1337,7 +1398,7 @@ def list_gtm_containers(gtm_account_id: str = "") -> dict:
1337
1398
  return _impl(current_config(), account_id=gtm_account_id)
1338
1399
 
1339
1400
 
1340
- @mcp.tool(title="List Tag Manager tags", annotations=_READONLY, tags={"gtm"})
1401
+ @_tool(title="List Tag Manager tags", annotations=_READONLY, tags={"gtm"})
1341
1402
  @_safe
1342
1403
  def list_gtm_tags(gtm_account_id: str = "", gtm_container_id: str = "") -> dict:
1343
1404
  """List every tag in the LIVE GTM container.
@@ -1357,7 +1418,7 @@ def list_gtm_tags(gtm_account_id: str = "", gtm_container_id: str = "") -> dict:
1357
1418
  )
1358
1419
 
1359
1420
 
1360
- @mcp.tool(title="Tag Manager tag details", annotations=_READONLY, tags={"gtm"})
1421
+ @_tool(title="Tag Manager tag details", annotations=_READONLY, tags={"gtm"})
1361
1422
  @_safe
1362
1423
  def get_gtm_tag(
1363
1424
  tag_id: str, gtm_account_id: str = "", gtm_container_id: str = ""
@@ -1382,7 +1443,7 @@ def get_gtm_tag(
1382
1443
  )
1383
1444
 
1384
1445
 
1385
- @mcp.tool(title="List Tag Manager triggers", annotations=_READONLY, tags={"gtm"})
1446
+ @_tool(title="List Tag Manager triggers", annotations=_READONLY, tags={"gtm"})
1386
1447
  @_safe
1387
1448
  def list_gtm_triggers(gtm_account_id: str = "", gtm_container_id: str = "") -> dict:
1388
1449
  """List every trigger in the LIVE GTM container.
@@ -1402,7 +1463,7 @@ def list_gtm_triggers(gtm_account_id: str = "", gtm_container_id: str = "") -> d
1402
1463
  )
1403
1464
 
1404
1465
 
1405
- @mcp.tool(title="Tag Manager trigger details", annotations=_READONLY, tags={"gtm"})
1466
+ @_tool(title="Tag Manager trigger details", annotations=_READONLY, tags={"gtm"})
1406
1467
  @_safe
1407
1468
  def get_gtm_trigger(
1408
1469
  trigger_id: str, gtm_account_id: str = "", gtm_container_id: str = ""
@@ -1427,7 +1488,7 @@ def get_gtm_trigger(
1427
1488
  )
1428
1489
 
1429
1490
 
1430
- @mcp.tool(title="List Tag Manager variables", annotations=_READONLY, tags={"gtm"})
1491
+ @_tool(title="List Tag Manager variables", annotations=_READONLY, tags={"gtm"})
1431
1492
  @_safe
1432
1493
  def list_gtm_variables(gtm_account_id: str = "", gtm_container_id: str = "") -> dict:
1433
1494
  """List GTM variables — both custom and enabled built-in.
@@ -1449,7 +1510,7 @@ def list_gtm_variables(gtm_account_id: str = "", gtm_container_id: str = "") ->
1449
1510
  )
1450
1511
 
1451
1512
 
1452
- @mcp.tool(title="List Tag Manager workspaces", annotations=_READONLY, tags={"gtm"})
1513
+ @_tool(title="List Tag Manager workspaces", annotations=_READONLY, tags={"gtm"})
1453
1514
  @_safe
1454
1515
  def list_gtm_workspaces(gtm_account_id: str = "", gtm_container_id: str = "") -> dict:
1455
1516
  """List workspaces (drafts) under a GTM container.
@@ -1469,7 +1530,7 @@ def list_gtm_workspaces(gtm_account_id: str = "", gtm_container_id: str = "") ->
1469
1530
  )
1470
1531
 
1471
1532
 
1472
- @mcp.tool(title="Tag Manager workspace changes", annotations=_READONLY, tags={"gtm"})
1533
+ @_tool(title="Tag Manager workspace changes", annotations=_READONLY, tags={"gtm"})
1473
1534
  @_safe
1474
1535
  def get_gtm_workspace_diff(
1475
1536
  workspace_id: str, gtm_account_id: str = "", gtm_container_id: str = ""
@@ -1496,7 +1557,7 @@ def get_gtm_workspace_diff(
1496
1557
  )
1497
1558
 
1498
1559
 
1499
- @mcp.tool(title="Tag Manager version history", annotations=_READONLY, tags={"gtm"})
1560
+ @_tool(title="Tag Manager version history", annotations=_READONLY, tags={"gtm"})
1500
1561
  @_safe
1501
1562
  def list_gtm_versions(
1502
1563
  gtm_account_id: str = "", gtm_container_id: str = "", page_size: int = 50
@@ -1522,7 +1583,7 @@ def list_gtm_versions(
1522
1583
  )
1523
1584
 
1524
1585
 
1525
- @mcp.tool(title="Tag Manager version details", annotations=_READONLY, tags={"gtm"})
1586
+ @_tool(title="Tag Manager version details", annotations=_READONLY, tags={"gtm"})
1526
1587
  @_safe
1527
1588
  def get_gtm_version(
1528
1589
  container_version_id: str, gtm_account_id: str = "", gtm_container_id: str = ""
@@ -1547,7 +1608,7 @@ def get_gtm_version(
1547
1608
  )
1548
1609
 
1549
1610
 
1550
- @mcp.tool(title="Custom Google Ads query", annotations=_READONLY, tags={"ads"})
1611
+ @_tool(title="Custom Google Ads query", annotations=_READONLY, tags={"ads"})
1551
1612
  @_safe
1552
1613
  def run_gaql(
1553
1614
  query: str,
@@ -1577,7 +1638,7 @@ def run_gaql(
1577
1638
  # ---------------------------------------------------------------------------
1578
1639
 
1579
1640
 
1580
- @mcp.tool(title="Draft a campaign", annotations=_WRITE, tags={"ads"})
1641
+ @_tool(title="Draft a campaign", annotations=_WRITE, tags={"ads"})
1581
1642
  @_safe
1582
1643
  def draft_campaign(
1583
1644
  campaign_name: str,
@@ -1644,7 +1705,7 @@ def draft_campaign(
1644
1705
  )
1645
1706
 
1646
1707
 
1647
- @mcp.tool(title="Draft an ad group", annotations=_WRITE, tags={"ads"})
1708
+ @_tool(title="Draft an ad group", annotations=_WRITE, tags={"ads"})
1648
1709
  @_safe
1649
1710
  def draft_ad_group(
1650
1711
  campaign_id: str,
@@ -1677,7 +1738,7 @@ def draft_ad_group(
1677
1738
  )
1678
1739
 
1679
1740
 
1680
- @mcp.tool(title="Draft campaign changes", annotations=_WRITE, tags={"ads"})
1741
+ @_tool(title="Draft campaign changes", annotations=_WRITE, tags={"ads"})
1681
1742
  @_safe
1682
1743
  def update_campaign(
1683
1744
  campaign_id: str,
@@ -1734,7 +1795,7 @@ def update_campaign(
1734
1795
  )
1735
1796
 
1736
1797
 
1737
- @mcp.tool(title="Draft a responsive search ad", annotations=_WRITE, tags={"ads"})
1798
+ @_tool(title="Draft a responsive search ad", annotations=_WRITE, tags={"ads"})
1738
1799
  @_safe
1739
1800
  def draft_responsive_search_ad(
1740
1801
  ad_group_id: str,
@@ -1777,7 +1838,7 @@ def draft_responsive_search_ad(
1777
1838
  )
1778
1839
 
1779
1840
 
1780
- @mcp.tool(title="Update a responsive search ad", annotations=_WRITE, tags={"ads"})
1841
+ @_tool(title="Update a responsive search ad", annotations=_WRITE, tags={"ads"})
1781
1842
  @_safe
1782
1843
  def update_responsive_search_ad(
1783
1844
  ad_id: str,
@@ -1836,7 +1897,7 @@ def update_responsive_search_ad(
1836
1897
  )
1837
1898
 
1838
1899
 
1839
- @mcp.tool(title="Draft keywords", annotations=_WRITE, tags={"ads"})
1900
+ @_tool(title="Draft keywords", annotations=_WRITE, tags={"ads"})
1840
1901
  @_safe
1841
1902
  def draft_keywords(
1842
1903
  ad_group_id: str,
@@ -1858,7 +1919,7 @@ def draft_keywords(
1858
1919
  )
1859
1920
 
1860
1921
 
1861
- @mcp.tool(title="Custom conversion goals", annotations=_READONLY, tags={"ads"})
1922
+ @_tool(title="Custom conversion goals", annotations=_READONLY, tags={"ads"})
1862
1923
  @_safe
1863
1924
  def get_custom_conversion_goals(
1864
1925
  campaign_id: str = "",
@@ -1887,7 +1948,7 @@ def get_custom_conversion_goals(
1887
1948
  )
1888
1949
 
1889
1950
 
1890
- @mcp.tool(
1951
+ @_tool(
1891
1952
  title="Draft a custom conversion goal",
1892
1953
  annotations=_WRITE,
1893
1954
  tags={"ads"},
@@ -1931,7 +1992,7 @@ def draft_custom_conversion_goal(
1931
1992
  )
1932
1993
 
1933
1994
 
1934
- @mcp.tool(
1995
+ @_tool(
1935
1996
  title="Draft changes to a custom conversion goal",
1936
1997
  annotations=_WRITE,
1937
1998
  tags={"ads"},
@@ -1969,7 +2030,7 @@ def draft_update_custom_conversion_goal(
1969
2030
  )
1970
2031
 
1971
2032
 
1972
- @mcp.tool(
2033
+ @_tool(
1973
2034
  title="Draft assigning a custom conversion goal",
1974
2035
  annotations=_WRITE,
1975
2036
  tags={"ads"},
@@ -2012,7 +2073,7 @@ def draft_assign_custom_conversion_goal(
2012
2073
  )
2013
2074
 
2014
2075
 
2015
- @mcp.tool(
2076
+ @_tool(
2016
2077
  title="Draft clearing a custom conversion goal",
2017
2078
  annotations=_WRITE,
2018
2079
  tags={"ads"},
@@ -2043,7 +2104,7 @@ def draft_clear_custom_conversion_goal(
2043
2104
  )
2044
2105
 
2045
2106
 
2046
- @mcp.tool(title="Draft negative keywords", annotations=_WRITE, tags={"ads"})
2107
+ @_tool(title="Draft negative keywords", annotations=_WRITE, tags={"ads"})
2047
2108
  @_safe
2048
2109
  def add_negative_keywords(
2049
2110
  campaign_id: str,
@@ -2068,7 +2129,7 @@ def add_negative_keywords(
2068
2129
  )
2069
2130
 
2070
2131
 
2071
- @mcp.tool(title="Draft negative locations", annotations=_WRITE, tags={"ads"})
2132
+ @_tool(title="Draft negative locations", annotations=_WRITE, tags={"ads"})
2072
2133
  @_safe
2073
2134
  def add_negative_locations(
2074
2135
  campaign_id: str,
@@ -2092,7 +2153,7 @@ def add_negative_locations(
2092
2153
  )
2093
2154
 
2094
2155
 
2095
- @mcp.tool(
2156
+ @_tool(
2096
2157
  title="Draft a shared negative keyword list",
2097
2158
  annotations=_WRITE,
2098
2159
  tags={"ads"},
@@ -2124,7 +2185,7 @@ def propose_negative_keyword_list(
2124
2185
  )
2125
2186
 
2126
2187
 
2127
- @mcp.tool(
2188
+ @_tool(
2128
2189
  title="Draft additions to a negative keyword list",
2129
2190
  annotations=_WRITE,
2130
2191
  tags={"ads"},
@@ -2161,7 +2222,7 @@ def add_to_negative_keyword_list(
2161
2222
  )
2162
2223
 
2163
2224
 
2164
- @mcp.tool(title="Draft attaching a shared set", annotations=_WRITE, tags={"ads"})
2225
+ @_tool(title="Draft attaching a shared set", annotations=_WRITE, tags={"ads"})
2165
2226
  @_safe
2166
2227
  def attach_shared_set_to_campaigns(
2167
2228
  shared_set_id: str,
@@ -2192,7 +2253,7 @@ def attach_shared_set_to_campaigns(
2192
2253
  )
2193
2254
 
2194
2255
 
2195
- @mcp.tool(title="Draft detaching a shared set", annotations=_WRITE, tags={"ads"})
2256
+ @_tool(title="Draft detaching a shared set", annotations=_WRITE, tags={"ads"})
2196
2257
  @_safe
2197
2258
  def detach_shared_set_from_campaigns(
2198
2259
  shared_set_id: str,
@@ -2223,7 +2284,7 @@ def detach_shared_set_from_campaigns(
2223
2284
  )
2224
2285
 
2225
2286
 
2226
- @mcp.tool(title="AI Max settings", annotations=_READONLY, tags={"ads"})
2287
+ @_tool(title="AI Max settings", annotations=_READONLY, tags={"ads"})
2227
2288
  @_safe
2228
2289
  def get_ai_max_settings(
2229
2290
  campaign_id: str = "",
@@ -2253,7 +2314,7 @@ def get_ai_max_settings(
2253
2314
  campaign_id=campaign_id,
2254
2315
  )
2255
2316
 
2256
- @mcp.tool(title="Draft AI Max settings", annotations=_WRITE, tags={"ads"})
2317
+ @_tool(title="Draft AI Max settings", annotations=_WRITE, tags={"ads"})
2257
2318
  @_safe
2258
2319
  def draft_ai_max_settings(
2259
2320
  campaign_id: str,
@@ -2318,7 +2379,7 @@ def draft_ai_max_settings(
2318
2379
  )
2319
2380
 
2320
2381
 
2321
- @mcp.tool(
2382
+ @_tool(
2322
2383
  title="Prepare a campaign for brand exclusions",
2323
2384
  annotations=_WRITE,
2324
2385
  tags={"ads"},
@@ -2361,7 +2422,7 @@ def draft_prepare_brand_exclusions(
2361
2422
  include_paused_ad_groups=include_paused_ad_groups,
2362
2423
  )
2363
2424
 
2364
- @mcp.tool(title="Draft demographic targeting", annotations=_WRITE, tags={"ads"})
2425
+ @_tool(title="Draft demographic targeting", annotations=_WRITE, tags={"ads"})
2365
2426
  @_safe
2366
2427
  def draft_demographic_targeting(
2367
2428
  customer_id: str = "",
@@ -2413,7 +2474,7 @@ def draft_demographic_targeting(
2413
2474
  )
2414
2475
 
2415
2476
 
2416
- @mcp.tool(title="Draft ad group changes", annotations=_WRITE, tags={"ads"})
2477
+ @_tool(title="Draft ad group changes", annotations=_WRITE, tags={"ads"})
2417
2478
  @_safe
2418
2479
  def update_ad_group(
2419
2480
  ad_group_id: str,
@@ -2433,7 +2494,7 @@ def update_ad_group(
2433
2494
  )
2434
2495
 
2435
2496
 
2436
- @mcp.tool(title="Draft callouts", annotations=_WRITE, tags={"ads"})
2497
+ @_tool(title="Draft callouts", annotations=_WRITE, tags={"ads"})
2437
2498
  @_safe
2438
2499
  def draft_callouts(
2439
2500
  campaign_id: str,
@@ -2451,7 +2512,7 @@ def draft_callouts(
2451
2512
  )
2452
2513
 
2453
2514
 
2454
- @mcp.tool(title="Draft structured snippets", annotations=_WRITE, tags={"ads"})
2515
+ @_tool(title="Draft structured snippets", annotations=_WRITE, tags={"ads"})
2455
2516
  @_safe
2456
2517
  def draft_structured_snippets(
2457
2518
  campaign_id: str,
@@ -2469,7 +2530,7 @@ def draft_structured_snippets(
2469
2530
  )
2470
2531
 
2471
2532
 
2472
- @mcp.tool(title="Draft image assets", annotations=_WRITE, tags={"ads"})
2533
+ @_tool(title="Draft image assets", annotations=_WRITE, tags={"ads"})
2473
2534
  @_safe
2474
2535
  def draft_image_assets(
2475
2536
  campaign_id: str,
@@ -2503,7 +2564,7 @@ def draft_image_assets(
2503
2564
  )
2504
2565
 
2505
2566
 
2506
- @mcp.tool(title="Draft pausing an entity", annotations=_WRITE, tags={"ads"})
2567
+ @_tool(title="Draft pausing an entity", annotations=_WRITE, tags={"ads"})
2507
2568
  @_safe
2508
2569
  def pause_entity(
2509
2570
  entity_type: str,
@@ -2531,7 +2592,7 @@ def pause_entity(
2531
2592
  )
2532
2593
 
2533
2594
 
2534
- @mcp.tool(title="Draft enabling an entity", annotations=_WRITE, tags={"ads"})
2595
+ @_tool(title="Draft enabling an entity", annotations=_WRITE, tags={"ads"})
2535
2596
  @_safe
2536
2597
  def enable_entity(
2537
2598
  entity_type: str,
@@ -2559,7 +2620,7 @@ def enable_entity(
2559
2620
  )
2560
2621
 
2561
2622
 
2562
- @mcp.tool(title="Draft removing an entity", annotations=_DESTRUCTIVE, tags={"ads"})
2623
+ @_tool(title="Draft removing an entity", annotations=_DESTRUCTIVE, tags={"ads"})
2563
2624
  @_safe
2564
2625
  def remove_entity(
2565
2626
  entity_type: str,
@@ -2595,7 +2656,7 @@ def remove_entity(
2595
2656
  )
2596
2657
 
2597
2658
 
2598
- @mcp.tool(title="Draft sitelinks", annotations=_WRITE, tags={"ads"})
2659
+ @_tool(title="Draft sitelinks", annotations=_WRITE, tags={"ads"})
2599
2660
  @_safe
2600
2661
  def draft_sitelinks(
2601
2662
  campaign_id: str,
@@ -2628,7 +2689,7 @@ def draft_sitelinks(
2628
2689
  )
2629
2690
 
2630
2691
 
2631
- @mcp.tool(title="Apply a previewed change", annotations=_DESTRUCTIVE, tags={"core"})
2692
+ @_tool(title="Apply a previewed change", annotations=_DESTRUCTIVE, tags={"core"})
2632
2693
  @_safe
2633
2694
  def confirm_and_apply(
2634
2695
  plan_id: str,
@@ -2681,7 +2742,7 @@ def _reddit_account(ad_account_id: str) -> str:
2681
2742
  return ad_account_id or current_config().reddit.ad_account_id
2682
2743
 
2683
2744
 
2684
- @mcp.tool(title="List Reddit ad accounts", annotations=_READONLY, tags={"reddit"})
2745
+ @_tool(title="List Reddit ad accounts", annotations=_READONLY, tags={"reddit"})
2685
2746
  @_safe
2686
2747
  def list_reddit_accounts() -> dict:
2687
2748
  """List Reddit businesses and ad accounts the connected Reddit user can access.
@@ -2695,7 +2756,7 @@ def list_reddit_accounts() -> dict:
2695
2756
  return _impl(current_config())
2696
2757
 
2697
2758
 
2698
- @mcp.tool(
2759
+ @_tool(
2699
2760
  title="Reddit billing + posting profiles", annotations=_READONLY, tags={"reddit"}
2700
2761
  )
2701
2762
  @_safe
@@ -2710,7 +2771,7 @@ def list_reddit_funding_instruments(ad_account_id: str = "") -> dict:
2710
2771
  return _impl(current_config(), ad_account_id=_reddit_account(ad_account_id))
2711
2772
 
2712
2773
 
2713
- @mcp.tool(title="Reddit campaigns", annotations=_READONLY, tags={"reddit"})
2774
+ @_tool(title="Reddit campaigns", annotations=_READONLY, tags={"reddit"})
2714
2775
  @_safe
2715
2776
  def get_reddit_campaigns(ad_account_id: str = "", include_archived: bool = False) -> dict:
2716
2777
  """List Reddit campaigns with status, objective, budget mode and bids.
@@ -2729,7 +2790,7 @@ def get_reddit_campaigns(ad_account_id: str = "", include_archived: bool = False
2729
2790
  )
2730
2791
 
2731
2792
 
2732
- @mcp.tool(title="Reddit ad groups", annotations=_READONLY, tags={"reddit"})
2793
+ @_tool(title="Reddit ad groups", annotations=_READONLY, tags={"reddit"})
2733
2794
  @_safe
2734
2795
  def get_reddit_ad_groups(ad_account_id: str = "", campaign_id: str = "") -> dict:
2735
2796
  """List Reddit ad groups (budget, bid, pixel, weekly schedule, targeting summary), optionally per campaign.
@@ -2746,7 +2807,7 @@ def get_reddit_ad_groups(ad_account_id: str = "", campaign_id: str = "") -> dict
2746
2807
  )
2747
2808
 
2748
2809
 
2749
- @mcp.tool(title="Reddit ads", annotations=_READONLY, tags={"reddit"})
2810
+ @_tool(title="Reddit ads", annotations=_READONLY, tags={"reddit"})
2750
2811
  @_safe
2751
2812
  def get_reddit_ads(
2752
2813
  ad_account_id: str = "",
@@ -2772,7 +2833,7 @@ def get_reddit_ads(
2772
2833
  )
2773
2834
 
2774
2835
 
2775
- @mcp.tool(title="Reddit performance", annotations=_READONLY, tags={"reddit"})
2836
+ @_tool(title="Reddit performance", annotations=_READONLY, tags={"reddit"})
2776
2837
  @_safe
2777
2838
  def get_reddit_performance(
2778
2839
  ad_account_id: str = "",
@@ -2808,7 +2869,7 @@ def get_reddit_performance(
2808
2869
  )
2809
2870
 
2810
2871
 
2811
- @mcp.tool(title="Raw Reddit report", annotations=_READONLY, tags={"reddit"})
2872
+ @_tool(title="Raw Reddit report", annotations=_READONLY, tags={"reddit"})
2812
2873
  @_safe
2813
2874
  def run_reddit_report(
2814
2875
  fields: _StrList,
@@ -2844,7 +2905,7 @@ def run_reddit_report(
2844
2905
  )
2845
2906
 
2846
2907
 
2847
- @mcp.tool(title="Reddit pixels", annotations=_READONLY, tags={"reddit"})
2908
+ @_tool(title="Reddit pixels", annotations=_READONLY, tags={"reddit"})
2848
2909
  @_safe
2849
2910
  def get_reddit_pixels(ad_account_id: str = "") -> dict:
2850
2911
  """Reddit pixels of the account and when each event (purchase, sign_up, lead, ...) last fired.
@@ -2858,7 +2919,7 @@ def get_reddit_pixels(ad_account_id: str = "") -> dict:
2858
2919
  return _impl(current_config(), ad_account_id=_reddit_account(ad_account_id))
2859
2920
 
2860
2921
 
2861
- @mcp.tool(title="Search Reddit targeting", annotations=_READONLY, tags={"reddit"})
2922
+ @_tool(title="Search Reddit targeting", annotations=_READONLY, tags={"reddit"})
2862
2923
  @_safe
2863
2924
  def search_reddit_targeting(
2864
2925
  kind: str, query: str = "", country: str = "", website_url: str = "", limit: int = 25
@@ -2880,7 +2941,7 @@ def search_reddit_targeting(
2880
2941
  )
2881
2942
 
2882
2943
 
2883
- @mcp.tool(title="Reddit account change history", annotations=_READONLY, tags={"reddit"})
2944
+ @_tool(title="Reddit account change history", annotations=_READONLY, tags={"reddit"})
2884
2945
  @_safe
2885
2946
  def get_reddit_account_history(
2886
2947
  ad_account_id: str = "",
@@ -2911,7 +2972,7 @@ def get_reddit_account_history(
2911
2972
  )
2912
2973
 
2913
2974
 
2914
- @mcp.tool(title="Estimate a Reddit ad group", annotations=_READONLY, tags={"reddit"})
2975
+ @_tool(title="Estimate a Reddit ad group", annotations=_READONLY, tags={"reddit"})
2915
2976
  @_safe
2916
2977
  def estimate_reddit_ad_group(
2917
2978
  daily_budget: float | None = None,
@@ -2971,7 +3032,7 @@ def estimate_reddit_ad_group(
2971
3032
  )
2972
3033
 
2973
3034
 
2974
- @mcp.tool(title="Draft pausing a Reddit entity", annotations=_WRITE, tags={"reddit"})
3035
+ @_tool(title="Draft pausing a Reddit entity", annotations=_WRITE, tags={"reddit"})
2975
3036
  @_safe
2976
3037
  def pause_reddit_entity(
2977
3038
  entity_type: str, entity_id: str, ad_account_id: str = ""
@@ -2991,7 +3052,7 @@ def pause_reddit_entity(
2991
3052
  )
2992
3053
 
2993
3054
 
2994
- @mcp.tool(title="Draft enabling a Reddit entity", annotations=_WRITE, tags={"reddit"})
3055
+ @_tool(title="Draft enabling a Reddit entity", annotations=_WRITE, tags={"reddit"})
2995
3056
  @_safe
2996
3057
  def enable_reddit_entity(
2997
3058
  entity_type: str, entity_id: str, ad_account_id: str = ""
@@ -3011,7 +3072,7 @@ def enable_reddit_entity(
3011
3072
  )
3012
3073
 
3013
3074
 
3014
- @mcp.tool(title="Draft archiving a Reddit entity", annotations=_DESTRUCTIVE, tags={"reddit"})
3075
+ @_tool(title="Draft archiving a Reddit entity", annotations=_DESTRUCTIVE, tags={"reddit"})
3015
3076
  @_safe
3016
3077
  def remove_reddit_entity(
3017
3078
  entity_type: str, entity_id: str, ad_account_id: str = ""
@@ -3032,7 +3093,7 @@ def remove_reddit_entity(
3032
3093
  )
3033
3094
 
3034
3095
 
3035
- @mcp.tool(title="Draft Reddit campaign changes", annotations=_WRITE, tags={"reddit"})
3096
+ @_tool(title="Draft Reddit campaign changes", annotations=_WRITE, tags={"reddit"})
3036
3097
  @_safe
3037
3098
  def update_reddit_campaign(
3038
3099
  campaign_id: str,
@@ -3080,7 +3141,7 @@ def update_reddit_campaign(
3080
3141
  )
3081
3142
 
3082
3143
 
3083
- @mcp.tool(title="Draft Reddit ad group changes", annotations=_WRITE, tags={"reddit"})
3144
+ @_tool(title="Draft Reddit ad group changes", annotations=_WRITE, tags={"reddit"})
3084
3145
  @_safe
3085
3146
  def update_reddit_ad_group(
3086
3147
  ad_group_id: str,
@@ -3151,7 +3212,7 @@ def update_reddit_ad_group(
3151
3212
  )
3152
3213
 
3153
3214
 
3154
- @mcp.tool(title="Draft Reddit ad changes", annotations=_WRITE, tags={"reddit"})
3215
+ @_tool(title="Draft Reddit ad changes", annotations=_WRITE, tags={"reddit"})
3155
3216
  @_safe
3156
3217
  def update_reddit_ad(
3157
3218
  ad_id: str,
@@ -3178,7 +3239,7 @@ def update_reddit_ad(
3178
3239
  )
3179
3240
 
3180
3241
 
3181
- @mcp.tool(title="Draft a Reddit campaign", annotations=_WRITE, tags={"reddit"})
3242
+ @_tool(title="Draft a Reddit campaign", annotations=_WRITE, tags={"reddit"})
3182
3243
  @_safe
3183
3244
  def draft_reddit_campaign(
3184
3245
  campaign_name: str,
@@ -3237,7 +3298,7 @@ def draft_reddit_campaign(
3237
3298
  )
3238
3299
 
3239
3300
 
3240
- @mcp.tool(title="Draft a Reddit ad group", annotations=_WRITE, tags={"reddit"})
3301
+ @_tool(title="Draft a Reddit ad group", annotations=_WRITE, tags={"reddit"})
3241
3302
  @_safe
3242
3303
  def draft_reddit_ad_group(
3243
3304
  campaign_id: str,
@@ -3318,7 +3379,7 @@ def draft_reddit_ad_group(
3318
3379
  )
3319
3380
 
3320
3381
 
3321
- @mcp.tool(title="Draft a Reddit ad", annotations=_WRITE, tags={"reddit"})
3382
+ @_tool(title="Draft a Reddit ad", annotations=_WRITE, tags={"reddit"})
3322
3383
  @_safe
3323
3384
  def draft_reddit_ad(
3324
3385
  ad_group_id: str,
@@ -3374,7 +3435,7 @@ def draft_reddit_ad(
3374
3435
  # ---------------------------------------------------------------------------
3375
3436
 
3376
3437
 
3377
- @mcp.tool(title="Draft an Analytics key event", annotations=_WRITE, tags={"ga4"})
3438
+ @_tool(title="Draft an Analytics key event", annotations=_WRITE, tags={"ga4"})
3378
3439
  @_safe
3379
3440
  def draft_key_event(
3380
3441
  event_name: str,
@@ -3399,7 +3460,7 @@ def draft_key_event(
3399
3460
  )
3400
3461
 
3401
3462
 
3402
- @mcp.tool(title="Draft a conversion action", annotations=_WRITE, tags={"ads"})
3463
+ @_tool(title="Draft a conversion action", annotations=_WRITE, tags={"ads"})
3403
3464
  @_safe
3404
3465
  def draft_create_conversion_action(
3405
3466
  name: str,
@@ -3452,7 +3513,7 @@ def draft_create_conversion_action(
3452
3513
  )
3453
3514
 
3454
3515
 
3455
- @mcp.tool(title="Draft conversion action changes", annotations=_WRITE, tags={"ads"})
3516
+ @_tool(title="Draft conversion action changes", annotations=_WRITE, tags={"ads"})
3456
3517
  @_safe
3457
3518
  def draft_update_conversion_action(
3458
3519
  conversion_action_id: str,
@@ -3499,7 +3560,7 @@ def draft_update_conversion_action(
3499
3560
  )
3500
3561
 
3501
3562
 
3502
- @mcp.tool(title="Draft removing a conversion action", annotations=_WRITE, tags={"ads"})
3563
+ @_tool(title="Draft removing a conversion action", annotations=_WRITE, tags={"ads"})
3503
3564
  @_safe
3504
3565
  def draft_remove_conversion_action(
3505
3566
  conversion_action_id: str,
@@ -3523,7 +3584,7 @@ def draft_remove_conversion_action(
3523
3584
  )
3524
3585
 
3525
3586
 
3526
- @mcp.tool(title="Validate tracking", annotations=_READONLY, tags={"tracking"})
3587
+ @_tool(title="Validate tracking", annotations=_READONLY, tags={"tracking"})
3527
3588
  @_safe
3528
3589
  def validate_tracking(
3529
3590
  expected_events: _StrList,
@@ -3551,7 +3612,7 @@ def validate_tracking(
3551
3612
  )
3552
3613
 
3553
3614
 
3554
- @mcp.tool(title="Generate tracking code", annotations=_READONLY, tags={"tracking"})
3615
+ @_tool(title="Generate tracking code", annotations=_READONLY, tags={"tracking"})
3555
3616
  @_safe
3556
3617
  def generate_tracking_code(
3557
3618
  event_name: str,
@@ -3586,7 +3647,7 @@ def generate_tracking_code(
3586
3647
  # ---------------------------------------------------------------------------
3587
3648
 
3588
3649
 
3589
- @mcp.tool(title="Budget forecast", annotations=_READONLY, tags={"ads"})
3650
+ @_tool(title="Budget forecast", annotations=_READONLY, tags={"ads"})
3590
3651
  @_safe
3591
3652
  def estimate_budget(
3592
3653
  keywords: _DictList,
@@ -3623,7 +3684,7 @@ def estimate_budget(
3623
3684
  )
3624
3685
 
3625
3686
 
3626
- @mcp.tool(title="Keyword ideas", annotations=_READONLY, tags={"ads"})
3687
+ @_tool(title="Keyword ideas", annotations=_READONLY, tags={"ads"})
3627
3688
  @_safe
3628
3689
  def discover_keywords(
3629
3690
  seed_keywords: _StrList = [], # noqa: B006 — mutable default required for MCP JSON schema
@@ -3671,7 +3732,7 @@ def discover_keywords(
3671
3732
  # ---------------------------------------------------------------------------
3672
3733
 
3673
3734
 
3674
- @mcp.tool(title="Suggest brands", annotations=_READONLY, tags={"ads"})
3735
+ @_tool(title="Suggest brands", annotations=_READONLY, tags={"ads"})
3675
3736
  @_safe
3676
3737
  def suggest_brands(
3677
3738
  brand_prefix: str,
@@ -3708,7 +3769,7 @@ def suggest_brands(
3708
3769
  )
3709
3770
 
3710
3771
 
3711
- @mcp.tool(title="Check brand names", annotations=_READONLY, tags={"ads"})
3772
+ @_tool(title="Check brand names", annotations=_READONLY, tags={"ads"})
3712
3773
  @_safe
3713
3774
  def check_brand_names(
3714
3775
  brand_names: _StrList,
@@ -3741,7 +3802,7 @@ def check_brand_names(
3741
3802
  customer_id=customer_id or current_config().ads.customer_id,
3742
3803
  )
3743
3804
 
3744
- @mcp.tool(title="List brand lists", annotations=_READONLY, tags={"ads"})
3805
+ @_tool(title="List brand lists", annotations=_READONLY, tags={"ads"})
3745
3806
  @_safe
3746
3807
  def get_brand_lists(
3747
3808
  customer_id: str = "",
@@ -3761,7 +3822,7 @@ def get_brand_lists(
3761
3822
  customer_id=customer_id or current_config().ads.customer_id,
3762
3823
  )
3763
3824
 
3764
- @mcp.tool(title="Brands in a brand list", annotations=_READONLY, tags={"ads"})
3825
+ @_tool(title="Brands in a brand list", annotations=_READONLY, tags={"ads"})
3765
3826
  @_safe
3766
3827
  def get_brand_list_brands(
3767
3828
  shared_set_id: str,
@@ -3785,7 +3846,7 @@ def get_brand_list_brands(
3785
3846
  shared_set_id=shared_set_id,
3786
3847
  )
3787
3848
 
3788
- @mcp.tool(title="Campaigns using a brand list", annotations=_READONLY, tags={"ads"})
3849
+ @_tool(title="Campaigns using a brand list", annotations=_READONLY, tags={"ads"})
3789
3850
  @_safe
3790
3851
  def get_brand_list_campaigns(
3791
3852
  shared_set_id: str = "",
@@ -3811,7 +3872,7 @@ def get_brand_list_campaigns(
3811
3872
  shared_set_id=shared_set_id,
3812
3873
  )
3813
3874
 
3814
- @mcp.tool(title="Draft a brand list", annotations=_WRITE, tags={"ads"})
3875
+ @_tool(title="Draft a brand list", annotations=_WRITE, tags={"ads"})
3815
3876
  @_safe
3816
3877
  def propose_brand_list(
3817
3878
  list_name: str,
@@ -3849,7 +3910,7 @@ def propose_brand_list(
3849
3910
  negative=negative,
3850
3911
  )
3851
3912
 
3852
- @mcp.tool(title="Draft additions to a brand list", annotations=_WRITE, tags={"ads"})
3913
+ @_tool(title="Draft additions to a brand list", annotations=_WRITE, tags={"ads"})
3853
3914
  @_safe
3854
3915
  def add_to_brand_list(
3855
3916
  shared_set_id: str,
@@ -3879,7 +3940,7 @@ def add_to_brand_list(
3879
3940
  brand_ids=brand_ids,
3880
3941
  )
3881
3942
 
3882
- @mcp.tool(title="Draft removing brands from a list", annotations=_DESTRUCTIVE, tags={"ads"})
3943
+ @_tool(title="Draft removing brands from a list", annotations=_DESTRUCTIVE, tags={"ads"})
3883
3944
  @_safe
3884
3945
  def remove_from_brand_list(
3885
3946
  shared_set_id: str,
@@ -3909,7 +3970,7 @@ def remove_from_brand_list(
3909
3970
  criterion_ids=criterion_ids,
3910
3971
  )
3911
3972
 
3912
- @mcp.tool(title="Draft attaching a brand list", annotations=_WRITE, tags={"ads"})
3973
+ @_tool(title="Draft attaching a brand list", annotations=_WRITE, tags={"ads"})
3913
3974
  @_safe
3914
3975
  def attach_brand_list_to_campaigns(
3915
3976
  shared_set_id: str,
@@ -3946,7 +4007,7 @@ def attach_brand_list_to_campaigns(
3946
4007
  negative=negative,
3947
4008
  )
3948
4009
 
3949
- @mcp.tool(title="Draft detaching a brand list", annotations=_DESTRUCTIVE, tags={"ads"})
4010
+ @_tool(title="Draft detaching a brand list", annotations=_DESTRUCTIVE, tags={"ads"})
3950
4011
  @_safe
3951
4012
  def detach_brand_list_from_campaigns(
3952
4013
  shared_set_id: str,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes