adloop 0.22.0__tar.gz → 0.22.1__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.
- {adloop-0.22.0 → adloop-0.22.1}/PKG-INFO +1 -1
- {adloop-0.22.0 → adloop-0.22.1}/pyproject.toml +1 -1
- {adloop-0.22.0 → adloop-0.22.1}/pyproject.toml.orig +1 -1
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/rules/adloop.md +2 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/runtime.py +19 -1
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/server.py +54 -2
- {adloop-0.22.0 → adloop-0.22.1}/README.md +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/__init__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/__main__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/_mcp_patches.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/__init__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/ai_max.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/brands.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/client.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/conversion_actions.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/currency.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/custom_conversion_goals.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/enums.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/forecast.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/gaql.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/image_fetch.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/pmax.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/read.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/validate_only.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ads/write.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/auth.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/cli.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/config.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/crossref.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/diagnostics.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ga4/__init__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ga4/client.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ga4/reports.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ga4/tracking.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/ga4/write.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/gsc/__init__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/gsc/client.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/gsc/reports.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/gtm/__init__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/gtm/client.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/gtm/entities.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/gtm/read.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/gtm/write.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/merchant/__init__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/merchant/client.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/merchant/read.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/pagespeed.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/reddit/__init__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/reddit/auth.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/reddit/client.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/reddit/read.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/reddit/schedule.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/reddit/write.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/rules/__init__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/rules/commands/analyze-performance.md +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/rules/commands/budget-plan.md +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/rules/commands/create-ad.md +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/rules/commands/create-campaign.md +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/rules/commands/optimize-campaign.md +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/rules_install.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/safety/__init__.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/safety/audit.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/safety/guards.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/safety/preview.py +0 -0
- {adloop-0.22.0 → adloop-0.22.1}/src/adloop/tracking.py +0 -0
|
@@ -14,6 +14,8 @@ You have access to AdLoop MCP tools that connect Google Ads, Reddit Ads and Goog
|
|
|
14
14
|
|------|-------------|----------------|
|
|
15
15
|
| `health_check` | First thing to run when tools are failing — tests OAuth token, GA4, Ads, and (when configured) Reddit Ads connectivity | (none) |
|
|
16
16
|
|
|
17
|
+
**A tool you expect is missing?** Apps such as claude.ai, ChatGPT and Perplexity cache AdLoop's tool list from when the connection was made. `health_check` returns `adloop_version` and `tools_offered`, the tools this server offers to you. If the user asks for something a tool in `tools_offered` does and that tool isn't in your own tool list, don't improvise with other tools: tell the user their app shows an older list and to refresh the AdLoop connection (claude.ai: Settings → Connectors → AdLoop; ChatGPT: the AdLoop plugin's settings; Perplexity: remove and add the connector again; coding agents: reconnect the MCP server or start a new session).
|
|
18
|
+
|
|
17
19
|
**GA4 has two surfaces in health_check:** `ga4_admin` (listing properties) and `ga4_data` (every report). `ga4_data: error` with `SERVICE_DISABLED` means the Google Analytics Data API is not enabled in the Google Cloud project — tell the user to enable it; it is not a permissions or consent problem.
|
|
18
20
|
|
|
19
21
|
**If health_check reports Google auth errors:** Tell the user to delete `~/.adloop/token.json` and re-run any tool to trigger re-authorization. If tokens keep expiring weekly, the GCP consent screen needs to be published from "Testing" to "In production". **Reddit auth errors** (`REDDIT_INVALID_GRANT`, `REDDIT_NOT_CONNECTED`) are fixed by re-running the Reddit step of `adloop init` (self-hosted) or reconnecting under Settings → Reddit Ads (AdLoop Cloud); the Google token is unrelated.
|
|
@@ -26,7 +26,7 @@ from __future__ import annotations
|
|
|
26
26
|
import threading
|
|
27
27
|
from contextlib import contextmanager
|
|
28
28
|
from contextvars import ContextVar
|
|
29
|
-
from typing import TYPE_CHECKING, Iterator, Literal
|
|
29
|
+
from typing import TYPE_CHECKING, Callable, Iterable, Iterator, Literal
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
32
|
from adloop.config import AdLoopConfig
|
|
@@ -46,6 +46,24 @@ _default_config_lock = threading.Lock()
|
|
|
46
46
|
_deployment_mode: DeploymentMode = "local"
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
# Which registered tools the current caller is actually offered, given each
|
|
50
|
+
# tool's tags. Self-hosted, every enabled tool is; a hosted runtime narrows it
|
|
51
|
+
# per request (a key's toolsets, tools it cannot serve) so health_check can
|
|
52
|
+
# report the same list the caller's tools/list shows.
|
|
53
|
+
ToolVisibility = Callable[[dict[str, frozenset[str]]], Iterable[str]]
|
|
54
|
+
_tool_visibility: ToolVisibility | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def set_tool_visibility(fn: ToolVisibility | None) -> None:
|
|
58
|
+
"""Install the per-request tool filter. Called once at startup."""
|
|
59
|
+
global _tool_visibility
|
|
60
|
+
_tool_visibility = fn
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def tool_visibility() -> ToolVisibility | None:
|
|
64
|
+
return _tool_visibility
|
|
65
|
+
|
|
66
|
+
|
|
49
67
|
def deployment_mode() -> DeploymentMode:
|
|
50
68
|
"""Return the process-wide deployment mode ("local" or "server")."""
|
|
51
69
|
return _deployment_mode
|
|
@@ -9,7 +9,7 @@ from typing import Annotated, Callable
|
|
|
9
9
|
|
|
10
10
|
from fastmcp import FastMCP
|
|
11
11
|
from fastmcp.utilities.docstring_parsing import parse_docstring
|
|
12
|
-
from mcp.types import ToolAnnotations
|
|
12
|
+
from mcp.types import Icon, ToolAnnotations
|
|
13
13
|
from pydantic import BeforeValidator
|
|
14
14
|
|
|
15
15
|
from adloop import diagnostics
|
|
@@ -166,8 +166,22 @@ def _gtm_defaults(account_id: str, container_id: str) -> tuple[str, str]:
|
|
|
166
166
|
mcp = FastMCP(
|
|
167
167
|
"AdLoop",
|
|
168
168
|
instructions=_build_orchestration_instructions(),
|
|
169
|
+
website_url="https://getadloop.com",
|
|
170
|
+
# Clients that support MCP server icons show these instead of guessing
|
|
171
|
+
# from the domain's favicon. Square and full-bleed, so a client's own
|
|
172
|
+
# rounded mask leaves no stray corners.
|
|
173
|
+
icons=[
|
|
174
|
+
Icon(src="https://getadloop.com/icon-512.png", mimeType="image/png", sizes=["512x512"]),
|
|
175
|
+
Icon(src="https://getadloop.com/favicon.svg", mimeType="image/svg+xml", sizes=["any"]),
|
|
176
|
+
],
|
|
169
177
|
)
|
|
170
178
|
|
|
179
|
+
# name -> tags of every registered tool, so health_check can say which tools
|
|
180
|
+
# this server offers without reaching into FastMCP internals.
|
|
181
|
+
_TOOL_TAGS: dict[str, frozenset[str]] = {}
|
|
182
|
+
# Tags enabled by ADLOOP_TOOLSETS (None: the full catalog).
|
|
183
|
+
_ENABLED_TAGS: set[str] | None = None
|
|
184
|
+
|
|
171
185
|
|
|
172
186
|
# The API each toolset calls, linked at the end of every tool description:
|
|
173
187
|
# directory reviews ask descriptions to reference the API they target.
|
|
@@ -217,6 +231,7 @@ def _tool(*, title: str, annotations: ToolAnnotations, tags: set[str], **kwargs)
|
|
|
217
231
|
description = parse_docstring(fn).description or inspect.cleandoc(fn.__doc__ or "")
|
|
218
232
|
if docs and "https://" not in description:
|
|
219
233
|
description = f"{description}\n\n{docs}"
|
|
234
|
+
_TOOL_TAGS[kwargs.get("name") or fn.__name__] = frozenset(tags)
|
|
220
235
|
return mcp.tool(
|
|
221
236
|
title=title,
|
|
222
237
|
annotations=annotations,
|
|
@@ -585,9 +600,44 @@ def health_check() -> dict:
|
|
|
585
600
|
elif status.get("reddit") == "error" and status.get("reddit_hint"):
|
|
586
601
|
status["hint"] = status["reddit_hint"]
|
|
587
602
|
|
|
603
|
+
status["adloop_version"] = _adloop_version()
|
|
604
|
+
status["tools_offered"] = _offered_tool_names()
|
|
605
|
+
status["tools_note"] = (
|
|
606
|
+
"These are the tools this AdLoop server offers to you. If one of "
|
|
607
|
+
"them is missing from your own tool list, your app is showing a "
|
|
608
|
+
"cached list from an earlier version: ask the user to refresh the "
|
|
609
|
+
"AdLoop connection (claude.ai: Settings → Connectors → AdLoop; "
|
|
610
|
+
"ChatGPT: the AdLoop plugin's settings; Perplexity: remove and add "
|
|
611
|
+
"the connector again; coding agents: reconnect the MCP server or "
|
|
612
|
+
"start a new session)."
|
|
613
|
+
)
|
|
588
614
|
return status
|
|
589
615
|
|
|
590
616
|
|
|
617
|
+
def _adloop_version() -> str:
|
|
618
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
619
|
+
|
|
620
|
+
try:
|
|
621
|
+
return version("adloop")
|
|
622
|
+
except PackageNotFoundError:
|
|
623
|
+
return "unknown"
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _offered_tool_names() -> list[str]:
|
|
627
|
+
"""The tools the current caller sees in tools/list: enabled by
|
|
628
|
+
ADLOOP_TOOLSETS, then narrowed by the runtime's visibility hook."""
|
|
629
|
+
from adloop.runtime import tool_visibility
|
|
630
|
+
|
|
631
|
+
tools = {
|
|
632
|
+
name: tags
|
|
633
|
+
for name, tags in _TOOL_TAGS.items()
|
|
634
|
+
if _ENABLED_TAGS is None or tags & _ENABLED_TAGS
|
|
635
|
+
}
|
|
636
|
+
hook = tool_visibility()
|
|
637
|
+
names = hook(tools) if hook is not None else tools
|
|
638
|
+
return sorted(names)
|
|
639
|
+
|
|
640
|
+
|
|
591
641
|
# ---------------------------------------------------------------------------
|
|
592
642
|
# GA4 Read Tools
|
|
593
643
|
# ---------------------------------------------------------------------------
|
|
@@ -5728,7 +5778,9 @@ def _apply_toolsets_env() -> None:
|
|
|
5728
5778
|
f"ADLOOP_TOOLSETS names unknown toolset(s): {', '.join(unknown)}. "
|
|
5729
5779
|
f"Valid toolsets: {', '.join(TOOLSETS)}. Example: ADLOOP_TOOLSETS=ads,ga4"
|
|
5730
5780
|
)
|
|
5731
|
-
|
|
5781
|
+
global _ENABLED_TAGS
|
|
5782
|
+
_ENABLED_TAGS = requested | {"core"}
|
|
5783
|
+
mcp.enable(tags=_ENABLED_TAGS, only=True)
|
|
5732
5784
|
|
|
5733
5785
|
|
|
5734
5786
|
_apply_toolsets_env()
|
|
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
|
|
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
|
|
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
|
|
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
|