adloop 0.19.3__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.3 → adloop-0.19.4}/PKG-INFO +1 -1
  2. {adloop-0.19.3 → adloop-0.19.4}/pyproject.toml +1 -1
  3. {adloop-0.19.3 → adloop-0.19.4}/pyproject.toml.orig +1 -1
  4. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/server.py +55 -8
  5. {adloop-0.19.3 → adloop-0.19.4}/README.md +0 -0
  6. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/__init__.py +0 -0
  7. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/__main__.py +0 -0
  8. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/_mcp_patches.py +0 -0
  9. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/__init__.py +0 -0
  10. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/ai_max.py +0 -0
  11. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/brands.py +0 -0
  12. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/client.py +0 -0
  13. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/conversion_actions.py +0 -0
  14. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/currency.py +0 -0
  15. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/custom_conversion_goals.py +0 -0
  16. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/enums.py +0 -0
  17. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/forecast.py +0 -0
  18. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/gaql.py +0 -0
  19. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/image_fetch.py +0 -0
  20. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/pmax.py +0 -0
  21. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/read.py +0 -0
  22. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/validate_only.py +0 -0
  23. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ads/write.py +0 -0
  24. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/auth.py +0 -0
  25. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/cli.py +0 -0
  26. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/config.py +0 -0
  27. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/crossref.py +0 -0
  28. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/diagnostics.py +0 -0
  29. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ga4/__init__.py +0 -0
  30. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ga4/client.py +0 -0
  31. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ga4/reports.py +0 -0
  32. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ga4/tracking.py +0 -0
  33. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/ga4/write.py +0 -0
  34. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/gsc/__init__.py +0 -0
  35. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/gsc/client.py +0 -0
  36. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/gsc/reports.py +0 -0
  37. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/gtm/__init__.py +0 -0
  38. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/gtm/client.py +0 -0
  39. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/gtm/read.py +0 -0
  40. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/merchant/__init__.py +0 -0
  41. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/merchant/client.py +0 -0
  42. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/merchant/read.py +0 -0
  43. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/pagespeed.py +0 -0
  44. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/reddit/__init__.py +0 -0
  45. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/reddit/auth.py +0 -0
  46. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/reddit/client.py +0 -0
  47. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/reddit/read.py +0 -0
  48. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/reddit/schedule.py +0 -0
  49. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/reddit/write.py +0 -0
  50. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/rules/__init__.py +0 -0
  51. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/rules/adloop.md +0 -0
  52. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/rules/commands/analyze-performance.md +0 -0
  53. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/rules/commands/budget-plan.md +0 -0
  54. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/rules/commands/create-ad.md +0 -0
  55. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/rules/commands/create-campaign.md +0 -0
  56. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
  57. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/rules/commands/optimize-campaign.md +0 -0
  58. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/rules_install.py +0 -0
  59. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/runtime.py +0 -0
  60. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/safety/__init__.py +0 -0
  61. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/safety/audit.py +0 -0
  62. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/safety/guards.py +0 -0
  63. {adloop-0.19.3 → adloop-0.19.4}/src/adloop/safety/preview.py +0 -0
  64. {adloop-0.19.3 → 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.3
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.3"
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.3"
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,18 +168,63 @@ mcp = FastMCP(
166
168
  )
167
169
 
168
170
 
169
- def _tool(*, title: str, annotations: ToolAnnotations, **kwargs):
170
- """Register a tool with its title in both places MCP defines one.
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.
171
203
 
172
204
  The tool's own ``title`` is what clients display; ``annotations.title``
173
205
  is what directory reviews (Claude's connector portal) check. One title,
174
206
  copied into the shared annotation preset, keeps them from drifting.
175
- """
176
- return mcp.tool(
177
- title=title,
178
- annotations=annotations.model_copy(update={"title": title}),
179
- **kwargs,
180
- )
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
181
228
 
182
229
 
183
230
  def _reddit_structured_error(exc: Exception) -> dict | None:
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