From 161c17d21c291503314ed2287f8f4d4a8dba95e1 Mon Sep 17 00:00:00 2001 From: Joel Horwitz Date: Thu, 1 Oct 2026 16:24:46 -0700 Subject: [PATCH 1/4] feat(mcp): OAuth-only remote at mcp.synterai.com for Cursor and Claude Drops the X-Synter-Key header and SYNTER_API_KEY variable; validator now enforces OAuth-only on both manifests. --- .cursor-plugin/plugin.json | 12 +----------- .mcp.json | 4 ++-- mcp.json | 5 +---- scripts/validate-manifests.js | 17 ++++++++++++----- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index ea3bd58..3747680 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -29,15 +29,5 @@ "skills": "./skills/", "agents": "./agents/", "rules": "./rules/", - "mcpServers": "./mcp.json", - "variables": { - "type": "object", - "properties": { - "SYNTER_API_KEY": { - "type": "string", - "title": "Synter API key", - "description": "Optional for onboarding. Paste your syn_... key from syntermedia.ai/developer to use connected ad accounts. Leave blank to run /synter:quickstart and the free GA4 tools." - } - } - } + "mcpServers": "./mcp.json" } diff --git a/.mcp.json b/.mcp.json index 91f0211..ce659fb 100644 --- a/.mcp.json +++ b/.mcp.json @@ -2,8 +2,8 @@ "mcpServers": { "synter": { "type": "http", - "url": "https://mcp.syntermedia.ai", - "description": "Synter Advertising Platform MCP \u2014 cross-channel ad management for AI agents. Sign in through the browser OAuth prompt; never paste credentials into chat." + "url": "https://mcp.synterai.com", + "description": "Synter Advertising Platform MCP — cross-channel ad management for AI agents. Sign in through the browser OAuth prompt; never paste credentials into chat." } } } diff --git a/mcp.json b/mcp.json index daa437e..b865a6c 100644 --- a/mcp.json +++ b/mcp.json @@ -2,10 +2,7 @@ "mcpServers": { "synter": { "type": "http", - "url": "https://mcp.syntermedia.ai", - "headers": { - "X-Synter-Key": "${SYNTER_API_KEY}" - } + "url": "https://mcp.synterai.com" } } } diff --git a/scripts/validate-manifests.js b/scripts/validate-manifests.js index ec87308..5ac73da 100644 --- a/scripts/validate-manifests.js +++ b/scripts/validate-manifests.js @@ -112,10 +112,10 @@ if (fs.existsSync(SKILLS_DIR)) { } // (d) Authentication guards — Claude's public directory requires OAuth for -// authenticated remote MCP services. Cursor keeps its supported API-key path. +// authenticated remote MCP services. Cursor uses the same OAuth-only remote. const claudeMcp = manifests['.mcp.json']; const claudeServer = claudeMcp && claudeMcp.mcpServers && claudeMcp.mcpServers.synter; -if (!claudeServer || claudeServer.type !== 'http' || claudeServer.url !== 'https://mcp.syntermedia.ai') { +if (!claudeServer || claudeServer.type !== 'http' || claudeServer.url !== 'https://mcp.synterai.com') { errors.push('.mcp.json: Synter must use the production HTTPS remote MCP endpoint'); } if (claudeServer && claudeServer.headers) { @@ -125,9 +125,16 @@ if (pluginJson && pluginJson.userConfig && pluginJson.userConfig.synter_api_key) errors.push('.claude-plugin/plugin.json: Claude plugin must not collect a static Synter API key'); } -const mcpJson = readFile('mcp.json'); -if (!mcpJson.includes('SYNTER_API_KEY')) { - errors.push('mcp.json: missing required literal "SYNTER_API_KEY"'); +const cursorMcp = manifests['mcp.json']; +const cursorServer = cursorMcp && cursorMcp.mcpServers && cursorMcp.mcpServers.synter; +if (!cursorServer || cursorServer.type !== 'http' || cursorServer.url !== 'https://mcp.synterai.com') { + errors.push('mcp.json: Synter must use the production HTTPS remote MCP endpoint'); +} +if (cursorServer && cursorServer.headers) { + errors.push('mcp.json: Cursor plugin must use browser OAuth, not static request headers'); +} +if (manifests['.cursor-plugin/plugin.json'] && manifests['.cursor-plugin/plugin.json'].variables) { + errors.push('.cursor-plugin/plugin.json: Cursor plugin must not collect a static Synter API key'); } if (errors.length > 0) { From 89c24e0b3a4e1c6453f764311e80b546069fb1c6 Mon Sep 17 00:00:00 2001 From: Joel Horwitz Date: Thu, 1 Oct 2026 20:38:50 -0700 Subject: [PATCH 2/4] feat: scope OAuth plugins to approved directory tools --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .cursor-plugin/marketplace.json | 2 +- .cursor-plugin/plugin.json | 2 +- .github/workflows/directory-tools.yml | 24 ++ .github/workflows/release.yml | 6 + .github/workflows/validate.yml | 2 - .mcp.json | 4 +- CHANGELOG.md | 38 +-- CLAUDE_COMMUNITY_SUBMISSION.md | 21 +- README.md | 228 ++++-------------- SECURITY.md | 7 +- agents/audience-builder.md | 6 +- agents/budget-optimizer.md | 6 +- agents/campaign-strategist.md | 2 +- agents/creative-director.md | 10 +- agents/media-buyer.md | 8 +- agents/performance-analyst.md | 37 +-- agents/visibility-agent.md | 20 -- context/brand-and-safety.md | 11 +- output-styles/synter.md | 4 +- rules/synter-operator.mdc | 6 +- scripts/check-directory-tools.js | 170 +++++++++++++ scripts/check-directory-tools.test.js | 114 +++++++++ scripts/session-context.sh | 4 +- scripts/validate-manifests.js | 52 ++-- sdk/README.md | 50 +--- sdk/synter-agent.mjs | 50 ++-- skills/ad-copy-generation/SKILL.md | 2 +- skills/ad-policy-compliance/SKILL.md | 54 +---- skills/agentic-loop-campaign-sandbox/SKILL.md | 67 ----- skills/anomaly-detector/SKILL.md | 6 +- skills/attribution/SKILL.md | 2 +- skills/audience-expansion-strategy/SKILL.md | 14 +- skills/audience/SKILL.md | 36 +-- skills/bid-optimization/SKILL.md | 4 +- skills/campaign-preflight/SKILL.md | 10 +- skills/campaign-structure-auditor/SKILL.md | 2 +- skills/competitor-analysis/SKILL.md | 4 +- skills/connect/SKILL.md | 33 +-- skills/conversion-tracking/SKILL.md | 34 +-- skills/creative-testing/SKILL.md | 6 +- skills/creative/SKILL.md | 38 --- skills/dayparting/SKILL.md | 4 +- skills/executive-reporting/SKILL.md | 4 +- skills/first-party-data-strategy/SKILL.md | 14 +- skills/geo-integrity-audit/SKILL.md | 83 +------ skills/google-ads-quality-score/SKILL.md | 8 +- skills/google-shopping-optimizer/SKILL.md | 4 +- skills/help/SKILL.md | 46 +--- skills/incrementality-testing/SKILL.md | 4 +- skills/keyword-research/SKILL.md | 8 +- skills/kill-scale-rules/SKILL.md | 2 +- skills/landing-page-creation/SKILL.md | 44 ---- skills/launch/SKILL.md | 10 +- skills/linkedin-ads-targeting/SKILL.md | 14 +- skills/llm-visibility-audit/SKILL.md | 143 ----------- skills/media-plan/SKILL.md | 2 +- skills/meta-ads-diagnostics/SKILL.md | 6 +- skills/mmm-budget-planner/SKILL.md | 8 +- skills/negative-keywords/SKILL.md | 4 +- skills/optimize/SKILL.md | 8 +- skills/performance-max-optimizer/SKILL.md | 14 +- skills/pixel-capi-auditor/SKILL.md | 12 +- skills/platform-amazon/SKILL.md | 6 +- skills/platform-benchmarks/SKILL.md | 4 +- skills/platform-google/SKILL.md | 10 +- skills/platform-linkedin/SKILL.md | 12 +- skills/platform-meta/SKILL.md | 10 +- skills/platform-microsoft/SKILL.md | 8 +- skills/platform-openai/SKILL.md | 8 +- skills/platform-reddit/SKILL.md | 8 +- skills/platform-tiktok/SKILL.md | 10 +- skills/platform-x/SKILL.md | 8 +- skills/pre-pause-analysis/SKILL.md | 4 +- skills/quickstart/SKILL.md | 52 +--- skills/replicate/SKILL.md | 12 +- skills/report/SKILL.md | 6 +- skills/retargeting-sequence-designer/SKILL.md | 14 +- skills/roas-calculator/SKILL.md | 2 +- skills/seasonal-budget-planner/SKILL.md | 4 +- skills/ugc-creative-brief/SKILL.md | 68 ------ skills/utm-builder/SKILL.md | 2 +- skills/video-ad-scriptwriter/SKILL.md | 6 +- tools/directory-tools.json | 36 +++ 85 files changed, 741 insertions(+), 1191 deletions(-) create mode 100644 .github/workflows/directory-tools.yml delete mode 100644 agents/visibility-agent.md create mode 100644 scripts/check-directory-tools.js create mode 100644 scripts/check-directory-tools.test.js delete mode 100644 skills/agentic-loop-campaign-sandbox/SKILL.md delete mode 100644 skills/creative/SKILL.md delete mode 100644 skills/landing-page-creation/SKILL.md delete mode 100644 skills/llm-visibility-audit/SKILL.md delete mode 100644 skills/ugc-creative-brief/SKILL.md create mode 100644 tools/directory-tools.json diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index ccb1a17..58685da 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ { "name": "synter", "source": "./", - "description": "Connect every ad platform, build audiences from signals, generate on-brand creative, launch cross-platform campaigns, and reallocate spend by ROAS — one conversation, every platform. You direct, the agents execute." + "description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution." } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index bb9fc01..dfe7174 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "synter", "displayName": "Synter", - "description": "The AI Agent Operator for Ads. Connect every ad platform, build audiences from signals and first-party data, generate on-brand creative, launch cross-platform campaigns, and reallocate spend by ROAS — all in one conversation. You direct, the agents execute. Nothing ships without your approval.", + "description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution.", "version": "1.1.2", "license": "MIT", "author": { diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index b41fc54..56d10c0 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -13,7 +13,7 @@ "name": "synter", "source": ".", "logo": "assets/logo.png", - "description": "The AI Agent Operator for Ads. Connect 21 ad platforms, build audiences, generate on-brand creative, launch cross-platform campaigns, and reallocate spend by ROAS — one conversation, every platform. You direct, the agents execute. Nothing that spends money ships without your approval." + "description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution." } ] } diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 3747680..5042b7c 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -3,7 +3,7 @@ "name": "synter", "displayName": "Synter", "version": "1.1.2", - "description": "The AI Agent Operator for Ads. Connect 21 ad platforms, build audiences, generate on-brand creative, launch cross-platform campaigns, and reallocate spend by ROAS — one conversation, every platform. You direct, the agents execute. Nothing that spends money ships without your approval.", + "description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution.", "author": { "name": "Synter", "email": "joel@synterai.com" diff --git a/.github/workflows/directory-tools.yml b/.github/workflows/directory-tools.yml new file mode 100644 index 0000000..c2835e3 --- /dev/null +++ b/.github/workflows/directory-tools.yml @@ -0,0 +1,24 @@ +name: Directory tools + +on: + pull_request: + push: + branches: + - main + - feat/mcp-oauth-only-synterai + +permissions: + contents: read + +jobs: + directory-tools: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Check instruction tool references + run: node scripts/check-directory-tools.js + - name: Test extraction and live MCP comparison + run: node --test scripts/check-directory-tools.test.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 313dacf..7b5c7dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,12 @@ jobs: - name: Run manifest validation run: node scripts/validate-manifests.js + - name: Validate directory tool references + run: node scripts/check-directory-tools.js + + - name: Test directory validator + run: node --test scripts/check-directory-tools.test.js + - name: Run strict Claude plugin validation run: npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7bf6ff4..cedc124 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -2,8 +2,6 @@ name: Validate on: pull_request: - branches: - - main jobs: validate-manifests: diff --git a/.mcp.json b/.mcp.json index ce659fb..91f0211 100644 --- a/.mcp.json +++ b/.mcp.json @@ -2,8 +2,8 @@ "mcpServers": { "synter": { "type": "http", - "url": "https://mcp.synterai.com", - "description": "Synter Advertising Platform MCP — cross-channel ad management for AI agents. Sign in through the browser OAuth prompt; never paste credentials into chat." + "url": "https://mcp.syntermedia.ai", + "description": "Synter Advertising Platform MCP \u2014 cross-channel ad management for AI agents. Sign in through the browser OAuth prompt; never paste credentials into chat." } } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 631fd87..9a49459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to the Synter plugin are documented here. This project follows [semantic versioning](https://semver.org). +## Unreleased — 2026-10-01 + +- Cursor connects to `https://mcp.synterai.com`; Claude retains `https://mcp.syntermedia.ai` to match the connector under review. Both use browser OAuth. +- Setup and support documentation now describe the hosted connection, shipped skills, and local hook accurately. +- Removed unsupported workflows and limited instruction tool references to the approved 34-tool directory. Ad-copy writing and existing-asset selection remain supported. +- Added offline directory validation and an optional OAuth-authenticated live MCP comparison. + ## [1.1.2] — 2026-09-06 ### Fixed @@ -26,22 +33,16 @@ All notable changes to the Synter plugin are documented here. This project follo ## [1.0.0] — 2026-08-27 ### Changed -- **Approval gating is universal again.** The Solo/Scale tier carve-out that allowed direct single-turn execution has been reverted — every tier now requires explicit approval before any create/enable/budget/launch/pause call that spends money. Reads (`list_*`, `get_*`, `pull_*`, `verify_*`, reports) remain free to run without asking. The session-start hook, output style, and brand-and-safety context all describe the same universal gate again. +- Account mutations and spend changes require explicit user approval. Read-only reports and account lookups run without asking. - Version canonicalized to **1.0.0** across `.claude-plugin/plugin.json`, `.cursor-plugin/plugin.json`, and `.cursor-plugin/marketplace.json`, closing the skew between the Claude and Cursor packaging surfaces. ### Added - **Packaging and release automation** — a zip build produces `dist/synter-plugin-.zip` with the plugin at the archive root, matching Claude Desktop's zip-upload layout. CI validates every PR (manifest JSON, marketplace/plugin name match, skill frontmatter, `.mcp.json`/`mcp.json` interpolation guards) and a release workflow attaches the built zip to the GitHub Release on each `synter--v*` tag push. -### Fixed -- README no longer tells Claude Code installers they can "leave it blank" — `userConfig.synter_api_key` is `required: true` and a blank key yields zero Synter tools (see 0.1.1 below). The install steps now point straight at [syntermedia.ai/developer](https://syntermedia.ai/developer) and clarify that `.mcp.json` (Claude) and `mcp.json` (Cursor) use different key-interpolation syntax and must not be cross-wired. - ## [0.4.1] — 2026-08-19 ### Added -- **Cursor Plugin packaging** — `.cursor-plugin/plugin.json`, `.cursor-plugin/marketplace.json`, and `mcp.json` so the same repo can be imported as a Cursor Team Marketplace and submitted to the [Cursor Marketplace](https://cursor.com/marketplace/publish). The hosted Synter MCP (`https://mcp.syntermedia.ai`) takes `SYNTER_API_KEY` via Cursor plugin variables. Listing metadata includes the Synter mark (`assets/logo.png`, 1024×1024) and the marketplace description on both the plugin manifest and the marketplace index. - -### Fixed -- Cursor install no longer requires `SYNTER_API_KEY` up front (onboarding and free GA4 tools work without one). HTTP MCP declares `type: "http"`. Logo uses an absolute GitHub URL so the marketplace crawler does not 404. Cursor hooks use `sessionStart` instead of Claude's `SessionStart`. +- Cursor plugin manifest, marketplace index, and remote MCP configuration. ## [0.4.0] — 2026-08-04 @@ -60,7 +61,6 @@ Fourteen new skills deepening diagnostics, measurement, and creative coverage - **Platform depth** — `meta-ads-diagnostics` (Learning Phase, fatigue, overlap, Advantage+), `google-ads-quality-score`, `google-shopping-optimizer` (feeds, disapprovals, Shopping vs PMax), `performance-max-optimizer` (asset groups, brand cannibalization), `linkedin-ads-targeting` (ABM, lead gen forms). - **Measurement & data** — `pixel-capi-auditor` (dedup, Event Match Quality), `first-party-data-strategy` (Enhanced Conversions, server-side tagging, Consent Mode v2), `incrementality-testing` (geo holdouts, lift studies), `attribution` companions via `measure_incrementality` and `get_attribution`. - **Strategy** — `retargeting-sequence-designer` (staged funnels, recency windows, exclusions), `seasonal-budget-planner` (build your own seasonality index from your account data), `audience-expansion-strategy` (lookalike ladders, seed quality, layering), `geo-integrity-audit` (location-criteria and click-geography verification). -- **Creative** — `video-ad-scriptwriter` (platform-specific scripts with timing marks), `ugc-creative-brief` (briefs, hooks, usage-rights structure). ## [0.2.0] — 2026-07-22 @@ -73,33 +73,21 @@ Broadened skill and agent coverage so the operator can plan, port, and run campa - Agents `media-buyer` and `campaign-strategist` now point at the `replicate` workflow and the platform playbooks. ### Changed -- **`campaign-preflight`** rewritten to use only public MCP tools. The prior version referenced internal backend scripts (`*_validate_campaign_structure`, `run_gaql`, `verify_website_tracking`, `POST /tools/run` blocks) that the public MCP does not expose, and carried a hardcoded country blocklist. Now grounded in `run_gaql_query`, `ga4_list_conversions`, `get_gtm_tag`, and `verify_pixel_ownership`. -- **`mmm-budget-planner`** rewritten around the public `optimize_budget`, `get_attribution`, and `measure_incrementality` tools instead of internal `mmm_*` training scripts that are not available through the MCP. +- **`campaign-preflight`** moved from internal backend scripts to public MCP checks. +- **`mmm-budget-planner`** rewritten around the public `optimize_budget`, `get_attribution`, and `measure_incrementality` tools instead of internal scripts that are not available through the MCP. ### Note on exclusions The new `kill-scale-rules`, `platform-benchmarks`, and `pre-pause-analysis` skills ship **generic methodology only**. Synter's proprietary numeric thresholds and cost-benchmark tables remain withheld — these skills teach the approach and defer to the account's own data, they do not carry the internal numbers. Internal runbooks and infrastructure-coupled skills are still not shipped. -## 0.1.1 — 2026-07-20 - -### Fixed -- **API key is now required at plugin enable time.** The previous config invited users to - "leave blank to onboard," but the hosted Synter MCP server (mcp.syntermedia.ai) rejects - unauthenticated connections with a 401 — so a blank key meant the MCP server silently - failed to load and Claude had skill files but zero Synter tools. Requiring the key up - front prevents the broken skills-without-tools state. Get a key at syntermedia.ai/developer. - - ## [0.1.0] — 2026-06-16 Initial release. The AI Agent Operator for Ads, packaged for Claude Code / Claude Desktop. ### Added -- **MCP server** — Synter Advertising Platform (`https://mcp.syntermedia.ai`), cross-platform ad read/write. Free GA4 + onboarding tools work without a key. -- **Workflow skills** (`/synter:*`) — quickstart, connect, audience, creative, launch, optimize, report, help. -- **Craft skills** — 9 generic advertising-methodology skills: ad-policy-compliance, anomaly-detector, campaign-preflight, campaign-structure-auditor, creative-fatigue-detector, executive-reporting, landing-page-creation, mmm-budget-planner, roas-calculator. Deliberately excluded from the public package: internal-execution runbooks (Doppler/DB-coupled), Synter's proprietary operating playbooks (kill/scale thresholds, cost-benchmark + budget-split logic), and skills referencing internal infrastructure (pixel pipeline, table names, non-public tools). +- **Workflow skills** (`/synter:*`) — quickstart, connect, audience, launch, optimize, report, help. +- **Craft skills** — 9 generic advertising-methodology skills: ad-policy-compliance, anomaly-detector, campaign-preflight, campaign-structure-auditor, creative-fatigue-detector, executive-reporting, mmm-budget-planner, roas-calculator. Deliberately excluded from the public package: internal-execution runbooks (Doppler/DB-coupled), Synter's proprietary operating playbooks (kill/scale thresholds, cost-benchmark + budget-split logic), and skills referencing internal infrastructure (pixel pipeline, table names, non-public tools). - **Agents** — campaign-strategist, media-buyer, audience-builder, creative-director, budget-optimizer, performance-analyst. - **Hook** — SessionStart primer enforcing the money-safety rules and brand voice. - **Output style** — `synter` agent voice. - **Context** — bundled brand-voice and safety rules referenced by skills and agents. -- `userConfig.synter_api_key` for turnkey, secure key entry at enable time. - **SDK runner** (`sdk/synter-agent.mjs`) — headless [`@anthropic-ai/claude-agent-sdk`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) harness that loads the plugin and runs the operator unattended. Read-only by default (spend/mutation auto-denied via `canUseTool`); `--allow-writes` to lift. Verified: SDK loads all 23 skills + 6 agents. diff --git a/CLAUDE_COMMUNITY_SUBMISSION.md b/CLAUDE_COMMUNITY_SUBMISSION.md index 5352dd4..7201ce2 100644 --- a/CLAUDE_COMMUNITY_SUBMISSION.md +++ b/CLAUDE_COMMUNITY_SUBMISSION.md @@ -30,7 +30,7 @@ complementary catalogs. Do not treat connector review as plugin publication. | Category | Marketing and advertising | | Short description | Operate cross-channel advertising with approval-gated Synter agents, skills, and MCP tools. | | Brand icon | `assets/logo.png` — canonical transparent 1024×1024 Synter mark | -| Support | `https://syntermedia.ai/contact` | +| Support | `https://syntermedia.ai/support` | | Privacy policy | `https://syntermedia.ai/privacy` | | Terms | `https://syntermedia.ai/terms` | @@ -91,11 +91,8 @@ Direct installation is not public-directory discovery. - [x] Production OAuth protected-resource and authorization-server metadata return `200`; a protected tool call returns `401` with a `WWW-Authenticate` resource-metadata challenge. -- [x] The production MCP tool surface has 195 tools; every tool supplies a - title plus boolean `readOnlyHint` and `destructiveHint` annotations. -- [x] Clean Claude Code configuration can add `Synter-Media-AI/plugin`, install - `synter@synter`, and inventory all 57 skills, 7 agents, 1 hook, and 1 MCP - server. +- [ ] Run the optional live directory check with reviewer-authorized OAuth before submission; the checked-in directory contains 34 tools. +- [ ] Re-inventory the updated package: 52 skills, 6 agents, one Claude session hook, and one remote MCP server per client. - [ ] Provider reviewer exercises `/synter:quickstart`, one read-only report, and one write preview without approving the write. - [ ] Provider reviewer uses a Synter-owned test workspace with sample data; @@ -107,9 +104,8 @@ Direct installation is not public-directory discovery. ## Security and data-use evidence -- The plugin declares no local executable dependency or install script. -- The only remote MCP destination is `https://mcp.syntermedia.ai`, which uses - OAuth 2.1 with PKCE for Claude clients. +- Claude invokes a Bash session hook that prints static guidance through `cat`. Cursor declares no hook. The repository also contains a manually invoked Node SDK runner and development scripts; no compiled binaries are bundled. +- Claude uses `https://mcp.syntermedia.ai` to match the connector under review. Cursor separately uses `https://mcp.synterai.com`. Both plugin configurations use browser OAuth without static credentials or headers. - The session hook prints a static operating and approval-safety primer into session context; it does not read or transmit credentials. - Advertising writes are approval-gated. The bundled guidance does not turn a @@ -138,14 +134,13 @@ boundary before any external spend-changing action. | --- | --- | | Public source and license | Public GitHub repository; MIT `LICENSE` | | OAuth for authenticated remote MCP | OAuth metadata, DCR, PKCE, and protected-tool `401` challenge at `https://mcp.syntermedia.ai` | -| Tool annotations | All 195 discovered tools include `title`, boolean `readOnlyHint`, and boolean `destructiveHint` | -| Privacy and support | `https://syntermedia.ai/privacy`; `https://syntermedia.ai/contact` | +| Tool directory | 34 checked-in tool names; live annotations require a fresh authenticated review | +| Privacy and support | `https://syntermedia.ai/privacy`; `https://syntermedia.ai/support` | | Narrow, visible instructions | Human-readable skills and agents in this repository; no encoded or remotely loaded behavioral instructions | | Approval boundary | Spend-changing operations require explicit approval; a model response is not approval | | Test account | Must be supplied privately by Synter in the submission form | -The plugin manages advertising workflows and can invoke creative tools, so the -submission must describe those capabilities accurately. Anthropic retains +The plugin manages advertising workflows, writes text ads, and selects existing assets. The submission must describe those capabilities accurately. Anthropic retains discretion under the Software Directory Policy's unsupported-use restrictions; do not claim acceptance or an Anthropic Verified status before it appears in the directory. diff --git a/README.md b/README.md index 2d6f775..4833bc9 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,9 @@ # Synter — Claude and Cursor plugin -**The AI Agent Operator for Ads.** Claude, Cowork, Claude Code, and Cursor for advertising. You direct, the agents execute. +Plan advertising campaigns, review performance, write ad copy, and apply approved campaign changes through Synter. This plugin is a no-cost client for the paid Synter service. You need a Synter account and authorized advertising accounts to use the hosted tools. -Connect every ad platform, build audiences from signals and first-party data, generate on-brand creative, plan and launch cross-platform campaigns, and reallocate spend by ROAS — all in one conversation. Nothing that spends money ships without your approval. - -One interface. Every ad platform. Ship faster. - ---- - -## Install +## Install and sign in ### Claude Code @@ -21,207 +15,75 @@ One interface. Every ad platform. Ship faster. /plugin install synter@synter ``` -Start a new conversation and run `/synter:quickstart`. Claude opens Synter's -secure browser OAuth flow when the first protected tool is used. Never paste an -API key, access token, or authorization code into chat. +Start a new conversation and run `/synter:quickstart`. Complete Synter’s browser OAuth prompt when the client requests authorization. Never paste credentials, access tokens, or authorization codes into chat. ### Claude Desktop and Cowork -Open **Customize → Plugins**. Under **Personal plugins**, click **+ → Add -marketplace → Add from a repository**, enter `Synter-Media-AI/plugin`, and -install `synter` from that marketplace. The full GitHub URL also works. +In **Customize → Plugins**, add `Synter-Media-AI/plugin` as a repository marketplace and install `synter`. You can also upload the plugin zip from the [latest GitHub release](https://github.com/Synter-Media-AI/plugin/releases/latest). Start a new conversation and complete the browser OAuth prompt. -Alternatively, download the package from the [latest GitHub -release](https://github.com/Synter-Media-AI/plugin/releases/latest) and choose -**+ → Upload plugin**. A tagged release (`synter--v*`) rebuilds the zip through -the repository's release workflow. - -Start a new conversation after installation and complete the Synter browser -sign-in prompt. Skills work in Claude chat and Cowork; hooks and sub-agents run -in Cowork. The plugin can be installed directly today, but it will not appear -in Anthropic's public directory until Anthropic approves the separate community -submission. - -Official install documentation: [Use plugins in Claude](https://support.claude.com/en/articles/13837440-use-plugins-in-claude) and [Install plugins in Cowork](https://claude.com/docs/cowork/guide/plugins). +Direct installation does not imply that Anthropic has approved a public directory listing. See the [submission notes](CLAUDE_COMMUNITY_SUBMISSION.md) for the recorded review status. ### Cursor -**Team / local:** import `https://github.com/Synter-Media-AI/plugin` under **Dashboard → Plugins → Team Marketplaces**, or symlink this repo into `~/.cursor/plugins/local/synter` and reload the window. - -**Public marketplace:** submit this repository at [cursor.com/marketplace/publish](https://cursor.com/marketplace/publish). Cursor reviews every listing; the plugin is MIT and open source. - -After install, set `SYNTER_API_KEY` in **Plugins → Configure**. The hosted MCP is `https://mcp.syntermedia.ai`. - -Free GA4 and onboarding tools work with no key and no credits. - -> Campaign write actions spend real money — every spend asks for your approval first. - -### ChatGPT (Developer Mode / Connectors) - -ChatGPT's custom-connector setup (**Settings → Connectors → Advanced → Developer mode**, or the Workspace admin equivalent under **Apps**) accepts Synter's production remote MCP server through OAuth 2.1: - -- [Developer mode and MCP apps in ChatGPT](https://help.openai.com/en/articles/12584461-developer-mode-and-full-mcp-connectors-in-chatgpt-beta) (OpenAI Help Center) — app setup lets you "pick the authentication mechanism, if applicable," then walks through the OAuth authorization prompt; no custom-header option is described. -- [Authentication – Apps SDK](https://developers.openai.com/apps-sdk/build/auth) (OpenAI Developers) — the only documented way to add custom auth to an MCP server ChatGPT connects to is a full OAuth 2.1 flow (protected-resource metadata, CIMD/DCR, PKCE). Static API keys or bearer headers aren't part of the spec ChatGPT implements. - -Add `https://mcp.syntermedia.ai` as the remote MCP URL and complete the Synter authorization flow in the browser. The hosted service publishes OAuth authorization-server and protected-resource metadata, supports dynamic client registration and PKCE, and returns the required resource challenge to unauthenticated MCP clients. - -Supported connection paths are: - -- **ChatGPT** — connect the production MCP URL and authorize with OAuth 2.1. -- **Claude Code, Claude Desktop, or Cowork** — install this plugin and authorize with OAuth 2.1 in the browser. -- **Cursor** — install the Cursor plugin and set `SYNTER_API_KEY` in **Plugins → Configure**. -- **Any other MCP client that supports custom headers** — wire the HTTP endpoint directly with `X-Synter-Key: syn_your_api_key_here` (see "Wire the MCP straight into any client" below). -- **A client with no remote-MCP support** — run the published stdio server locally: `SYNTER_API_KEY=syn_... npx -y @synterai/mcp-server` (get a key at [syntermedia.ai/developer](https://syntermedia.ai/developer)). - ---- - -## What's in the box - -This plugin packages **agents, skills, an MCP server, a hook, and an output style** — the same shape as the [Watt Data plugin](https://github.com/wattdata/plugin), built for Synter. - -### Skills — `/synter:*` - -| Command | Does | -| --- | --- | -| `/synter:quickstart` | Onboard, connect a platform, run a first action. | -| `/synter:connect` | Link ad platforms and GA4 — Google, Meta, LinkedIn, Microsoft, Reddit, TikTok, X, Amazon, and more. | -| `/synter:audience` | Build ABM lists, lookalikes, and signal-based segments; activate them. | -| `/synter:creative` | Generate on-brand images, video, UGC, and ad copy. | -| `/synter:launch` | Plan, preflight, and ship a cross-platform campaign. | -| `/synter:replicate` | Rebuild an existing campaign from one platform onto another (e.g. Google to Microsoft). | -| `/synter:optimize` | Cut wasted spend, scale winners, reallocate budget by ROAS. | -| `/synter:report` | Cross-channel performance report and exec summary. | -| `/synter:help` | What Synter can do, and where to get support. | - -### Craft skills +Import `https://github.com/Synter-Media-AI/plugin` through your team’s plugin marketplace, or symlink this repository into `~/.cursor/plugins/local/synter` and reload Cursor. Complete the browser OAuth prompt for Synter, then run `/synter:quickstart`. -Bundled alongside the workflows is a library of advertising-craft knowledge skills Claude pulls in automatically when the task fits. +Both plugin configurations use remote HTTP MCP with browser OAuth. They contain no static credentials, environment-variable secrets, or authentication headers: -**Platform playbooks** — structure, targeting, and cost realities per platform: -`platform-google` · `platform-microsoft` · `platform-meta` · `platform-linkedin` · `platform-tiktok` · `platform-reddit` · `platform-x` · `platform-amazon` · `platform-openai` +| Client | Configuration | Hosted MCP URL | +| --- | --- | --- | +| Cursor | `mcp.json`, referenced by `.cursor-plugin/plugin.json` | `https://mcp.synterai.com` | +| Claude | `.mcp.json`, discovered by the Claude plugin | `https://mcp.syntermedia.ai` | -**Plan & structure:** `media-plan` · `keyword-research` · `negative-keywords` · `campaign-preflight` · `campaign-structure-auditor` · `platform-benchmarks` +Claude retains the connector URL under Anthropic review. Keep the two configurations separate. For another remote MCP client, use its browser OAuth flow with the appropriate hosted URL. If sign-in fails, reconnect through the client’s settings or contact [support](https://syntermedia.ai/support). -**Bid & budget:** `bid-optimization` · `dayparting` · `kill-scale-rules` · `pre-pause-analysis` · `roas-calculator` · `mmm-budget-planner` · `anomaly-detector` +## What this plugin runs -**Creative:** `ad-copy-generation` · `creative-testing` · `creative-fatigue-detector` · `ad-policy-compliance` · `landing-page-creation` +The plugin connects to the hosted Synter MCP server over OAuth. The advertising tools run on that server. Local Markdown skills, agents, and rules guide how the client uses them; the checked-in [tool directory](tools/directory-tools.json) contains the 34 supported tool names. -**Measurement:** `conversion-tracking` · `attribution` · `utm-builder` · `executive-reporting` +Claude also runs one `SessionStart` hook from [hooks/hooks.json](hooks/hooks.json). It invokes [scripts/session-context.sh](scripts/session-context.sh), a Bash script that uses `cat` to print static account-scope and approval guidance. It does not read credentials or make network requests. Cursor declares no hooks and uses [rules/synter-operator.mdc](rules/synter-operator.mdc). -**Research:** `competitor-analysis` +There is no local MCP server or bundled compiled binary. The repository includes an optional [Node SDK runner](sdk/README.md), invoked manually, and development scripts for validation and packaging. The release zip excludes the SDK. Agent definitions may delegate work when the host client supports them. -(Internal runbooks, Synter's proprietary operating playbooks, and anything that names internal infrastructure are intentionally not shipped — this plugin acts through the Synter MCP, not bundled scripts.) +Bundled skills (invoke as `/synter:`): -### Agents — `/agents` +- [`ad-copy-generation`](skills/ad-copy-generation/SKILL.md) · [`ad-policy-compliance`](skills/ad-policy-compliance/SKILL.md) · [`anomaly-detector`](skills/anomaly-detector/SKILL.md) · [`attribution`](skills/attribution/SKILL.md) +- [`audience`](skills/audience/SKILL.md) · [`audience-expansion-strategy`](skills/audience-expansion-strategy/SKILL.md) · [`bid-optimization`](skills/bid-optimization/SKILL.md) · [`campaign-preflight`](skills/campaign-preflight/SKILL.md) +- [`campaign-structure-auditor`](skills/campaign-structure-auditor/SKILL.md) · [`competitor-analysis`](skills/competitor-analysis/SKILL.md) · [`connect`](skills/connect/SKILL.md) · [`conversion-tracking`](skills/conversion-tracking/SKILL.md) +- [`creative-fatigue-detector`](skills/creative-fatigue-detector/SKILL.md) · [`creative-testing`](skills/creative-testing/SKILL.md) · [`dayparting`](skills/dayparting/SKILL.md) · [`executive-reporting`](skills/executive-reporting/SKILL.md) +- [`first-party-data-strategy`](skills/first-party-data-strategy/SKILL.md) · [`geo-integrity-audit`](skills/geo-integrity-audit/SKILL.md) · [`google-ads-quality-score`](skills/google-ads-quality-score/SKILL.md) · [`google-shopping-optimizer`](skills/google-shopping-optimizer/SKILL.md) +- [`help`](skills/help/SKILL.md) · [`incrementality-testing`](skills/incrementality-testing/SKILL.md) · [`keyword-research`](skills/keyword-research/SKILL.md) · [`kill-scale-rules`](skills/kill-scale-rules/SKILL.md) +- [`launch`](skills/launch/SKILL.md) · [`linkedin-ads-targeting`](skills/linkedin-ads-targeting/SKILL.md) · [`media-plan`](skills/media-plan/SKILL.md) · [`meta-ads-diagnostics`](skills/meta-ads-diagnostics/SKILL.md) +- [`mmm-budget-planner`](skills/mmm-budget-planner/SKILL.md) · [`negative-keywords`](skills/negative-keywords/SKILL.md) · [`optimize`](skills/optimize/SKILL.md) · [`performance-max-optimizer`](skills/performance-max-optimizer/SKILL.md) +- [`pixel-capi-auditor`](skills/pixel-capi-auditor/SKILL.md) · [`platform-amazon`](skills/platform-amazon/SKILL.md) · [`platform-benchmarks`](skills/platform-benchmarks/SKILL.md) · [`platform-google`](skills/platform-google/SKILL.md) +- [`platform-linkedin`](skills/platform-linkedin/SKILL.md) · [`platform-meta`](skills/platform-meta/SKILL.md) · [`platform-microsoft`](skills/platform-microsoft/SKILL.md) · [`platform-openai`](skills/platform-openai/SKILL.md) +- [`platform-reddit`](skills/platform-reddit/SKILL.md) · [`platform-tiktok`](skills/platform-tiktok/SKILL.md) · [`platform-x`](skills/platform-x/SKILL.md) · [`pre-pause-analysis`](skills/pre-pause-analysis/SKILL.md) +- [`quickstart`](skills/quickstart/SKILL.md) · [`replicate`](skills/replicate/SKILL.md) · [`report`](skills/report/SKILL.md) · [`retargeting-sequence-designer`](skills/retargeting-sequence-designer/SKILL.md) +- [`roas-calculator`](skills/roas-calculator/SKILL.md) · [`seasonal-budget-planner`](skills/seasonal-budget-planner/SKILL.md) · [`utm-builder`](skills/utm-builder/SKILL.md) · [`video-ad-scriptwriter`](skills/video-ad-scriptwriter/SKILL.md) -`campaign-strategist` · `media-buyer` · `audience-builder` · `creative-director` · `budget-optimizer` · `performance-analyst` · `visibility-agent` +Bundled agents: [`audience-builder`](agents/audience-builder.md) · [`budget-optimizer`](agents/budget-optimizer.md) · [`campaign-strategist`](agents/campaign-strategist.md) · [`creative-director`](agents/creative-director.md) · [`media-buyer`](agents/media-buyer.md) · [`performance-analyst`](agents/performance-analyst.md). -They run automatically when the task fits, or call one directly. +[Privacy policy](https://syntermedia.ai/privacy) · [Support](https://syntermedia.ai/support) -### MCP server +## Campaign safety and scope -The Synter Advertising Platform MCP (`https://mcp.syntermedia.ai`) — cross-platform campaign read/write, creative generation, audiences, attribution, and GA4. Tools register automatically once the plugin is enabled. +Confirm the workspace, account name, and account ID before account actions. Use the advertiser’s verified brand and original assets. Writes require an explicit approved scope; a tool’s plan-approval record is not a substitute for the user’s approval. ---- +The launch workflow reads the final plan, runs preflight, records approval, executes, and verifies the returned campaign state. Creating a campaign does not prove it is live or spending. Unsupported operations, including tag publishing and audience synchronization, require a manual handoff to the platform UI. -## Structure +Text ads use `create_ad_copy`; existing assets use `list_creative_assets`. The plugin reports missing capabilities and data rather than inventing tool names or results. -```text -plugin/ -├── .claude-plugin/ -│ ├── plugin.json # Claude/Cowork plugin manifest -│ └── marketplace.json # Claude Code marketplace -├── .cursor-plugin/ -│ ├── plugin.json # Cursor Plugin manifest + SYNTER_API_KEY variable -│ └── marketplace.json # Cursor Team Marketplace index -├── .mcp.json # Synter remote MCP for Claude (browser OAuth) -├── mcp.json # Synter MCP for Cursor (header: ${SYNTER_API_KEY}) -├── skills/ # /synter:* slash commands (SKILL.md each) -├── agents/ # subagent definitions -├── hooks/hooks.json # SessionStart safety + voice primer -├── scripts/session-context.sh -├── context/brand-and-safety.md -├── output-styles/synter.md -├── sdk/ # headless Claude Agent SDK runner -│ ├── synter-agent.mjs -│ └── README.md -├── CHANGELOG.md -└── LICENSE # MIT -``` - -> **Don't cross-wire the two MCP configs.** `.mcp.json` is Claude-only and uses browser OAuth with no static credentials; `mcp.json` is Cursor-only and interpolates `${SYNTER_API_KEY}` from the Cursor plugin's variables. Copying the Cursor header into Claude bypasses the supported secure sign-in flow and violates public-directory requirements. - ---- - -## Run it headlessly — Claude Agent SDK - -A ready-to-run agent harness lives in [`sdk/`](./sdk). It loads this plugin with the SDK and runs the Synter operator unattended — **read-only by default** (spend/mutation is auto-denied with no human to approve). Good for cron digests, pipelines, or embedding. +## Develop and validate ```bash -cd sdk && npm install -SYNTER_API_KEY=syn_... node synter-agent.mjs "/synter:report last 7 days" +node scripts/validate-manifests.js +node scripts/check-directory-tools.js +node --test scripts/check-directory-tools.test.js +npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict +bash scripts/build-zip.sh ``` -See [`sdk/README.md`](./sdk/README.md) for the safety model, `--allow-writes`, and the Python equivalent. - -Under the hood it's just: - -```ts -import { query } from "@anthropic-ai/claude-agent-sdk"; -for await (const m of query({ - prompt: "/synter:report last 7 days", - options: { plugins: [{ type: "local", path: "/path/to/plugin" }] }, -})) { /* skills, agents, hooks, MCP all loaded */ } -``` - -### Or wire the MCP straight into any client - -**HTTP with browser OAuth (recommended)** -```json -{ - "mcpServers": { - "synter": { - "type": "http", - "url": "https://mcp.syntermedia.ai" - } - } -} -``` - -Clients that do not implement remote MCP OAuth can instead use an -`X-Synter-Key` header if they support custom headers, or the stdio package -below. Create a key at [syntermedia.ai/developer](https://syntermedia.ai/developer) -and store it in the client's secret configuration, never in chat. - -**stdio (Claude Desktop)** — via the public SDK [`@synterai/mcp-server`](https://www.npmjs.com/package/@synterai/mcp-server): -```json -{ - "mcpServers": { - "synter": { - "command": "npx", - "args": ["@synterai/mcp-server"], - "env": { "SYNTER_API_KEY": "syn_your_api_key_here" } - } - } -} -``` - ---- - -## Safety - -Your agent can create campaigns, change budgets, and pause spend. The plugin defaults to **recommend-then-execute** and asks for explicit approval before anything spends money. It confirms the org/account before any write, uses only the real IDs the platform returns, and guards against fat-finger budgets. Reads are always free to run. - ---- - -## Develop - -```bash -git clone https://github.com/Synter-Media-AI/plugin synter-plugin && cd synter-plugin -claude plugin validate . --strict # check the manifest + frontmatter -claude --plugin-dir . # load locally without a marketplace -``` +The directory check scans every file under `skills/`, `commands/` (when present), `rules/`, and `agents/`, including prose, tables, and code fences. Spell out exact tool names; dynamic tool-name templates fail validation. Snake-case data fields are recognized separately, but calling them as functions still counts as a tool reference. For a tool name without underscores, use explicit `tool:name` or its MCP-qualified name. -After editing `hooks/`, `.mcp.json`, or `agents/`, run `/reload-plugins`. +The check runs offline in CI. To also compare the directory against live MCP `tools/list`, supply an OAuth access token through `SYNTER_MCP_TOKEN` in your environment and run the same script. It initializes an MCP session and follows paginated results. `--live` requires the token instead of silently running offline. `SYNTER_MCP_URL` optionally selects either hosted URL in the table; it defaults to the Cursor endpoint. The token is for this development check only and is never written to plugin configuration. Extra live tools do not expand the checked-in directory. -[Privacy](https://syntermedia.ai/privacy) · [Terms](https://syntermedia.ai/terms) · [Support](https://syntermedia.ai/contact) · MIT licensed. © 2026 Synter. +[Terms](https://syntermedia.ai/terms) · [MIT license](LICENSE) diff --git a/SECURITY.md b/SECURITY.md index d3bef89..6d5bf4d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,8 +6,9 @@ Email **joel@synterai.com** with the subject "Security: Synter plugin". Please i ## What this plugin does -- It contains Markdown skills, agents, and rules, plus one remote MCP server entry. It ships no executable hooks for Cursor. -- The MCP server is Synter's hosted server over HTTPS. Cursor authenticates with browser OAuth (scopes `tools:read` and `tools:write`). An API key is an optional fallback for headless use and is never stored in this repo. +The plugin ships Markdown skills, agents, and rules. Cursor connects to `https://mcp.synterai.com`; Claude connects to `https://mcp.syntermedia.ai`. Both use browser OAuth without static credential headers or secret environment configuration. + +Claude runs one SessionStart Bash hook that prints static guidance through `cat`. It does not read credentials or make network requests. Cursor declares no executable hooks. The optional Node SDK runner and repository validation/build scripts run only when invoked manually; no compiled binaries are bundled. ## Spend and write safety @@ -16,4 +17,4 @@ Email **joel@synterai.com** with the subject "Security: Synter plugin". Please i ## Data handling -Synter processes ad-account data only to perform the actions you request. See the [Privacy Policy](https://syntermedia.ai/privacy), [Terms](https://syntermedia.ai/terms), and [Security overview](https://syntermedia.ai/security). +For service data handling, see the [Privacy Policy](https://syntermedia.ai/privacy), [Terms](https://syntermedia.ai/terms), and [Security overview](https://syntermedia.ai/security). diff --git a/agents/audience-builder.md b/agents/audience-builder.md index 4f08624..3d6a277 100644 --- a/agents/audience-builder.md +++ b/agents/audience-builder.md @@ -8,13 +8,13 @@ effort: high You are Synter's audience builder. You turn "who we want to reach" into a real, activated segment. Approach: -- Pick the path that fits: `build_abm_audience` (named accounts / ICP by buying signals — hiring, ad spend, funding, intent, installs), `build_lookalike_audience` (from a seed), `find_audience_signals` then build from the strongest signal, or a first-party list upload. +- Inspect existing audiences with `list_audiences`; define account criteria, seeds, exclusions, and consent requirements. Use `research_campaign_opportunity` for available research. Have the account owner construct or synchronize new lists in the platform UI. - Check `list_audiences` first — never rebuild a duplicate. - Profile before activating: size, match rate, overlap, and whether it clears each platform's minimum (LinkedIn company audiences and Reddit have real floors). Report the numbers honestly — a sub-floor segment won't deliver, and you say so. - First-party data: canonicalize and hash identifiers before upload; tiny B2B work-email lists often miss the match floor — warn the user. -- Activate with `sync_audience` / `attach_audience`. For a fresh campaign on the segment, hand to the media-buyer. Use `stage_audience_artifact` when the user should review the list first. +- Confirm returned audience IDs, then hand the reviewed targeting brief to the media-buyer for supported campaign-plan fields and approval. Rules: -- Confirm the org/account before activating. Reads are free; activation is an action. +- Confirm the org/account before activating. Reads do not require approval; activation is an action. - Respect match-rate floors — surface them, never hide a too-small segment. - Handle PII correctly: hash where the platform expects hashes; never leak one tenant's data into another. diff --git a/agents/budget-optimizer.md b/agents/budget-optimizer.md index 50dae89..55ec4c0 100644 --- a/agents/budget-optimizer.md +++ b/agents/budget-optimizer.md @@ -8,15 +8,15 @@ effort: high You are Synter's budget optimizer. You move money toward what works and away from what doesn't — on the user's approval. Method: -- Pull recent performance across connected platforms (`pull__ads_performance`), reconcile with `reconcile_platforms`, and read true drivers with `get_attribution`. +- Pull recent performance across connected platforms (the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`)), reconcile with `get_spend_reconciliation`, and read true drivers with `get_attribution`. - Diagnose against minimum-data guardrails — never kill or scale on noise. State the threshold you used. - Wasted spend: spend with no conversions past the threshold. - Winners: low CPA / high ROAS with enough volume to scale. - Anomalies: spend spikes, CTR drops, budget runaway. - Fatigue: rising frequency + falling CTR → flag for creative rotation. -- Act with `optimize_budget` (cross-platform reallocation), `update_campaign_budget` (guard against fat-finger amounts), `pause_campaign` (kill), and bid tuning where supported. Scale in platform-appropriate steps — big jumps reset the learning phase. +- Use `optimize_budget` for allocation recommendations. Apply approved budget changes with `update_campaign_budget` and approved pauses with `pause_campaign`. For other changes, follow **launch** or hand off unsupported edits to the platform UI. Rules: - Recommend first: what to cut, what to scale, where freed budget goes, expected effect. Wait for explicit approval before any budget/pause/bid change. -- After acting, re-pull and report the new state. Set `set_spend_alert` so runaway spend is caught. +- After acting, re-pull and report the new state. Recommend alert thresholds for the user to configure in the platform UI. - Never report a change as done without confirming it landed. diff --git a/agents/campaign-strategist.md b/agents/campaign-strategist.md index 6bde788..d75ac7e 100644 --- a/agents/campaign-strategist.md +++ b/agents/campaign-strategist.md @@ -10,7 +10,7 @@ You are Synter's campaign strategist. You turn a business goal into a concrete, How you work: - Start from the objective and the conversion that defines success, not from a platform. Ask only for what you genuinely need: goal, target CPA/ROAS, budget, audience, geo, offer. - Recommend the channel mix with a real rationale grounded in how each platform performs for this objective and audience. Don't just echo the platform the user named. -- Use the Synter MCP to ground the plan in reality: `list_connected_accounts` (what's available), `forecast_campaign` (reach/CPM/CPC/CPA), `find_audience_signals` (who's reachable), historical `pull__ads_performance` (what's worked before). +- Ground the proposal in `list_connected_accounts`, `research_campaign_opportunity`, `forecast_campaign`, and the supported platform performance tools. Separate historical observations from estimates and unavailable data. - Allocate budget across channels with projected outcomes per channel. Show the math. Start conservative; plan the scale path. - Always include measurement: which conversion, which tracking, how you'll know it worked. diff --git a/agents/creative-director.md b/agents/creative-director.md index 761d8fc..d7a63f8 100644 --- a/agents/creative-director.md +++ b/agents/creative-director.md @@ -1,6 +1,6 @@ --- name: creative-director -description: Generates on-brand, policy-clean ad creative — images, video, UGC, RSA copy, and voice — and prepares variants for testing. Invoke when a user needs new ads, creative variations, or to refresh fatigued creative. +description: Writes ad copy, reviews existing advertiser assets, and prepares test briefs. Use for text variants, asset selection, and campaign creative review. model: opus effort: high --- @@ -9,12 +9,12 @@ You are Synter's creative director. You produce ad creative that is on-brand, ac Method: - Get the brief: platform + format, objective, audience, the one message, and the real proof point. Read brand rules at `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md` before writing anything. -- Generate with the right tool: `generate_image_ad` / `generate_image`, `generate_video_ad` / `generate_video`, `generate_ugc_ad`, `generate_text_ad` (headlines + descriptions), `generate_voice_ad`. Produce variants for a real test, not one-and-done. -- `list_creative_assets` to see the library; `upload_creative` for the user's own assets. +- Write text with `create_ad_copy`; prepare variants that test a specific hypothesis. Request missing media from the advertiser. +- Inspect existing assets with `list_creative_assets`. Use returned asset IDs in supported campaign-plan fields and follow the launch approval flow. Non-negotiables: -- Brand voice: terse, certain, doing the work — show, don't sell. Approved CTAs and verbs only. No banned language ("AI-Powered", "seamless", "revolutionary", "Try the beta", "Start Your Free Trial Today!", and the rest of the forbidden list). +- Use the verified advertiser’s brand kit, voice, approved claims, and original assets. Synter’s own brand rules apply only when Synter is the advertiser. - Accuracy over polish: never fabricate a price, competitor, statistic, or claim. No real number → say so; don't invent it. - Fill the format: RSAs ship with 11+ headlines and 4 descriptions (15/4 recommended, never the API minimum of 3/2), with the ad group's actual keywords echoed in at least 5 headlines — under-filled asset sets score "Poor" on Ad Strength and get throttled. No filler lines; rewrite to the character limit rather than truncate. - Policy: pre-check against the target platform's ad rules and flag anything restricted. -- Generating is safe; attaching creative to a live, spending campaign is an action the user approves. +- Writing text does not deploy it. Adding assets to a live campaign requires an approved scope and the **launch** workflow. diff --git a/agents/media-buyer.md b/agents/media-buyer.md index d902f58..70b6be8 100644 --- a/agents/media-buyer.md +++ b/agents/media-buyer.md @@ -9,13 +9,13 @@ You are Synter's media buyer. You take an approved plan and make it live, correc Discipline: - Confirm the target before touching anything: `list_connected_accounts`. Use the exact account IDs the tools return — never invent or guess an ID. Never run one org's campaign through another org's key. -- Confirm conversion tracking exists and is attached before launch (`ga4_list_conversions`, `get_gtm_tag`, `verify_pixel_ownership`). No tracking → stop and fix it. +- Check available conversion evidence with `ga4_get_conversions`, `ga4_get_report`, and `get_pixel_health`. Stop if required tracking verification is missing. - Run preflight every time: geo correct, exclusions applied, tracking firing, budget and bid caps sane (guard hard against fat-finger budgets — a daily budget 10–100x intended is the classic incident), creative policy- and brand-clean, final URL resolves and matches the ad. - Creative floor before launch: every RSA carries 11+ headlines and 4 descriptions (15/4 recommended — never the API minimum of 3/2), keywords echoed in the headlines. Google Ad Strength POOR or AVERAGE means fix the assets first, not launch and hope. -- Build with `create_campaign_plan` / `create_campaign_for_audience` / `execute_campaign_plan`; go live with `enable_campaign`. For multi-platform, ship each platform and confirm each with its real campaign ID. -- Porting an existing campaign to another platform (e.g. "build my Google campaign on Microsoft"): read the source structure first (`list_campaigns`, `run_gaql_query` for Google), map it to the target platform, then build. Carry over geo, match types, and negatives, not just keywords. This is the **replicate** workflow; the per-platform mapping lives in the **platform-google** / **platform-microsoft** / **platform-meta** / **platform-linkedin** and sibling playbook skills. +- Build with `create_campaign_plan` and `upsert_plan_entity`. Read `get_campaign_plan`, run `run_launch_preflight`, show the final scope, obtain explicit user approval, then call `approve_campaign_plan` and `execute_campaign_plan`. Check `get_plan_execution` and `get_job_status` for returned jobs; use `enable_campaign` only within the approved scope. +- When porting campaigns, read `list_campaigns` and `audit_account_structure`; request platform exports for missing geo, match-type, negative, and asset details. Follow **replicate** and the target platform playbook. Hard rules: -- Recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. Reads stay free. +- Recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. Reads do not require approval. - "Created" ≠ "live and spending." Verify with `list_campaigns` and report the real IDs before claiming it's running. - Start conservative on budget. Scaling is a later, separate execution step. diff --git a/agents/performance-analyst.md b/agents/performance-analyst.md index 56356bc..c6ed649 100644 --- a/agents/performance-analyst.md +++ b/agents/performance-analyst.md @@ -1,42 +1,13 @@ --- name: performance-analyst -description: Deep-dive performance analyst. Evaluates account change logs, recent activity, Synter Signals, first-party audience health, cross-platform multi-touch attribution, and vertical benchmarks to tell the complete truth about account performance and waste. +description: Reviews account performance, audiences, attribution, and measurement gaps; recommends next actions without executing changes. model: opus effort: high disallowedTools: Write, Edit --- -You are Synter's chief performance analyst. You provide deep, ground-truth account intelligence — analyzing recent change logs, user/platform activity, Synter Signals, audience health, multi-touch attribution, and vertical performance benchmarks. +Confirm the workspace and account IDs with `list_connected_accounts`. Pull comparable periods with `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, and `pull_reddit_ads_performance` where connected. -How you analyze: -1. **Account Change Logs & Activity Auditing**: - - Inspect recent account activity, budget shifts, bid changes, and creative swaps (`get_ad_readback`, `builtwith_recent_changes`, platform readbacks) to uncover *why* performance moved up or down. - - Correlate performance spikes or drops directly with specific edits in the change log. +Use `ga4_get_report`, `get_attribution`, and `get_spend_reconciliation` to explain differences between platform claims and analytics. Review `list_audiences` and `get_pixel_health` for available audience and tracking evidence. Request platform exports for missing breakdowns or change history. Never present a correlation as a proven cause. -2. **Synter Signals & High-Intent Momentum**: - - Analyze buyer intent signals and account engagement trends (`find_audience_signals`, `pull_linkedin_company_engagement`, `get_llm_visibility_overview`). - - Identify which ICP segments and companies are demonstrating high-intent signals before they convert. - -3. **Synter Audiences & First-Party Data Health**: - - Audit first-party data sync status, match rates, seed quality, and retargeting pool sizes (`list_audiences`). - -4. **Cross-Platform Multi-Touch Attribution**: - - Pull cross-channel performance (`pull__ads_performance`), GA4 ground truth (`ga4_run_report`), and conversion paths (`get_attribution`). - - Reconcile self-reported platform numbers against analytics and CRM using `reconcile_platforms`. Clearly distinguish "platform self-reported" from "analytics verified." - -5. **Vertical Benchmarks & Comparative Analysis**: - - Benchmark CPM, CPC, CTR, CPA, and ROAS against vertical benchmarks (`platform-benchmarks`, industry cost standards) to show exactly where the account is outperforming or overpaying. - -Narrative Format: -Lead with an authoritative, proactive opening: -"Hey! I analyzed your account across all channels. Here is what changed in your account activity, how your performance compares against industry benchmarks, where Synter Signals are showing high-intent momentum, and the 3 high-leverage moves to make next." - -Structure: -- **Executive Summary & Ground-Truth Performance**: Blended spend, conversions, CPA, and ROAS vs target. -- **Change Log & Activity Correlation**: How recent account changes impacted CTR, CPM, and CPA. -- **Synter Signals & Audience Health**: Intent signals, company engagement, and first-party match rates. -- **Vertical Benchmark Comparison**: How your CPMs, CPCs, and CPAs compare to industry benchmarks. -- **Attribution & Channel Breakdown**: Reconciled numbers per platform vs GA4/CRM ground truth. -- **Recommended Action Items**: 3 concrete, high-leverage moves handed off to the `budget-optimizer` or `media-buyer`. - -Read-only. You report and diagnose; you do not execute spend changes. +Lead with spend, conversions, CPA/ROAS, and the comparison period. Explain the supported causes, measurement limits, and practical next actions. Name the platform, account, and account ID for every recommendation. Hand proposed changes to the budget-optimizer or media-buyer for explicit user approval; do not execute them. diff --git a/agents/visibility-agent.md b/agents/visibility-agent.md deleted file mode 100644 index f6b9752..0000000 --- a/agents/visibility-agent.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: visibility-agent -description: Audits and optimizes brand visibility in AI Search (ChatGPT, Claude, Perplexity, Gemini). Runs visibility scans, finds citation gaps, audits LLM crawler readiness, generates JSON-LD schema, drafts Wikipedia/Wikidata entities, and recommends strategies to get cited in LLM answers. -model: grok-4.1 -effort: high ---- - -You are Synter's LLM Visibility & AI Search Agent. You monitor, audit, and optimize how brands and products are cited, recommended, and surfaced across AI search engines and LLM answers (ChatGPT, Claude, Gemini, Perplexity). - -How you work: -- Audit brand visibility across LLMs using the Synter MCP tools: `get_llm_visibility_overview`, `list_llm_visibility_prompts`, `list_llm_visibility_sightings`, and `run_llm_visibility_scan`. -- Identify prompt gaps and opportunities: find high-value prompts where competitors are cited but your brand is missing using `list_llm_visibility_opportunities` and `discover_llm_visibility_prompts`. -- Audit technical readiness: check crawler access (robots.txt, llms.txt, AI user-agents) using `audit_llm_crawler_readiness` and inspect review site profiles with `audit_review_site_profiles`. -- Actionable Optimization & Entity Building: - - Generate structured JSON-LD schema markup with `generate_jsonld_schema_markup`. - - Draft Wikipedia articles and sync Wikidata entities with `draft_wikipedia_article` and `sync_wikidata_entity`. - - Find top citation listicles and review sites with `find_llm_citation_listicles`. - - Recommend data-driven visibility strategies with `recommend_llm_visibility_strategy`. - -Output a structured visibility report: Visibility Score → LLM Sightings & Gaps → Technical/Crawler Audit → Content & Entity Action Items (Schema, Wikidata, Listicles) → Recommended Strategy. diff --git a/context/brand-and-safety.md b/context/brand-and-safety.md index f8b6867..c7e5051 100644 --- a/context/brand-and-safety.md +++ b/context/brand-and-safety.md @@ -4,7 +4,10 @@ The AI Agent Operator for Ads. Claude Code for advertising. **You direct, the agents execute.** One interface. Every ad platform. Ship faster. -## Voice +## Advertiser identity +Before writing copy or selecting assets, confirm the workspace ID, advertiser name, and domain using account evidence and the user’s verified context. Obtain the advertiser’s brand kit, voice, and original assets from the user or existing workspace assets returned by `list_creative_assets`. Use that context explicitly in supported tool fields. Do not apply Synter’s own voice or branding to another advertiser. Ask for missing brand details; never invent them. + +## Synter voice (only when Synter is the verified advertiser) Sound like the agent talking: terse, certain, doing the work. Show, don't sell. - Lead with the action or the result. ("Found $3k in wasted spend.") - Short declaratives, 5–12 words. One idea per sentence. @@ -18,7 +21,7 @@ Sound like the agent talking: terse, certain, doing the work. Show, don't sell. - "Revolutionary", "disruptive", "game-changing", "cutting-edge", "next-gen", "best-in-class". - "Leverage", "utilize", "optimize", "streamline", "empower", "enable", "unlock". - "Seamless", "frictionless", "effortless", "10x", "supercharge", "turbocharge". -- "Start Your Free Trial Today!"-style hype CTAs. +- "Act now!"-style hype CTAs. - **"Beta"** in any form — there is no beta. Synter is a live product. ## Approved CTAs & verbs @@ -32,9 +35,9 @@ Verbs/nouns: **Operate** (not manage), **Ship** (not launch/deploy), **Platforms Never fabricate a price, competitor, statistic, or claim. If you don't have the real number, say so. A plain true line beats an invented impressive one. ## Safety — money rules -- Nothing that spends money ships without explicit user approval. No exceptions, no tier carve-outs. +- Nothing that spends money ships without explicit user approval. No exceptions. - Default to recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. -- Reads are free: `list_*`, `get_*`, `pull_*`, `verify_*`, and reports run without asking. +- Read-only reports and account lookups do not require approval. - Confirm the org/account before any write (`list_connected_accounts`). Never run one org's campaigns through another's credentials or session. - Use real account/campaign IDs the tools return — never invent them. - Guard budgets against fat-finger amounts (a daily budget 10–100x intended is the classic incident); default to sane ceilings. diff --git a/output-styles/synter.md b/output-styles/synter.md index e987256..48e4bbd 100644 --- a/output-styles/synter.md +++ b/output-styles/synter.md @@ -9,8 +9,8 @@ Respond like the Synter operator: terse and certain, doing the work right now. - Short declaratives. One idea per sentence. Expand only when the reader needs detail. - Concrete over abstract — real numbers, real platform names, real outcomes. - First person, present tense, as the agent ("Pulling your spend.", "Executing end-to-end."). -- Recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. Reads (list_*, get_*, pull_*, verify_*, reports) run without asking. +- Recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. Read-only reports and account lookups run without asking. - Confidence without volume: no exclamation points, no all-caps, no emojis, no hype. -- Never use banned language ("AI-Powered", "seamless", "revolutionary", "leverage", "beta", "Start Your Free Trial Today!"). Use approved verbs: Operate, Ship, Platforms, Direct API, AI Agents. +- Never use banned language ("AI-Powered", "seamless", "revolutionary", "leverage", "beta", "Act now!"). Use approved verbs: Operate, Ship, Platforms, Direct API, AI Agents. - Never fabricate a price, competitor, or statistic. If you don't have the real number, say so. - Before reporting a campaign live or a change done, verify it. "Created" is not "live and spending." diff --git a/rules/synter-operator.mdc b/rules/synter-operator.mdc index 00f66c6..6f275d7 100644 --- a/rules/synter-operator.mdc +++ b/rules/synter-operator.mdc @@ -5,10 +5,12 @@ alwaysApply: true You are the Synter operator — the AI Agent Operator for Ads. You direct, the agents execute. Operating rules: -- MONEY SAFETY RULES: Nothing that spends money ships without explicit user approval. No exceptions, no tier carve-outs. +- MONEY SAFETY RULES: Nothing that spends money ships without explicit user approval. No exceptions. - Default to recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. -- Reads run without asking: list_*, get_*, pull_*, verify_*, and reports. +- Read-only reports and account lookups run without asking. - Confirm the org/account before any write (list_connected_accounts). Never run one org's campaigns through another's credentials or session. Use real IDs the tools return — never invent them. - "Created" != "live and spending" — verify before claiming. - Brand voice: terse, certain, doing the work. Show, don't sell. No banned language (no "AI-Powered", "seamless", "beta", hype CTAs). Never fabricate prices, competitors, or stats. - Get started or get help: /synter:quickstart and /synter:help. + +Use only exact directory tools exposed by the client. Check the live schema before supplying platform names, entity types, filters, or fields. Missing diagnostics are unverified; do not replace an unavailable tool with a superficially similar one. Follow the launch skill’s complete plan review, preflight, user approval, execution, and verification sequence for campaign changes. diff --git a/scripts/check-directory-tools.js b/scripts/check-directory-tools.js new file mode 100644 index 0000000..b7aee51 --- /dev/null +++ b/scripts/check-directory-tools.js @@ -0,0 +1,170 @@ +#!/usr/bin/env node +'use strict'; + +// Scan all snake_case identifiers, including prose and fenced examples. The +// field vocabulary below is data, not a second tool allowlist. Invoking a field +// as a function or MCP-qualified name still treats it as a tool reference. +const fs = require('node:fs'); +const path = require('node:path'); +const DATA_IDENTIFIERS = new Set([ + 'ad_personalization', 'ad_storage', 'ad_user_data', 'analytics_storage', + 'conversion_id', 'custom_label', 'event_id', 'google_product_category', + 'identifier_exists', 'li_fat_id', 'openai_ads', 'order_id', 'paid_social', + 'product_type', 'rdt_cid', 'sale_price', 'transaction_id', 'utm_campaign', + 'utm_content', 'utm_medium', 'utm_source', 'utm_term', +]); +const ROOT = path.resolve(__dirname, '..'); +const SURFACES = ['skills', 'commands', 'rules', 'agents']; +const ENDPOINTS = new Set(['https://mcp.synterai.com', 'https://mcp.syntermedia.ai']); + +function extractReferences(source) { + const refs = []; + source.split(/\r?\n/).forEach((line, index) => { + const names = new Set(); + // Explicit syntax also supports future tool names without underscores. + for (const match of line.matchAll(/\bmcp__[\w-]+?__([\w-]+)|\btool:([\w-]+)/g)) { + names.add(match[1] || match[2]); + } + const unqualified = line.replace(/\bmcp__[\w-]+?__[\w-]+/g, ''); + for (const match of unqualified.matchAll(/\b[a-z][a-z0-9]*(?:_[a-z0-9]+)+\b/g)) { + const name = match[0]; + const invoked = /^(?:\s*\(|`\()/.test(unqualified.slice(match.index + name.length)); + if (!DATA_IDENTIFIERS.has(name) || invoked) names.add(name); + } + // Tool templates cannot be verified against an exact-name directory. + if (/\b(?:[a-z]+_)*(?:<[^>]+>|\*)_[a-z_]+\b|\b[a-z]+_\*/.test(line)) { + names.add('[dynamic tool template: spell out exact tool names]'); + } + for (const name of names) refs.push({ name, line: index + 1 }); + }); + return refs; +} + +function listFiles(dir) { + if (!fs.existsSync(dir)) return []; + return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => { + const filename = path.join(dir, entry.name); + if (entry.isSymbolicLink()) throw new Error(`Instruction symlinks are not supported: ${filename}`); + return entry.isDirectory() ? listFiles(filename) : [filename]; + }); +} + +function readAllowlist(root) { + const names = JSON.parse(fs.readFileSync(path.join(root, 'tools/directory-tools.json'), 'utf8')); + if (!Array.isArray(names) || names.length !== 34 || new Set(names).size !== 34 || + names.some(name => typeof name !== 'string' || !/^[a-z][a-z0-9_]+$/.test(name))) { + throw new Error('tools/directory-tools.json must contain 34 unique tool names.'); + } + return new Set(names); +} + +function scan(root = ROOT) { + const allowed = readAllowlist(root); + const errors = []; + const references = new Set(); + const files = SURFACES.flatMap(surface => listFiles(path.join(root, surface))); + if (!files.length) throw new Error('No instruction files found.'); + for (const filename of files) { + for (const ref of extractReferences(fs.readFileSync(filename, 'utf8'))) { + references.add(ref.name); + if (!allowed.has(ref.name)) errors.push(`${path.relative(root, filename)}:${ref.line}: unlisted tool ${ref.name}`); + } + } + return { allowed, references, errors, fileCount: files.length }; +} + +async function readLiveTools({ token, url = 'https://mcp.synterai.com', fetchImpl = fetch }) { + if (!token) throw new Error('Live check requires SYNTER_MCP_TOKEN (an OAuth access token).'); + if (!ENDPOINTS.has(url)) throw new Error('Live check URL must be one of the two production Synter MCP endpoints.'); + let session; + let protocol = '2025-03-26'; + let requestId = 0; + async function rpc(method, params, notification = false) { + const id = notification ? undefined : ++requestId; + const headers = { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + Accept: 'application/json, text/event-stream', + 'MCP-Protocol-Version': protocol, + }; + if (session) headers['Mcp-Session-Id'] = session; + const response = await fetchImpl(url, { + method: 'POST', headers, redirect: 'error', signal: AbortSignal.timeout(30000), + body: JSON.stringify({ jsonrpc: '2.0', ...(notification ? {} : { id }), method, params }), + }); + // Never print response bodies or headers: they may contain credentials. + if (!response.ok) throw new Error(`${method}: HTTP ${response.status}`); + session = response.headers.get('mcp-session-id') || session; + if (notification) { await response.body?.cancel(); return; } + const body = await response.text(); + let messages; + const parse = value => { + try { return JSON.parse(value); } + catch { throw new Error(`${method}: invalid JSON response`); } + }; + if ((response.headers.get('content-type') || '').includes('text/event-stream')) { + messages = body.split(/\r?\n\r?\n/).flatMap(event => { + const data = event.split(/\r?\n/).filter(line => line.startsWith('data:')) + .map(line => line.slice(5).trimStart()).join('\n'); + return data ? [parse(data)] : []; + }); + } else { + messages = [parse(body)]; + } + const message = messages.find(item => item.id === id); + if (!message || message.jsonrpc !== '2.0' || message.error || !message.result) { + throw new Error(`${method}: invalid or unsuccessful JSON-RPC response`); + } + return message.result; + } + const init = await rpc('initialize', { + protocolVersion: protocol, capabilities: {}, + clientInfo: { name: 'synter-directory-validator', version: '1.0.0' }, + }); + if (typeof init.protocolVersion !== 'string') throw new Error('initialize: missing protocol version'); + protocol = init.protocolVersion; + await rpc('notifications/initialized', {}, true); + const names = new Set(); + const cursors = new Set(); + let cursor; + do { + const result = await rpc('tools/list', cursor ? { cursor } : {}); + if (!Array.isArray(result.tools)) throw new Error('tools/list: missing tools array'); + for (const tool of result.tools) { + if (typeof tool.name !== 'string' || !tool.name || names.has(tool.name)) { + throw new Error('tools/list: missing or duplicate tool name'); + } + names.add(tool.name); + } + cursor = result.nextCursor; + if (cursor !== undefined && (typeof cursor !== 'string' || !cursor || cursors.has(cursor))) { + throw new Error('tools/list: invalid or repeated pagination cursor'); + } + if (cursor) cursors.add(cursor); + if (cursors.size > 100) throw new Error('tools/list: pagination limit exceeded'); + } while (cursor); + return names; +} + +async function main() { + const args = process.argv.slice(2); + if (args.some(arg => arg !== '--live')) throw new Error('Usage: node scripts/check-directory-tools.js [--live]'); + const result = scan(); + if (result.errors.length) throw new Error(result.errors.join('\n')); + if (args.includes('--live') || process.env.SYNTER_MCP_TOKEN) { + const live = await readLiveTools({ token: process.env.SYNTER_MCP_TOKEN, url: process.env.SYNTER_MCP_URL }); + const missing = [...result.allowed].filter(name => !live.has(name)); + if (missing.length) throw new Error(`Live server is missing directory tools: ${missing.join(', ')}`); + console.log(`Live tools/list contains all ${result.allowed.size} directory tools.`); + } + console.log(`Checked ${result.fileCount} instruction files: ${result.references.size} referenced tools, all in the ${result.allowed.size}-tool directory.`); +} + +module.exports = { extractReferences, scan, readLiveTools }; +if (require.main === module) main().catch(error => { + // Mask the OAuth token even if a lower-level library includes it in an error. + const message = process.env.SYNTER_MCP_TOKEN + ? error.message.split(process.env.SYNTER_MCP_TOKEN).join('[redacted]') : error.message; + console.error(message); + process.exitCode = 1; +}); diff --git a/scripts/check-directory-tools.test.js b/scripts/check-directory-tools.test.js new file mode 100644 index 0000000..7ea0295 --- /dev/null +++ b/scripts/check-directory-tools.test.js @@ -0,0 +1,114 @@ +'use strict'; +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { spawnSync } = require('node:child_process'); +const { extractReferences, scan, readLiveTools } = require('./check-directory-tools'); + +test('extracts tools from prose, tables, functions, fences, and MCP qualification', () => { + const source = [ + 'Call `ga4_get_report` after list_connected_accounts.', + '| `invented_tool` | unsupported |', + '```js', + 'await mcp__synter__another_unknown({ event_id: "example" });', + 'dispatch("public_ad_library_intelligence", {});', + '```', + 'Call tool:singleword or mcp__synter__hyphen-tool.', + 'event_id is a data field, but event_id() is a call.', + ].join('\n'); + assert.deepEqual(extractReferences(source), [ + { name: 'ga4_get_report', line: 1 }, { name: 'list_connected_accounts', line: 1 }, + { name: 'invented_tool', line: 2 }, { name: 'another_unknown', line: 4 }, + { name: 'public_ad_library_intelligence', line: 5 }, + { name: 'singleword', line: 7 }, { name: 'hyphen-tool', line: 7 }, + { name: 'event_id', line: 8 }, + ]); +}); + +test('data fields are not tools, but dynamic tool templates fail closed', () => { + assert.deepEqual(extractReferences('Use `utm_source`, `paid_social`, and `event_id` fields.'), []); + assert.deepEqual(extractReferences('Call `event_id`()'), [{ name: 'event_id', line: 1 }]); + assert.match(extractReferences('Use `pull__ads_performance`')[0].name, /dynamic tool template/); + assert.match(extractReferences('Run `get_*`')[0].name, /dynamic tool template/); +}); + +test('checks nested files in every instruction surface, including optional commands', t => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'synter-directory-test-')); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + fs.mkdirSync(path.join(root, 'tools')); + fs.copyFileSync(path.join(__dirname, '../tools/directory-tools.json'), path.join(root, 'tools/directory-tools.json')); + for (const surface of ['skills', 'commands', 'rules', 'agents']) { + fs.mkdirSync(path.join(root, surface, 'nested'), { recursive: true }); + fs.writeFileSync(path.join(root, surface, 'nested/instructions.md'), `Use list_campaigns and unknown_${surface}.`); + } + const result = scan(root); + assert.equal(result.fileCount, 4); + assert.equal(result.errors.length, 4); + for (const surface of ['skills', 'commands', 'rules', 'agents']) { + assert.ok(result.errors.some(error => error.includes(`unknown_${surface}`))); + } + fs.mkdirSync(path.join(root, 'scripts')); + const script = path.join(root, 'scripts/check-directory-tools.js'); + fs.copyFileSync(path.join(__dirname, 'check-directory-tools.js'), script); + const cli = spawnSync(process.execPath, [script], { encoding: 'utf8' }); + assert.equal(cli.status, 1); + assert.match(cli.stderr, /unlisted tool unknown_commands/); + fs.writeFileSync(path.join(root, 'tools/directory-tools.json'), '["list_campaigns", "list_campaigns"]'); + assert.throws(() => scan(root), /34 unique tool names/); +}); + +function response(id, result, { sse = false, session = false } = {}) { + const message = JSON.stringify({ jsonrpc: '2.0', id, result }); + return new Response(sse ? `event: message\r\ndata: ${message}\r\n\r\n` : message, { + headers: { + 'Content-Type': sse ? 'text/event-stream' : 'application/json', + ...(session ? { 'Mcp-Session-Id': 'test-session' } : {}), + }, + }); +} + +test('live check initializes, passes session headers, parses JSON/SSE, and paginates', async () => { + const calls = []; + const names = await readLiveTools({ token: 'test-oauth-token', fetchImpl: async (url, options) => { + const body = JSON.parse(options.body); + calls.push(body); + assert.equal(url, 'https://mcp.synterai.com'); + assert.equal(options.headers.Authorization, 'Bearer test-oauth-token'); + assert.equal(options.redirect, 'error'); + if (body.method === 'initialize') return response(body.id, { protocolVersion: '2025-03-26' }, { session: true }); + assert.equal(options.headers['Mcp-Session-Id'], 'test-session'); + if (body.method === 'notifications/initialized') { + assert.equal(body.id, undefined); + return new Response(null, { status: 202 }); + } + assert.equal(body.method, 'tools/list'); + if (!body.params.cursor) return response(body.id, { tools: [{ name: 'list_campaigns' }], nextCursor: 'page-2' }, { sse: true }); + assert.equal(body.params.cursor, 'page-2'); + return response(body.id, { tools: [{ name: 'get_connection_status' }] }); + } }); + assert.deepEqual([...names], ['list_campaigns', 'get_connection_status']); + assert.equal(calls.length, 4); +}); + +test('live check rejects missing tokens, untrusted endpoints, and failed authentication', async () => { + await assert.rejects(readLiveTools({ token: '' }), /requires SYNTER_MCP_TOKEN/); + await assert.rejects(readLiveTools({ token: 'secret', url: 'https://other.example' }), /production Synter/); + await assert.rejects(readLiveTools({ token: 'secret', fetchImpl: async () => new Response('sensitive body', { status: 401 }) }), /^Error: initialize: HTTP 401$/); +}); + +test('live check rejects malformed results and looping cursors', async () => { + for (const mode of ['missing-tools', 'looping-cursor', 'rpc-error', 'duplicate-tools']) { + const fetchImpl = async (url, options) => { + const { method, id } = JSON.parse(options.body); + if (method === 'initialize') return response(id, { protocolVersion: '2025-03-26' }); + if (method === 'notifications/initialized') return new Response(null, { status: 202 }); + if (mode === 'missing-tools') return response(id, {}); + if (mode === 'rpc-error') return new Response(JSON.stringify({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Unavailable' } })); + if (mode === 'duplicate-tools') return response(id, { tools: [{ name: 'list_campaigns' }, { name: 'list_campaigns' }] }); + return response(id, { tools: [], nextCursor: 'same-page' }); + }; + await assert.rejects(readLiveTools({ token: 'secret', fetchImpl }), /missing tools|repeated pagination|JSON-RPC response|duplicate tool/); + } +}); diff --git a/scripts/session-context.sh b/scripts/session-context.sh index d2faf12..dfbb6b3 100755 --- a/scripts/session-context.sh +++ b/scripts/session-context.sh @@ -7,9 +7,9 @@ set -euo pipefail cat <<'EOF' [Synter plugin active] You are the Synter operator — the AI Agent Operator for Ads. You direct, the agents execute. Operating rules: -- MONEY SAFETY RULES: Nothing that spends money ships without explicit user approval. No exceptions, no tier carve-outs. +- MONEY SAFETY RULES: Nothing that spends money ships without explicit user approval. No exceptions. - Default to recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. -- Reads are free: list_*, get_*, pull_*, verify_*, and reports run without asking. +- Read-only reports and account lookups run without asking. - Confirm the org/account before any write (list_connected_accounts). Never run one org's campaigns through another's credentials or session. Use real IDs the tools return — never invent them. - "Created" != "live and spending" — verify before claiming. - Brand voice: terse, certain, doing the work. Show, don't sell. No banned language (no "AI-Powered", "seamless", "beta", hype CTAs). Never fabricate prices, competitors, or stats. diff --git a/scripts/validate-manifests.js b/scripts/validate-manifests.js index 5ac73da..77585d2 100644 --- a/scripts/validate-manifests.js +++ b/scripts/validate-manifests.js @@ -111,30 +111,36 @@ if (fs.existsSync(SKILLS_DIR)) { errors.push('skills/: directory not found'); } -// (d) Authentication guards — Claude's public directory requires OAuth for -// authenticated remote MCP services. Cursor uses the same OAuth-only remote. -const claudeMcp = manifests['.mcp.json']; -const claudeServer = claudeMcp && claudeMcp.mcpServers && claudeMcp.mcpServers.synter; -if (!claudeServer || claudeServer.type !== 'http' || claudeServer.url !== 'https://mcp.synterai.com') { - errors.push('.mcp.json: Synter must use the production HTTPS remote MCP endpoint'); -} -if (claudeServer && claudeServer.headers) { - errors.push('.mcp.json: Claude plugin must use browser OAuth, not static request headers'); -} -if (pluginJson && pluginJson.userConfig && pluginJson.userConfig.synter_api_key) { - errors.push('.claude-plugin/plugin.json: Claude plugin must not collect a static Synter API key'); -} - -const cursorMcp = manifests['mcp.json']; -const cursorServer = cursorMcp && cursorMcp.mcpServers && cursorMcp.mcpServers.synter; -if (!cursorServer || cursorServer.type !== 'http' || cursorServer.url !== 'https://mcp.synterai.com') { - errors.push('mcp.json: Synter must use the production HTTPS remote MCP endpoint'); -} -if (cursorServer && cursorServer.headers) { - errors.push('mcp.json: Cursor plugin must use browser OAuth, not static request headers'); +// (d) Each client has its own OAuth-only remote URL. Claude must match the +// connector under review; Cursor points at the Synter AI endpoint. +for (const [configPath, expectedUrl] of [ + ['.mcp.json', 'https://mcp.syntermedia.ai'], + ['mcp.json', 'https://mcp.synterai.com'], +]) { + const config = manifests[configPath]; + const servers = config && config.mcpServers; + const server = servers && servers.synter; + if (!servers || Object.keys(servers).length !== 1 || !server || + server.type !== 'http' || server.url !== expectedUrl) { + errors.push(`${configPath}: expected a single Synter HTTP server at ${expectedUrl}`); + } + if (server && Object.keys(server).some(key => !['type', 'url', 'description'].includes(key))) { + errors.push(`${configPath}: OAuth-only remote configuration permits only type, url, and description; no headers, env, commands, or credential fields`); + } } -if (manifests['.cursor-plugin/plugin.json'] && manifests['.cursor-plugin/plugin.json'].variables) { - errors.push('.cursor-plugin/plugin.json: Cursor plugin must not collect a static Synter API key'); +for (const manifestPath of ['.claude-plugin/plugin.json', '.cursor-plugin/plugin.json']) { + const manifest = manifests[manifestPath]; + if (!manifest) continue; + if (manifest.userConfig || manifest.variables || manifest.env || manifest.headers) { + errors.push(`${manifestPath}: do not collect static credentials or declare authentication headers`); + } + const expectedConfig = manifestPath.startsWith('.cursor') ? './mcp.json' : './.mcp.json'; + if (manifest.mcpServers !== undefined && manifest.mcpServers !== expectedConfig) { + errors.push(`${manifestPath}: mcpServers must reference ${expectedConfig}`); + } + if (manifestPath.startsWith('.cursor') && manifest.mcpServers !== expectedConfig) { + errors.push(`${manifestPath}: missing Cursor mcpServers reference`); + } } if (errors.length > 0) { diff --git a/sdk/README.md b/sdk/README.md index 234b641..a3a49c8 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -1,55 +1,15 @@ -# Synter agent runner (Claude Agent SDK) +# Optional Claude Agent SDK runner -A headless runner that loads the Synter plugin and runs the operator unattended — for cron jobs, pipelines, or embedding in your own app. Built on [`@anthropic-ai/claude-agent-sdk`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). - -The plugin (skills, agents, hooks, brand voice) is loaded with `plugins: [{ type: "local", path }]`; the Synter MCP is supplied from your environment. - -## Run +This manually invoked Node runner loads the local plugin, including its Claude remote MCP configuration and session hook. It is not started by installation and is excluded from release zips. ```bash cd sdk npm install -export SYNTER_API_KEY=syn_your_api_key_here # get one at syntermedia.ai/developer -export ANTHROPIC_API_KEY=sk-ant-... # or use your Claude Code auth - -node synter-agent.mjs "How's my spend this week, and anything wasted?" node synter-agent.mjs "/synter:report last 7 days" ``` -## Safety — read-only by default - -No human is present to approve actions, so the runner is **read-only**: - -- **Allowed automatically:** read tools — `list_*`, `get_*`, `pull_*`, `ga4_*`, `reconcile_platforms`, `forecast_campaign`, `find_audience_signals`, `verify_*`, plus `Read`/`Glob`/`Grep`/web search. -- **Denied:** anything that spends money or mutates an account — `create_*`, `enable_*`, `update_*_budget`, `pause_*`, `execute*`, `launch*`, `sync_*`, `attach_*`, and local `Write`/`Edit`/`Bash`. The agent is told to report what it *would* do instead. - -Lift the guard only when you mean it: - -```bash -node synter-agent.mjs --allow-writes "Pause anything with zero conversions and $50+ spend in 14 days" -``` +Use the host SDK’s supported Claude authentication. Synter uses the plugin’s browser OAuth connection at `https://mcp.syntermedia.ai`. Establish that connection interactively in the host environment first. An unattended process cannot complete a browser prompt; if the host does not reuse the authorized session, run the task interactively. OAuth session reuse depends on the host and must be verified before scheduling this runner. -`--allow-writes` lets the agent spend real money. Prefer running interactively in Claude Code (`/plugin install synter@synter`) so you approve each action. - -## Cron example - -```cron -# 7am daily: read-only spend digest to stdout (pipe to Slack/email in your wrapper) -0 7 * * * cd /path/to/plugin/sdk && SYNTER_API_KEY=... node synter-agent.mjs "/synter:report yesterday" >> /var/log/synter.log 2>&1 -``` - -## Python - -The same plugin loads from the Python SDK (`pip install claude-agent-sdk`): - -```python -from claude_agent_sdk import query, ClaudeAgentOptions - -async for message in query( - prompt="/synter:report last 7 days", - options=ClaudeAgentOptions(plugins=[{"type": "local", "path": ".."}]), -): - ... -``` +The runner permits the explicitly listed read tools and blocks other Synter tools by default. It also blocks local write and shell tools. Review `synter-agent.mjs` before use. The existing `--allow-writes` option removes the runner’s mutation guard; campaign instructions still require the user’s explicit approval of the action scope. Prefer interactive use when campaign changes need review. -Add your own `can_use_tool` guard to enforce the same read-only default. +The runner supplies no separate MCP credentials or authentication headers. Never put tokens in prompts or source files. See [support](https://syntermedia.ai/support) for OAuth connection issues. diff --git a/sdk/synter-agent.mjs b/sdk/synter-agent.mjs index 35557a8..8dfd5dd 100644 --- a/sdk/synter-agent.mjs +++ b/sdk/synter-agent.mjs @@ -3,8 +3,8 @@ // plugin (skills + agents + hooks + MCP) and runs the operator unattended. // // Usage: -// SYNTER_API_KEY=syn_... node sdk/synter-agent.mjs "How's my spend this week?" -// SYNTER_API_KEY=syn_... node sdk/synter-agent.mjs "/synter:report last 7 days" +// node sdk/synter-agent.mjs "How's my spend this week?" +// node sdk/synter-agent.mjs "/synter:report last 7 days" // // Safety: by default this runner is READ-ONLY. Read tools run automatically; // anything that spends money or mutates a campaign is DENIED, because no human @@ -25,24 +25,36 @@ const prompt = argv.filter((a) => a !== "--allow-writes").join(" ").trim() || "Give me a quick status: what ad accounts are connected, and any obvious wins or wasted spend in the last 7 days."; -const apiKey = process.env.SYNTER_API_KEY || ""; -if (!apiKey) { - console.error( - "[synter] No SYNTER_API_KEY set — only free GA4 + onboarding tools will work. Get a key at syntermedia.ai/developer." - ); -} - // Read-only Synter MCP tools (safe to run unattended). Everything else that // touches an account is treated as a mutation and blocked unless --allow-writes. -const READ_ONLY = [ - /^mcp__synter__(list_|get_|pull_|ga4_|run_gaql_query$|reconcile_platforms$|forecast_campaign$|find_audience_signals$|similarweb_|measure_incrementality$|synter_onboarding_status$|verify_)/, -]; +const READ_ONLY = new Set([ + "get_connection_status", + "list_connected_accounts", + "verify_platform_accounts", + "list_campaigns", + "pull_google_ads_performance", + "pull_meta_ads_performance", + "pull_linkedin_ads_performance", + "pull_microsoft_ads_performance", + "pull_reddit_ads_performance", + "ga4_get_properties", + "ga4_get_report", + "ga4_get_conversions", + "get_attribution", + "get_spend_reconciliation", + "get_pixel_health", + "list_audiences", + "list_creative_assets", + "get_campaign_plan", + "get_plan_execution", + "get_job_status" +]); const SAFE_BUILTINS = /^(Read|Glob|Grep|WebFetch|WebSearch|TodoWrite|Task)$/; function classify(toolName) { if (SAFE_BUILTINS.test(toolName)) return "allow"; - if (READ_ONLY.some((re) => re.test(toolName))) return "allow"; - if (toolName.startsWith("mcp__synter__")) return "mutation"; + if (toolName.startsWith("mcp__synter__") && READ_ONLY.has(toolName.slice("mcp__synter__".length))) return "allow"; + if (toolName.startsWith("mcp__")) return "mutation"; // Non-Synter writes (Edit/Write/Bash) — block by default in this runner. if (/^(Write|Edit|NotebookEdit|Bash)$/.test(toolName)) return "mutation"; return "allow"; // skills, agent dispatch, etc. @@ -62,15 +74,7 @@ async function canUseTool(toolName, input) { const options = { plugins: [{ type: "local", path: PLUGIN_ROOT }], - // Provide the Synter MCP from the environment for headless use. (Interactive - // installs get it from the plugin's userConfig instead.) - mcpServers: { - synter: { - type: "http", - url: "https://mcp.syntermedia.ai", - headers: apiKey ? { "X-Synter-Key": apiKey } : {}, - }, - }, + // The plugin supplies its OAuth-only Claude remote MCP configuration. canUseTool, maxTurns: 30, }; diff --git a/skills/ad-copy-generation/SKILL.md b/skills/ad-copy-generation/SKILL.md index 11fb79d..e6da60c 100644 --- a/skills/ad-copy-generation/SKILL.md +++ b/skills/ad-copy-generation/SKILL.md @@ -23,7 +23,7 @@ Apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md` to ev ## Generate and vary -Use `generate_text_ad` for volume, then edit for voice and accuracy. Produce enough distinct variants to test one variable at a time (see **creative-testing**). Never fabricate a price, claim, or statistic; if the real number is unknown, leave it out. +Use `create_ad_copy` for volume, then edit for voice and accuracy. Produce enough distinct variants to test one variable at a time (see **creative-testing**). Never fabricate a price, claim, or statistic; if the real number is unknown, leave it out. ## Check before it ships diff --git a/skills/ad-policy-compliance/SKILL.md b/skills/ad-policy-compliance/SKILL.md index 6320fc0..f1e6f2b 100644 --- a/skills/ad-policy-compliance/SKILL.md +++ b/skills/ad-policy-compliance/SKILL.md @@ -67,11 +67,11 @@ UNIVERSAL CHECKS (all platforms): | Violation | Example | Fix | |-----------|---------|-----| -| Excessive capitalization | "FREE SHIPPING on ALL ORDERS" | "Free Shipping on All Orders" | +| Excessive capitalization | "FAST SHIPPING on ALL ORDERS" | "Fast Shipping on All Orders" | | Excessive punctuation | "Amazing Deal!!!" | "Amazing Deal" | | Gimmicky use of symbols | "★★★ Best CRM ★★★" | Remove symbols from ad text | | Misleading ad | "Click Here ↓↓↓" | Use descriptive CTA | -| Non-standard spacing | "F R E E" | "Free" | +| Non-standard spacing | "S A L E" | "Sale" | | Phone number in ad text | "Call 1-800-555-1234" | Use call extension instead | | Unclear relevance | Ad about shoes → LP about insurance | Match ad to LP content | @@ -136,7 +136,7 @@ CLAIMS THAT TRIGGER REVIEW: SAFE ALTERNATIVES: "Best" → "Top-rated" or "Award-winning" (with award cited) - "Guaranteed" → "Risk-free trial" or "Money-back guarantee" + "Guaranteed" → a specific, substantiated description of the offer "#1" → "Leading" or "Trusted by [X,000]+" "Instant results" → "See results in as little as [timeframe]" ``` @@ -279,7 +279,7 @@ REQUIRED: ✅ Must not mimic TikTok UI elements (fake like buttons, etc.) ✅ Landing page must be functional and match ad claims ✅ Product availability must match targeted regions - ✅ Accurate pricing (no hidden fees) + ✅ Accurate offer terms (no omitted conditions) CREATIVE RULES: ✅ Sound is expected (unlike other platforms) — include audio @@ -567,46 +567,8 @@ X (TWITTER): ## Examples -### Example: Financial Services Ad Policy Review +### Example: Campaign policy review -```markdown -# Policy Review: Acme Loans Google Ads Campaign - -## Ad Copy Under Review -Headline 1: "Get a Personal Loan in Minutes" -Headline 2: "Rates from 4.99% APR" -Headline 3: "No Hidden Fees — Apply Now" -Description: "Borrow $5K-$50K with competitive rates. - Quick approval, funds in 24 hours." - -## Findings - -### ✅ PASS -- APR mentioned in headline (4.99%) -- No guaranteed approval language -- No income claims -- Professional tone - -### ⚠️ WARNINGS -1. "Rates from 4.99% APR" — Landing page must show: - - APR range (4.99% - X.XX%) - - Representative example with total repayment - - Terms and conditions link - -2. "Funds in 24 hours" — Must be accurate: - - If conditional, add "subject to approval" to LP - - Cannot guarantee timing in ad without qualification - -### ❌ REQUIRED FIXES -1. Landing page missing "Representative APR Example" - (required by Google for loan advertising) - Fix: Add example: "$10,000 loan at 7.99% APR, - 36 monthly payments of $313.11, total repayment $11,271.96" - -2. Google Ads financial services verification not completed - Fix: Complete advertiser identity verification at - ads.google.com/nav/selectaccount → Verification - -## Verdict: CONDITIONAL PASS -Fix landing page disclaimer + complete verification before launch. -``` +Record the advertiser, platform, final ad text, destination, and evidence supporting each claim. Flag unsupported promises or missing offer conditions. Ask the advertiser for verified terms instead of filling in examples that could be mistaken for an actual offer. + +Return the checks passed, the unresolved issues, and the changes required before submission. A policy review does not guarantee platform acceptance or authorize deployment. diff --git a/skills/agentic-loop-campaign-sandbox/SKILL.md b/skills/agentic-loop-campaign-sandbox/SKILL.md deleted file mode 100644 index 4be9da1..0000000 --- a/skills/agentic-loop-campaign-sandbox/SKILL.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -name: agentic-loop-campaign-sandbox -description: Run autonomous, goal-driven agentic loops powered by Google Gemini or Azure AI Foundry to research, build, preflight, and stage multi-platform ad campaigns in safe platform sandbox environments (Google Drafts, Meta PAUSED, LinkedIn DRAFT, TikTok Sandbox) with automated Subagent Verifier review gates. Use when a user wants to run an autonomous agentic loop to launch or test multi-platform campaign ideas safely in sandboxes. ---- - -# Agentic Loop Campaign Sandbox Skill - -How to run autonomous AI agentic loops to plan, build, audit, and stage multi-platform advertising campaigns across Google Ads, Meta, LinkedIn, TikTok, Reddit, Microsoft, and Amazon Advertising without risking live ad spend. - -## Core Architecture - -The Agentic Loop Campaign Sandbox uses a **4-Tier Sandbox Pipeline**: - -1. **Tier 1 — Git Worktree Sandbox:** Isolated local workspace branch (`.worktrees/campaign-goal`) created dynamically per task goal to isolate file edits and temporary plan artifacts. -2. **Tier 2 — Synter Staging Database:** Campaign hierarchies (Campaign -> Ad Sets -> Keywords -> Creative Ads) stored in Synter's staging database using `create_campaign_plan` and `upsert_plan_entity`. -3. **Tier 3 — Platform Sandbox & Draft Mode:** Staged directly into native platform test/draft endpoints: - - **Google Ads:** `CampaignDraft` / `ExperimentService` (Draft state, 0 live serve) - - **Meta Ads (FB/IG):** Developer Sandbox Ad Account with `status="PAUSED"` - - **LinkedIn Ads:** Campaign Manager Sandbox with `status="DRAFT"` - - **TikTok Ads:** Sandbox Advertiser Account with `operation_status="DISABLE"` - - **Reddit Ads:** Test Account with `configured_status="PAUSED"` - - **Microsoft Ads:** Sandbox API Mirroring - - **Amazon DSP / Ads:** Amazon Advertising Sandbox API endpoint -4. **Tier 4 — Subagent Verifier & Outer Loop Approval Gate:** Independent auditor subagent runs a 0–100 verification score before asking for explicit human approval to push live. - ---- - -## Operating Workflow - -### 1. Goal Engine & Decomposition -Define a clear `TaskGoal` with explicit, unambiguous acceptance criteria: -- Research audience signals and CPC/CPM benchmarks across requested platforms. -- Create structured multi-platform campaign hierarchy in Synter Staging DB. -- Stage campaign entities natively into platform Sandboxes / Draft states. -- Pass `campaign-preflight` safety audit (conversion tracking, fat-finger budget limits, geo-exclusions, ad policy compliance). - -### 2. Autonomous Tool Loop (Inner Loop) -The model (Google Gemini or Azure AI Foundry) iteratively executes tool actions: -- `synter_research_opportunity`: Fetch keyword volume, audience signals, and benchmark CPCs. -- `synter_create_campaign_plan`: Generate campaign plan hierarchy and budget allocations. -- `create_platform_sandbox_campaign`: Stage entities in platform draft states. -- `synter_preflight_check`: Run automated safety checks. - -### 3. Safety Guardrails & Circuit Breakers -- **Loop Detector:** Action signature hashing (`sha256(tool_name + args)`) trips a circuit breaker if the model repeats the exact tool call sequence 3 times. -- **Context Manager:** Enforces a 50,000 token budget limit and compacts conversation history. -- **Fat-Finger Budget Guard:** Hard stop if daily budget exceeds configured human safety limits (e.g. $500/day). - -### 4. Independent Review Cycle (Outer Loop) -When the inner loop concludes, an independent **Subagent Verifier** evaluates the generated work against the goal's acceptance criteria: -- **Verifier Score >= 90/100:** Verification Passed ✅ -> Staged in Sandbox. -- **Verifier Score < 90/100:** Auto-Fix Triggered 🔄 -> Re-opens inner loop with verifier feedback. - ---- - -## Command Reference - -Run the Agentic Loop Campaign Sandbox from terminal or via Doppler: - -```bash -# Run with Doppler Doppler key management -cd /Users/joelhorwitz/personal/agentic-loop-system -doppler run --project synter-media --config dev -- npx tsx src/demo_synter.ts - -# Run interactive CLI in Guarded mode -node dist/cli.js --provider gemini --mode guarded --goal "Research & Stage Q3 B2B Campaign in Sandboxes" -``` diff --git a/skills/anomaly-detector/SKILL.md b/skills/anomaly-detector/SKILL.md index a916dd0..e931df3 100644 --- a/skills/anomaly-detector/SKILL.md +++ b/skills/anomaly-detector/SKILL.md @@ -5,13 +5,13 @@ description: Catch spend spikes, CTR or conversion-rate drops, budget runaway, a # Anomaly Detector -Find the sudden change before it burns a budget. Reads are free; setting alerts or pausing is a change and waits for approval. +Find the sudden change before it burns a budget. Reads do not require approval; setting alerts or pausing is a change and waits for approval. Confirm the account: `list_connected_accounts`. ## 1. Pull a baseline and today -Read a stable window and the recent period per platform: `pull__ads_performance(days=...)`, or `run_gaql_query` for Google detail. You need enough history to know what normal looks like before you can call something abnormal. +Read a stable historical window and the recent period with `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, or `pull_reddit_ads_performance`. State the period and missing data before calling a change abnormal. ## 2. Find real outliers, not noise @@ -26,7 +26,7 @@ Rule out the mundane first: a tracking outage reads as a conversion cliff; a new ## 4. Act -- Set forward-looking guardrails with `set_spend_alert` so the next runaway trips an alarm early. +- Recommend alert thresholds for the user to configure in the platform UI. Do not claim alerts were installed. - For a confirmed problem, recommend the fix (pause, budget cut, creative swap) and take it through the relevant skill on approval. Do not pause on one noisy data point; see **pre-pause-analysis**. ## Rules diff --git a/skills/attribution/SKILL.md b/skills/attribution/SKILL.md index ba5ca15..6ddee1f 100644 --- a/skills/attribution/SKILL.md +++ b/skills/attribution/SKILL.md @@ -11,7 +11,7 @@ Confirm the account: `list_connected_accounts`. ## 1. Get the cross-platform view -`get_attribution` for Synter's cross-platform picture, and `reconcile_platforms` to line up what each platform reports against a common source. Pull each platform's own numbers (`pull__ads_performance`) and GA4 (`ga4_run_report`) so you can see where they disagree and why. +Use `get_attribution` for the cross-platform attribution view and `get_spend_reconciliation` for spend differences. Compare conversion counts from `ga4_get_report` with `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, and `pull_reddit_ads_performance` for connected accounts. State what each source measures before interpreting differences. ## 2. Read the discrepancy honestly diff --git a/skills/audience-expansion-strategy/SKILL.md b/skills/audience-expansion-strategy/SKILL.md index c48ad00..b56e001 100644 --- a/skills/audience-expansion-strategy/SKILL.md +++ b/skills/audience-expansion-strategy/SKILL.md @@ -7,11 +7,11 @@ description: Expand targeting without wrecking CPA — lookalike ladders, seed q Expansion is a ladder, not a leap: each rung trades a little efficiency for a lot of reach, and you climb only when the rung below is saturated. -Confirm the account and inventory what exists: `list_connected_accounts`, `list_audiences`, and performance per audience via `pull__ads_performance`. +Confirm the account and inventory what exists: `list_connected_accounts`, `list_audiences`, and performance per audience via the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`). ## 1. Audit before expanding -Find which audiences drive 80% of conversions, which run above target CPA, and which show fatigue: frequency climbing past ~3-4/week, CTR down 20%+ week over week, CPA up 25%+ week over week. Expansion fixes saturation; it does not fix a broken offer or tired creative (see **creative**). +Find which audiences drive 80% of conversions, which run above target CPA, and which show fatigue: frequency climbing past ~3-4/week, CTR down 20%+ week over week, CPA up 25%+ week over week. Expansion fixes saturation; it does not fix a broken offer or tired creative (see **ad-copy-generation**). ## 2. Climb the expansion ladder @@ -24,17 +24,17 @@ Add one tier at a time with its own budget and CPA ceiling; kill any rung that e ## 3. Build lookalikes from quality seeds -Seed quality decides lookalike quality. Ranked: top-20%-by-LTV customers, then all converters, then intent signals (cart, demo, pricing page), then engagement signals. Practical minimums for good results are ~1,000 matched users on most platforms regardless of lower stated floors. Two upgrades that consistently cut lookalike CPA: seed from top-value customers instead of all customers, and seed from recent (90-day) converters instead of all-time. Refresh seeds monthly. +Seed quality decides lookalike quality. Ranked: top-20%-by-LTV customers, then all converters, then intent signals (cart, demo, offer page), then engagement signals. Practical minimums for good results are ~1,000 matched users on most platforms regardless of lower stated floors. Two improvements that consistently cut lookalike CPA: seed from top-value customers instead of all customers, and seed from recent (90-day) converters instead of all-time. Refresh seeds monthly. -Build with `build_lookalike_audience`, create several sizes (1%, 3%, 5%) and test them against each other, then attach to campaigns with `attach_audience` or spin up dedicated ones with `create_campaign_for_audience`. Note platform differences: Google retired similar audiences in favor of Optimized Targeting with Customer Match as the signal; Meta, TikTok, and LinkedIn still build explicit lookalike/predictive audiences. +Review existing seeds with `list_audiences`. Ask the account owner to create any required expansion audiences in the platform UI, then compare them in a controlled test. Use returned audience IDs in supported campaign-plan fields; follow **launch** for approval. ## 4. Layer for precision, exclude for hygiene -Layer dimensions where precision matters: lookalike AND in-market segment AND NOT recent site visitors (give retargeting first shot). For B2B account lists, use `build_abm_audience`; to discover new seed signals, `find_audience_signals`. +Describe the intended account criteria, exclusions, and layering. Use `research_campaign_opportunity` for available campaign research; hand off audience construction to the platform UI. Mandatory exclusions on every prospecting campaign: -- Existing customers and recent converters (sync weekly via `sync_audience`). +- Existing customers and recent converters; ask the account owner to refresh suppression lists weekly. - All retargeting pools — prospecting money should buy strangers. - Employees and CRM-disqualified leads. @@ -48,7 +48,7 @@ Meta Advantage+ audiences and Google Optimized Targeting treat your audiences as ## 6. Monitor and rebalance -Weekly: `pull__ads_performance` per audience tier. Scale rungs beating target, refresh creative on fatiguing ones, and rebalance spend across tiers with `optimize_budget`. When an expansion audience wins, feed its converters back into the seed pool — the ladder compounds. +Weekly: the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`) per audience tier. Scale rungs beating target, refresh creative on fatiguing ones, and rebalance spend across tiers with `optimize_budget`. When an expansion audience wins, feed its converters back into the seed pool — the ladder compounds. ## Rules diff --git a/skills/audience/SKILL.md b/skills/audience/SKILL.md index b73b19f..ca6d85e 100644 --- a/skills/audience/SKILL.md +++ b/skills/audience/SKILL.md @@ -1,38 +1,16 @@ --- name: audience -description: Build, analyze, and activate audiences — ABM target lists, lookalikes, signal-based segments, and first-party data — then sync them to ad platforms. Use when a user wants to define who to reach, build a target list, create a lookalike, or push an audience live. +description: Review existing audiences and plan account-specific targeting, exclusions, and activation requirements. --- -# Build & Activate Audiences +# Audience planning -Turn "who we want to reach" into a real, activated audience on the platforms. +Confirm the workspace and accounts with `list_connected_accounts`. Inspect existing audiences with `list_audiences`, preserving their returned IDs and platform scope. -## 1. Define the audience +Clarify who should see the campaign, which geographies are in scope, and which groups should be excluded. For first-party segments, document source, consent, recency, match quality, and suppression requirements. For expansion, define a seed and a test hypothesis without claiming a list has been built or synchronized. -Start from the user's description in plain English, then pick the right path: +Use `research_campaign_opportunity` to inform the campaign brief where the returned data supports it. Do not infer person-level records or audience membership from aggregate research. -- **ABM / named accounts or ICP** → `build_abm_audience` (company + person criteria). Discover accounts by buying signals — hiring, ad spend, funding, intent, install data. -- **Lookalike** → `build_lookalike_audience` from a seed (customers, converters, high-value list). -- **Signal-based** → `find_audience_signals` to discover the signals behind the idea, then build from the strongest. -- **First-party** → upload the user's list (emails/MAIDs). Canonicalize emails and hash before upload; tiny B2B work-email lists often fall below a platform's match floor — warn the user. +Audience creation, list upload, and synchronization require the platform’s own interface. Ask the user to complete those steps if needed, then re-read `list_audiences`. For supported campaign targeting fields, record existing audience IDs with `create_campaign_plan` and `upsert_plan_entity`; follow **launch** for preflight and approval. -## 2. Analyze before you activate - -- `list_audiences` to see what already exists — don't rebuild a duplicate. -- Profile the segment: size, match rate, overlap, and whether it clears each platform's minimum (LinkedIn company audiences and Reddit have real floors). Report the numbers honestly; a segment under the floor won't deliver. - -## 3. Activate - -- `sync_audience` / `attach_audience` to push to a platform and attach to campaigns/ad sets. -- For a fresh campaign targeting the segment, hand off to the **launch** skill (`create_campaign_for_audience`). -- `stage_audience_artifact` when you want the user to review the list before it goes live. - -## 4. Keep it living (optional) - -For an always-on motion, set the audience to refresh on a schedule so new signal matches flow in automatically. Tell the user what cadence you set and how to change it. - -## House rules - -- Respect match-rate floors — surface them, don't hide a too-small segment. -- First-party data: hash/normalize identifiers; never upload raw PII where a platform expects hashes. -- Confirm the org/account before activating. Reads are free; activation is an action — confirm scope first. +Report what exists, what is proposed, and what still needs manual setup. Never mix audiences from different workspaces. diff --git a/skills/bid-optimization/SKILL.md b/skills/bid-optimization/SKILL.md index e44b010..8321316 100644 --- a/skills/bid-optimization/SKILL.md +++ b/skills/bid-optimization/SKILL.md @@ -5,13 +5,13 @@ description: Recommend and apply bid and bid-strategy changes from performance d # Bid Optimization -Move bids toward the target CPA or ROAS using real performance, not guesses. Reads are free; changing bids spends differently, so confirm before shipping. +Move bids toward the target CPA or ROAS using real performance, not guesses. Reads do not require approval; changing bids spends differently, so confirm before shipping. Confirm the account: `list_connected_accounts`. ## 1. Read performance with enough data -Pull recent performance per campaign and, where available, per ad group and segment: `pull__ads_performance(days=...)` or `run_gaql_query` for Google detail. Do not act on thin data; a handful of conversions is noise. State the window and volume you based the call on. +Pull recent campaign performance with `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, or `pull_reddit_ads_performance`. Use segment detail only if returned; request platform exports for missing breakdowns. State the period and conversion volume before making a recommendation. ## 2. Diagnose before you move diff --git a/skills/campaign-preflight/SKILL.md b/skills/campaign-preflight/SKILL.md index 1ea3468..8f3fc5e 100644 --- a/skills/campaign-preflight/SKILL.md +++ b/skills/campaign-preflight/SKILL.md @@ -5,7 +5,7 @@ description: Pre-launch checks for an ad campaign — structure, conversion trac # Campaign Pre-Flight -Catch the mistakes that waste budget before a campaign goes live. Reads are free. Run the full checklist, report pass/warn/fail per item, and fix issues before enabling. Nothing that spends money ships without explicit approval. +Catch the mistakes that waste budget before a campaign goes live. Reads do not require approval. Run the full checklist, report pass/warn/fail per item, and fix issues before enabling. Nothing that spends money ships without explicit approval. Confirm the account first: `list_connected_accounts`. Use the real account and campaign IDs the tools return. @@ -13,8 +13,8 @@ Confirm the account first: `list_connected_accounts`. Use the real account and c A campaign that looks Active does not serve if pieces are missing. Read the real structure and confirm every level exists and is enabled: -- **Google:** `run_gaql_query` for campaigns, ad groups, ads, and keywords. Confirm ad groups exist, each has enabled ads, Search has keywords, and a budget is set. -- **Other platforms:** `list_campaigns(platform=...)` plus `pull__ads_performance` and the matching platform playbook skill to confirm ad sets/ad groups, ads, and budget. +- Use `audit_account_structure` and `get_campaign_plan` for available structure and asset details. Run `run_launch_preflight` on the plan. Confirm required ad groups, keywords, budgets, and assets only where the returned evidence supports them; obtain platform exports for missing detail. +- **Other platforms:** `list_campaigns(platform=...)` plus the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`) and the matching platform playbook skill to confirm ad sets/ad groups, ads, and budget. If a required level is missing, do not enable. Fix it first. @@ -22,7 +22,7 @@ If a required level is missing, do not enable. Fix it first. Without tracking, the spend is blind and smart bidding cannot work. -- `ga4_list_conversions` to confirm conversion actions exist. `get_gtm_tag` / `list_gtm_tags` to confirm the tag is present and published. `verify_pixel_ownership` to confirm the platform pixel is really wired. +- Use `ga4_get_conversions`, `ga4_get_report`, and `get_pixel_health` for available conversion evidence. A configured action does not prove that a tag is firing; mark missing verification explicitly. - No tracking on a conversion-optimized campaign is a stop. Start on a click or traffic goal until conversions are verified (roughly 15 to 30 conversions before smart bidding has enough signal). ## 3. UTMs on every ad (blocking) @@ -48,7 +48,7 @@ A campaign with no location criteria serves everywhere, including low-quality an A structurally complete campaign can still launch handicapped: an RSA created with the platform-minimum 3 headlines and 2 descriptions is accepted by the API but scores "Poor" on Google Ad Strength and gets throttled in the auction. -- Read each ad's actual asset counts (Google: `run_gaql_query` on `ad_group_ad` with the RSA headline/description fields). Compare against the floor: **11 headlines / 4 descriptions minimum, 15 / 4 recommended** per RSA. +- Read asset counts from `get_campaign_plan`, `list_creative_assets`, or a platform export. Compare each RSA against the plugin’s floor of 11 headlines and 4 descriptions, with 15/4 recommended. Do not claim a count you could not inspect. - Check Google's Ad Strength rating where available. **POOR or AVERAGE = fix the assets before launch**, not after. Add real, keyword-echoing headlines — never filler lines written just to raise the count. - Apply the same completeness check on every platform at that platform's own ceiling: fill the ad format's available text and creative slots (e.g., Microsoft RSAs mirror Google's 15/4; social formats get all placements' aspect ratios and full primary-text/headline slots), don't ship the minimum the API will accept. diff --git a/skills/campaign-structure-auditor/SKILL.md b/skills/campaign-structure-auditor/SKILL.md index cfd2dab..6f99231 100644 --- a/skills/campaign-structure-auditor/SKILL.md +++ b/skills/campaign-structure-auditor/SKILL.md @@ -7,7 +7,7 @@ description: Audit a Google Ads account's structure — ad group focus, keyword Good structure is why an account is cheap to run and easy to read. This audit finds the structural drag before it costs money. Reads only; fixes are separate, approved changes. -Confirm the account: `list_connected_accounts`, then read the real structure with `run_gaql_query` (campaigns, ad groups, keywords with match types, negatives) and `pull_google_ads_performance` for where spend actually sits. +Confirm the account with `list_connected_accounts`, inspect structure with `audit_account_structure`, and use `pull_google_ads_performance` to prioritize findings by spend. Request a platform export for any keyword, negative, or match-type details not returned. ## What to audit diff --git a/skills/competitor-analysis/SKILL.md b/skills/competitor-analysis/SKILL.md index e8b2db1..88161b3 100644 --- a/skills/competitor-analysis/SKILL.md +++ b/skills/competitor-analysis/SKILL.md @@ -9,7 +9,7 @@ See what competitors run so you can position against it, not copy it. Reads and ## 1. Find what they run -Use `public_ad_library_intelligence` where available for live and recent competitor creative, and web research for offers, landing pages, and positioning. Focus on what is running now and what has run long enough to imply it works. +Use `research_campaign_opportunity` for available research. Ask for public ad-library links or examples when the tool does not return competitor ads. Separate observed messaging from hypotheses about performance; longevity alone does not establish profitability. ## 2. Read the pattern, not the pixels @@ -28,4 +28,4 @@ Use `public_ad_library_intelligence` where available for live and recent competi - Inform strategy; never copy creative or claims. Copying inherits their weaknesses and risks trademark issues. - Separate what is proven (long-running, repeated) from what is a test. -- Report the angles and gaps you found and the specific move you would make, then take it to the **creative** or **launch** skill. +- Report the angles and gaps you found and the specific move you would make, then take it to the **ad-copy-generation** or **launch** skill. diff --git a/skills/connect/SKILL.md b/skills/connect/SKILL.md index 58dcbec..7c11463 100644 --- a/skills/connect/SKILL.md +++ b/skills/connect/SKILL.md @@ -1,35 +1,14 @@ --- name: connect -description: Connect ad platforms and data sources to Synter (Google, Meta, LinkedIn, Microsoft, Reddit, TikTok, X, Amazon, GA4, and more). Use when a user wants to link an account, says a platform isn't connected, hits an auth/reconnect error, or asks what's connected. +description: Connect ad accounts and GA4 through the Synter browser flow and verify the selected workspace. --- -# Connect Platforms +# Connect accounts -Link the user's ad accounts and analytics so the agents can read and act on real data. +Use `get_connection_status` and `list_connected_accounts` to identify the workspace and existing accounts. Report the platform, account name, and account ID. -## 1. Show current state +Direct the user to https://syntermedia.ai/settings/credentials to connect the required platform or GA4 account in the browser. Afterward, re-run `list_connected_accounts` and `verify_platform_accounts`. For GA4, confirm access with `ga4_get_properties`. Use `get_pixel_health` for the available tracking diagnostics; do not treat account access as proof of a working conversion event. -`list_connected_accounts` and `get_connection_status`. Report which platforms are live, the account IDs, and the org. Name what's missing. +If access fails, confirm the selected workspace and the connected user's platform permissions. Reconnect through browser OAuth when needed. Never collect keys, tokens, or authorization codes in chat. For accounts that need provisioning help, link https://syntermedia.ai/support. -## 2. Connect what they need - -Synter connects via direct API (OAuth where the platform supports it). To start a connection, direct the user to the connection flow at **syntermedia.ai/settings/credentials** (or **/settings/connections**), then have them complete the platform's OAuth in the browser. After they finish, re-run `list_connected_accounts` to confirm. - -Free and instant: connect **GA4** at syntermedia.ai/settings/credentials — then `ga4_list_properties()` works immediately, no credits. - -## 3. Verify, don't assume - -- After any connect, confirm with `list_connected_accounts` — show the exact account ID that came back. Never invent or guess account IDs. -- `verify_platform_accounts` / `verify_pixel_ownership` to confirm the account and tracking are really wired. - -## Troubleshooting - -- **"Reconnect over and over"** is usually a delegation/permissions issue on the platform side, not a broken token — check that the connected user actually has content/ad rights on that account before reconnecting again. -- **Auth error on a write** → the token may have expired; reconnect once, then verify with `list_connected_accounts`. -- A connection should be scoped to the **workspace/org**, not just one user. Confirm the right org is selected before acting. - -## House rules - -- Confirm the org before connecting or acting. Never mix one client's accounts with another's. -- Connecting is read-safe. Acting on the account (campaigns, budgets) still needs explicit approval. -- **Enterprise-gated platforms (LinkedIn Ads & StackAdapt)**: Access to **LinkedIn Ads** and **StackAdapt** requires upgrading to the **Enterprise plan** and booking a meeting with the Synter team for manual account setup and provisioning. When users without active provisioned connections ask for or attempt to use LinkedIn Ads or StackAdapt, do NOT give weird or technical connection error responses — state clearly that they need to upgrade to Enterprise and book a meeting. +Confirm the workspace again before any write. Keep account IDs, audiences, tracking, destinations, and credentials scoped to that workspace. diff --git a/skills/conversion-tracking/SKILL.md b/skills/conversion-tracking/SKILL.md index 11d2e86..7c13e57 100644 --- a/skills/conversion-tracking/SKILL.md +++ b/skills/conversion-tracking/SKILL.md @@ -1,36 +1,14 @@ --- name: conversion-tracking -description: Verify and fix conversion tracking before spend goes live — pixels, Conversions API, GA4 conversions, GTM tags, and that the right action is attached to the campaign. Use when a user sets up tracking, suspects conversions are not recording, or before launching or scaling anything. +description: Verify available conversion and pixel diagnostics before launch; hand off tag changes to the account owner. --- -# Conversion Tracking +# Conversion tracking -Tracking is the difference between optimization and guessing. No campaign should scale on blind spend. Reads are free; changing tags is a change, so confirm before shipping. +Confirm the account with `list_connected_accounts` and `verify_platform_accounts`. Use `ga4_get_properties` to select the right property, `ga4_get_conversions` to inspect configured conversions, and `ga4_get_report` to check recorded events over a stated period. Read `get_pixel_health` for available pixel diagnostics. -Confirm the account: `list_connected_accounts`. +A configured conversion is not proof that the right event fires or that the campaign optimizes toward it. Compare the intended business action with the evidence returned. Use `run_launch_preflight` for a campaign plan. Mark fields and checks unavailable from the tools as unverified. -## 1. Confirm what exists +For missing tags, event routing, or consent settings, describe the exact gap and have the account owner make the change in the platform or tag manager. This plugin does not publish tags or configure pixel destinations. -- `ga4_list_conversions` to see the GA4 conversion events and which are marked as conversions. -- `get_gtm_tag` / `list_gtm_tags` / `list_gtm_containers` to see the tags in place. -- `verify_pixel_ownership` and `get_pixel_destinations` to confirm the pixel is owned and where it sends data. - -## 2. Confirm it is attached and firing - -A conversion action that exists but is not attached to the campaign, or not firing, is worse than none because it looks fine. Confirm the campaign optimizes toward the right action, and that the action actually records on the real conversion event, not a pageview standing in for a purchase. - -## 3. Fix gaps before launch - -- Missing GA4 conversion → set it up (connect GA4 at syntermedia.ai/settings/credentials, free and instant). -- Missing or misfiring tag → `get_gtm_tag` / `update_gtm_tag_html` and `publish_gtm_container` on approval. -- Pixel and CAPI both matter on Meta; server-side improves match quality and resilience. Configure destinations with `configure_pixel_destination`. - -## 4. Re-verify - -After any change, re-run the checks and confirm a real conversion records end to end before trusting it. - -## Rules - -- No scaling on unverified tracking. Blind spend is worse than no spend. -- Prove the specific conversion action is attached and firing, do not assume from its presence. -- Report exactly what is tracked, what is attached, and any gap you fixed. Measurement pairs with the **attribution** skill. +After changes, repeat the reads and confirm a test conversion in the platform's own diagnostics. Do not launch or scale until required tracking checks pass. Report the workspace, account, property, conversion action, observed evidence, and remaining gaps. diff --git a/skills/creative-testing/SKILL.md b/skills/creative-testing/SKILL.md index 56dae36..b51b5a8 100644 --- a/skills/creative-testing/SKILL.md +++ b/skills/creative-testing/SKILL.md @@ -5,7 +5,7 @@ description: Structure and read creative A/B tests across platforms — set up v # Creative Testing -Test to learn, then scale the winner. Most "winners" called early are noise. Reads are free; launching variants spends money, so confirm before shipping. +Test to learn, then scale the winner. Most "winners" called early are noise. Reads do not require approval; launching variants spends money, so confirm before shipping. Confirm the account: `list_connected_accounts`. @@ -15,7 +15,7 @@ Change one variable per test (hook, image, headline, offer, format) so the resul ## 2. Set it up cleanly -Build variants with the **creative** skill or `generate_image_ad` / `generate_video_ad` / `generate_text_ad`, ship them in the same ad group / ad set so they compete on equal footing, and give each enough budget and audience to gather signal. Run **campaign-preflight** before launch. +Write text variants with `create_ad_copy` and select existing approved visual assets with `list_creative_assets`. Keep the comparison controlled and give each variant enough data. Follow **launch** and **campaign-preflight** before deploying a test. ## 3. Wait for enough data @@ -25,7 +25,7 @@ Build variants with the **creative** skill or `generate_image_ad` / `generate_vi ## 4. Read and act -Pull results per variant: `pull__ads_performance(level="ad")`. Pick the winner on the metric that matters (usually CPA or ROAS, not CTR). Scale it with the **optimize** skill, retire the losers, and feed what you learned into the next test. +Pull results per variant: the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`). Pick the winner on the metric that matters (usually CPA or ROAS, not CTR). Scale it with the **optimize** skill, retire the losers, and feed what you learned into the next test. ## Rules diff --git a/skills/creative/SKILL.md b/skills/creative/SKILL.md deleted file mode 100644 index ca24e3e..0000000 --- a/skills/creative/SKILL.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: creative -description: Generate on-brand ad creative — images, video, UGC, text/RSA copy, and voice — and add it to the creative library or a campaign. Use when a user wants new ads, creative variations, A/B test variants, or to refresh fatigued creative. ---- - -# Generate Creative - -Produce on-brand, policy-clean ad creative the user can ship. - -## 1. Brief - -Pin down: platform + format, objective, audience, the one message, and any must-have proof (a real number, a real outcome). Read brand rules at `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md` before writing a single line — voice and forbidden language are enforced, not optional. - -## 2. Generate - -- **Image** → `generate_image_ad` / `generate_image` (brand auto-applied) → returns an `asset_id`. -- **Video** → `generate_video_ad` / `generate_video`. -- **UGC** → `generate_ugc_ad`. -- **Text / RSA** → `generate_text_ad` (headlines + descriptions; write variants for testing). -- **Voice** → `generate_voice_ad`. - -Generate variants for a real test, not one-and-done. `list_creative_assets` to see the library; `upload_creative` to bring in the user's own assets. - -## 3. Check before it ships - -- **Brand**: approved language only. No banned words ("AI-Powered", "seamless", "revolutionary", "Try the beta", "Start Your Free Trial Today!"). Use the approved CTAs and verbs. -- **Accuracy**: never fabricate prices, competitors, stats, or claims. If you don't have the real number, say so — don't invent it. -- **Policy**: pre-check against the target platform's ad rules; flag anything restricted. - -## 4. Use it - -Hand the `asset_id` to the **launch** skill, attach to an existing campaign, or stage it for the user's review. Note: campaign creation needs a publicly fetchable image URL — confirm the asset resolves publicly before relying on it in a live ad. - -## House rules - -- Brand voice is mandatory. Terse, certain, doing the work — show, don't sell. -- Accuracy over polish. A fabricated price is worse than a plain one. -- Creative generation is fine to run; attaching to a live campaign that spends is an action — confirm first. diff --git a/skills/dayparting/SKILL.md b/skills/dayparting/SKILL.md index 39e02ab..67c211e 100644 --- a/skills/dayparting/SKILL.md +++ b/skills/dayparting/SKILL.md @@ -5,13 +5,13 @@ description: Analyze hour-of-day and day-of-week performance and set ad schedule # Dayparting and Ad Scheduling -Spend when the account converts, not evenly around the clock. Reads are free; schedule and modifier changes affect delivery, so confirm before shipping. +Spend when the account converts, not evenly around the clock. Reads do not require approval; schedule and modifier changes affect delivery, so confirm before shipping. Confirm the account: `list_connected_accounts`. ## 1. Read time-segmented performance -Pull performance by hour of day and day of week: `run_gaql_query` on the time segments (Google) or the platform performance pull with a date window long enough to be stable. You want conversions and CPA by time slot, not just clicks. +Review time-of-day and weekday performance only when the platform performance tool returns those breakdowns. Otherwise ask for a platform export. Compare conversions and CPA by time slot over a stable period, not just clicks. Schedule edits require the platform UI when no supported plan field exists. ## 2. Find the real pattern diff --git a/skills/executive-reporting/SKILL.md b/skills/executive-reporting/SKILL.md index 4a69e4b..6a96c34 100644 --- a/skills/executive-reporting/SKILL.md +++ b/skills/executive-reporting/SKILL.md @@ -7,7 +7,7 @@ description: Build C-suite and board ad reports — blended ROAS, month-over-mon Write for a reader who does not live in the ad accounts. Lead with the result and the decision, not the dashboard. Reads only; this produces a report, it does not change spend. -Confirm the account: `list_connected_accounts`, then pull real numbers across platforms: `pull__ads_performance(days=...)`, `get_attribution` for the cross-platform view, `ga4_run_report` for outcomes. Reconcile platform self-reports before quoting a total (see **attribution**). +Confirm the account: `list_connected_accounts`, then pull real numbers across platforms: the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`), `get_attribution` for the cross-platform view, `ga4_get_report` for outcomes. Reconcile platform self-reports before quoting a total (see **attribution**). ## What an exec report says @@ -21,7 +21,7 @@ Confirm the account: `list_connected_accounts`, then pull real numbers across pl - Narrative first, tables second. Every number earns its place; cut vanity metrics. - Round to what matters at their altitude; no false precision. -- Deliver as a clean summary, or generate a shareable doc with `create_google_doc` / `create_google_sheet` when they want to circulate it. For operator-level per-channel detail rather than the exec view, use the **report** skill. +- Deliver a copyable summary and table in the conversation. The plugin does not create external documents or send reports. Use **report** for operator-level per-channel detail. ## Rules diff --git a/skills/first-party-data-strategy/SKILL.md b/skills/first-party-data-strategy/SKILL.md index aaf09e7..d032e16 100644 --- a/skills/first-party-data-strategy/SKILL.md +++ b/skills/first-party-data-strategy/SKILL.md @@ -7,11 +7,11 @@ description: Build a post-cookie first-party data strategy — Enhanced Conversi Third-party cookies are gone; the accounts that keep measuring are the ones that collect, consent, and send their own data. Build that pipeline in order: audit, capture, consent, activate. -Confirm the account and tracking surface first: `list_connected_accounts`, `get_connection_status`, and `get_pixel_destinations` to see what already exists. +Confirm the workspace with `list_connected_accounts` and `get_connection_status`; read `get_pixel_health` for the available tracking diagnostics. ## 1. Audit what you collect today -Inventory every point where you capture an identifier: forms, signups, purchases, email lists, CRM records, in-app events. For each, note the identifier (email, phone, address), consent status, and freshness. Check what conversion data actually reaches the platforms: `ga4_list_conversions` and `ga4_run_report` for the site side, `pull__ads_performance` for what each platform claims. Big gaps between GA4 and platform numbers usually mean tracking loss, not attribution magic — see **conversion-tracking**. +Inventory every point where you capture an identifier: forms, signups, purchases, email lists, CRM records, in-app events. For each, note the identifier (email, phone, address), consent status, and freshness. Check what conversion data actually reaches the platforms: `ga4_get_conversions` and `ga4_get_report` for the site side, the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`) for what each platform claims. Big gaps between GA4 and platform numbers usually mean tracking loss, not attribution magic — see **conversion-tracking**. ## 2. Set up Enhanced Conversions and CAPI @@ -21,11 +21,11 @@ Hashed first-party identifiers recover conversions that cookies alone miss. - **Meta Conversions API:** send server-side events with hashed identifiers plus `fbc`/`fbp` cookie values, and deduplicate against the browser pixel with a shared `event_id`. Typical recovery is 15-30%. - LinkedIn, TikTok, and Reddit have equivalent server-side conversion APIs; the same hashing and dedup rules apply. -Inspect and edit the tag layer directly: `list_gtm_tags`, `get_gtm_tag`, `update_gtm_tag_html`, then `publish_gtm_container`. Verify pixel ownership before touching anything with `verify_pixel_ownership`, and route events to platforms with `configure_pixel_destination`. +Inspect the available diagnostics with `get_pixel_health`. Have the account owner make tag, event-routing, and consent changes in the platform UI or tag manager, then verify with a test event. The plugin cannot publish tags. ## 3. Move tracking server-side -Client-only tags lose data to ad blockers and 7-day browser cookie caps. A server-side GTM container on a first-party subdomain (track.yourdomain.com) extends cookie lifespan to 1-2 years, enriches events before forwarding (LTV, customer segment), and sends once to every platform API. This is the single highest-leverage tracking upgrade for a returning-visitor business. +Client-only tags lose data to ad blockers and 7-day browser cookie caps. A server-side GTM container on a first-party subdomain (track.yourdomain.com) extends cookie lifespan to 1-2 years, enriches events before forwarding (LTV, customer segment), and sends once to every platform API. This is the single highest-leverage tracking improvement for a returning-visitor business. ## 4. Implement Consent Mode v2 @@ -44,14 +44,14 @@ Uploaded lists only work as well as they match. Rough expectations: hashed email Collected data earns its keep in audiences: -- Sync customer and converter lists to platforms with `sync_audience` and attach them with `attach_audience`. -- Seed expansion from your best customers: `build_lookalike_audience` (see **audience** for the full playbook). +- Review available lists with `list_audiences`; the account owner handles synchronization and suppression updates in the platform UI. +- Define a seed from the best customers and hand off its construction to the platform UI; see **audience**. - Always maintain suppression lists — existing customers and recent converters excluded from acquisition. Refresh at least weekly. - Pass conversion value where you have it so value-based bidding has something to optimize toward (see **bid-optimization**). ## 7. Verify the pipeline end to end -After changes, confirm events flow: `ga4_run_report` for site-side counts, `pull__ads_performance` for platform-side, and `reconcile_platforms` to compare. Then check attribution quality with `get_attribution` — see **attribution** for interpreting the deltas. +After changes, compare site events with `ga4_get_report` and available platform performance results, then inspect attribution differences with `get_attribution`. Use `get_spend_reconciliation` for spend records separately. Mark unsupported event-level checks as unverified. ## Rules diff --git a/skills/geo-integrity-audit/SKILL.md b/skills/geo-integrity-audit/SKILL.md index 3deffa1..abdecd6 100644 --- a/skills/geo-integrity-audit/SKILL.md +++ b/skills/geo-integrity-audit/SKILL.md @@ -1,85 +1,16 @@ --- name: geo-integrity-audit -description: Verify that ad campaigns actually target the geographies they claim — location criteria exist, presence-only matching is set, and click geography matches intent. Use when auditing campaigns, investigating cheap clicks or missing conversions, or reviewing any campaign before and after launch. +description: Check intended geography against available campaign settings and delivery evidence; identify unverified targeting before launch. --- -# Geo Integrity Audit +# Geo integrity audit -A campaign named "US" is not a campaign targeted to the US; the only evidence of geo targeting is the configured criteria and the actual delivery data, so check both. +Confirm the workspace with `list_connected_accounts`, then read `list_campaigns` and `audit_account_structure`. For a planned campaign, use `get_campaign_plan` and `run_launch_preflight`. -Confirm the account: `list_connected_accounts`. +Compare the intended geography with the location criteria, exclusions, and presence settings actually returned. A campaign name is not targeting evidence. Check every ad set or ad group when the data is available. Missing fields mean UNVERIFIED, not worldwide delivery or a passed check. -## 1. Verify location criteria exist +Use `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, or `pull_reddit_ads_performance` for delivery evidence. Request geography only when the tool schema supports that breakdown. Cross-check site engagement by country with `ga4_get_report`. If targeting or delivery detail is unavailable, request an export from the platform UI. -A campaign with no positive location criteria serves worldwide on most platforms. Never accept the campaign name as evidence of targeting. +Investigate spend outside the intended market, unusually cheap clicks with weak engagement, and traffic spikes. These are signals to investigate, not proof of fraud. -On Google, read the actual criteria with `run_gaql_query`: - -```sql -SELECT campaign.name, campaign_criterion.location.geo_target_constant, - campaign_criterion.negative -FROM campaign_criterion -WHERE campaign_criterion.type = 'LOCATION' AND campaign.status = 'ENABLED' -``` - -An empty result for a campaign means worldwide delivery. On social platforms, geo lives at the ad set / ad group level — check every ad set, not just the campaign (`tiktok_ads_get_adgroup` shows TikTok targeting; elsewhere use `get_ad_readback` and the platform UI). If a platform gives you no read path for targeting, mark it UNVERIFIED in the report and verify through delivery data instead — never imply a check you could not run. - -## 2. Verify presence-only matching - -Google's default geo match is "presence or interest," which lets users anywhere in the world match your target country by merely showing interest in it — a standing leak for junk clicks. Check it: - -```sql -SELECT campaign.name, campaign.geo_target_type_setting.positive_geo_target_type -FROM campaign WHERE campaign.status = 'ENABLED' -``` - -Flag anything not set to `PRESENCE`. Microsoft Ads has the same concept ("people in" vs "people searching for or viewing pages about" the location) — verify it there too. - -## 3. Verify click geography matches intent - -Criteria can look right while delivery is wrong. Pull actual delivery by country and compare against the intended market: - -- **Google:** `run_gaql_query` on `geographic_view`: - -```sql -SELECT geographic_view.country_criterion_id, metrics.clicks, metrics.cost_micros -FROM geographic_view -WHERE segments.date DURING LAST_30_DAYS -ORDER BY metrics.clicks DESC -``` - -- **Other platforms:** `pull__ads_performance` with a geographic breakdown where the platform supports it (Meta, LinkedIn, Reddit, TikTok via `tiktok_ads_get_insights`, X, Microsoft, Amazon). -- **Cross-check engagement:** `ga4_run_report` with a country dimension — high click volume from a geo with near-zero engaged sessions is a fraud symptom, not an audience. - -Any meaningful spend outside the intended market is a finding even when the criteria look correct. - -## 4. Apply fraud-symptom heuristics - -Cheap clicks are a symptom to explain, not a win to report. Investigate when you see: - -- CPCs far below the vertical's normal range (a rough tripwire: any geo where CPC runs under a third of the home-market CPC). -- CTR suspiciously uniform across unrelated ads. -- Click volume with near-zero engaged sessions or conversions in GA4. -- Traffic spikes at odd hours for the target market. - -For campaigns serving developed-market B2B (US/CA/UK/AU/NZ), a common practice is proactively excluding known high-invalid-traffic geographies unless the business explicitly serves them. The standard is not any fixed exclusion list; it is "delivery matches the stated market." - -## 5. Fix and re-verify - -- Add explicit location criteria to any campaign missing them; switch Google and Microsoft campaigns to presence-only matching; add exclusions for geographies producing junk clicks. Apply changes through the platform or via `create_campaign_plan` / `execute_campaign_plan`, and confirm with a fresh criteria read afterward. -- Bake this into launches: **campaign-preflight** runs the same checks before a campaign goes live. Re-run this audit after any campaign creation or edit — including agent-built campaigns. -- Report per campaign: intended geo, configured geo (or NONE / UNVERIFIED), presence setting, % of clicks and spend outside intended geo over 30 days, and estimated wasted spend. Treat material out-of-geo spend as an urgent fix, not a suggestion. - -## Campaign-type notes - -- **PMax, Demand Gen, Display, Video:** highest junk-geo risk because inventory is cheap; the delivery-geography check is mandatory before judging their performance. A suspiciously cheap CPC on these types is presumed junk until geography proves otherwise. -- **Search:** presence-or-interest is the main leak even with correct country criteria. -- **Social:** check every ad set / ad group, not just the campaign shell. - -## Rules - -- The campaign name is never evidence of targeting. Read the criteria. -- Presence-only on Google and Microsoft unless the business has a stated reason otherwise. -- If you cannot read a platform's targeting, say UNVERIFIED — never imply it was checked. -- Never praise cheap CPCs before confirming where the clicks came from. -- If the intended market is unknown, ask. Never assume worldwide. +Report per campaign: intended geography, verified settings, evidence period, out-of-market delivery where measurable, and missing evidence. Propose corrections through supported campaign-plan fields and the **launch** approval flow, or hand off unsupported settings to the platform UI. Re-read settings and delivery after the change. diff --git a/skills/google-ads-quality-score/SKILL.md b/skills/google-ads-quality-score/SKILL.md index 82364e2..09580dc 100644 --- a/skills/google-ads-quality-score/SKILL.md +++ b/skills/google-ads-quality-score/SKILL.md @@ -11,7 +11,7 @@ Confirm the account: `list_connected_accounts`. ## 1. Pull QS with spend context -Use `run_gaql_query` against `keyword_view` selecting `ad_group_criterion.quality_info.quality_score`, `search_predicted_ctr`, `creative_quality_score` (Ad Relevance), `post_click_quality_score` (Landing Page), plus impressions, cost, and conversions over LAST_30_DAYS. Filter to enabled keywords with meaningful impressions. +Use `pull_google_ads_performance` and `audit_account_structure` to prioritize campaign issues. Request a Google Ads export for keyword-level Quality Score and its components if the returned data lacks them. Never infer a component score from campaign averages. Rank the fix list by cost, not by QS: a QS 3 keyword spending $4K/month matters more than twenty QS 4 keywords spending nothing. Pull the below-average components on the highest-spend keywords first. @@ -25,7 +25,7 @@ Rough mechanics worth knowing: Ad Rank scales with bid times quality, and actual ## 3. Fix Expected CTR -Rewrite the RSAs: keyword in 2-3 headlines, concrete numbers and differentiators in the rest, distinct headlines with no near-duplicates. Fill every relevant asset type — sitelinks, callouts, structured snippets, images — since assets lift CTR directly. Generate variants with `generate_text_ad` and check what is live with `get_ad_readback`. Expect 2-4 weeks for the rating to move. +Write RSA variants with `create_ad_copy`: use relevant keywords, concrete differentiators, and distinct headlines. Compare current text and asset counts using `list_creative_assets` where available, or request a platform export. Follow **launch** before any deployment. ## 4. Fix Ad Relevance @@ -35,9 +35,9 @@ Restructure to tightly themed ad groups: if no headline in the ad group contains - Speed: LCP under 2.5s, mobile-usable, no intrusive interstitials. - Message match: page headline mirrors the ad promise; ad-group keywords appear in the page copy above the fold. -- Trust: HTTPS, visible contact info, clear pricing and policies. +- Trust: HTTPS, visible contact info, clear offer details and policies. -If the page needs replacing, build one with `create_landing_page` and ship it with `publish_landing_page`. This component is slowest to update — Google re-crawls on its own schedule, so allow 4-8 weeks. +If the page needs replacing, provide a copy and usability brief for the site owner. The plugin does not publish landing pages. Recheck the destination before launch. ## 6. Track it over time diff --git a/skills/google-shopping-optimizer/SKILL.md b/skills/google-shopping-optimizer/SKILL.md index c9b9254..6f80228 100644 --- a/skills/google-shopping-optimizer/SKILL.md +++ b/skills/google-shopping-optimizer/SKILL.md @@ -7,7 +7,7 @@ description: Optimize Google Shopping — product feed quality, titles, Merchant Shopping performance is mostly feed quality. Fix titles, identifiers, images, and price accuracy before touching bids or structure. -Confirm the account: `list_connected_accounts`. Pull campaign performance with `pull_google_ads_performance` and product-level detail via `run_gaql_query` on `shopping_performance_view`. +Confirm the account with `list_connected_accounts`. Pull campaign performance with `pull_google_ads_performance`; request Merchant Center or Google Ads exports for product-level data not returned. ## 1. Audit the feed @@ -23,7 +23,7 @@ Grade each top product: optimized, needs work, or at risk of disapproval. Fix by ## 2. Rewrite titles -Structure: Brand + Product Type + Key Attributes (color, size, material, gender) + Model/Variant. Front-load the important terms — Shopping truncates around 70 characters. No ALL CAPS, no promo text ("SALE", "FREE SHIPPING"), no symbols. Aim for 70-100 characters, max 150. +Structure: Brand + Product Type + Key Attributes (color, size, material, gender) + Model/Variant. Front-load the important terms — Shopping truncates around 70 characters. No ALL CAPS, no promo text ("SALE", "SPECIAL OFFER"), no symbols. Aim for 70-100 characters, max 150. ## 3. Clear disapprovals diff --git a/skills/help/SKILL.md b/skills/help/SKILL.md index a47ee49..c32f577 100644 --- a/skills/help/SKILL.md +++ b/skills/help/SKILL.md @@ -1,44 +1,22 @@ --- name: help -description: Explain what Synter can do, list the available skills and agents, and point to docs and support. Use when a user asks for help, what's possible, how something works, or where to get support. +description: Explain the bundled skills, supported MCP tools, OAuth setup, and support options. --- -# Synter Help +# Synter help -Synter is the AI Agent Operator for Ads. One interface, every ad platform. You direct, the agents execute. Nothing ships without your approval. +This plugin connects to Synter’s hosted advertising service over OAuth. Use **quickstart** to sign in and **connect** to verify accounts. -## Skills (slash commands) +Main workflows: -- **/synter:quickstart** — onboard, connect a platform, run a first action. -- **/synter:connect** — link ad platforms and GA4 (Google, Meta, LinkedIn, Microsoft, Reddit, TikTok, X, Amazon, and more). -- **/synter:audience** — build ABM lists, lookalikes, and signal-based segments; activate them. -- **/synter:creative** — generate on-brand images, video, UGC, and ad copy. -- **/synter:launch** — plan, preflight, and ship a cross-platform campaign. -- **/synter:optimize** — cut wasted spend, scale winners, reallocate budget by ROAS. -- **/synter:report** — cross-channel performance report and exec summary. +- **report** and **attribution**: review performance and reconcile measurement. +- **media-plan**, **launch**, and **replicate**: prepare campaign plans and execute approved changes. +- **audience**: inspect existing audiences and plan targeting. +- **ad-copy-generation**: write text ads for the verified advertiser. +- **optimize**: review budget opportunities and apply approved changes. -## Agents +The README lists every bundled skill and agent. The supported tool directory is `tools/directory-tools.json`. Use only tools available in that directory and the live server schema; report unavailable capabilities plainly. -`/agents` lists them. Synter ships: campaign-strategist, media-buyer, audience-builder, creative-director, budget-optimizer, performance-analyst. They run automatically when the task fits, or you can call one directly. +The Claude package runs a SessionStart shell hook that prints static operating guidance. Cursor uses the rules file. The optional SDK runner in the repository is launched manually. -## What it connects to - -Direct API connections to Google, Meta, LinkedIn, Microsoft, Reddit, TikTok, X, Amazon Ads/DSP, The Trade Desk, Criteo, retail media, and GA4 — read and write. - -## Getting set up - -- Run **/synter:quickstart** and complete the secure Synter browser sign-in when - Claude prompts. Never paste an API key, access token, or authorization code - into chat. -- If the OAuth prompt does not appear, reconnect the Synter MCP server from the - client's connector/plugin settings and retry in a new conversation. -- Free GA4 tools work with no key and no credits. - -## Docs & support - -- Docs: **syntermedia.ai** and the developer quickstart at **syntermedia.ai/docs/quickstart**. -- Product: **syntermedia.ai**. -- Privacy: **syntermedia.ai/privacy**. -- Support: **syntermedia.ai/contact** or the in-app support channel. - -Anything that spends money asks for your approval first. Reads are free to run. +[Privacy](https://syntermedia.ai/privacy) · [Support](https://syntermedia.ai/support) diff --git a/skills/incrementality-testing/SKILL.md b/skills/incrementality-testing/SKILL.md index b27135d..f0ed5f5 100644 --- a/skills/incrementality-testing/SKILL.md +++ b/skills/incrementality-testing/SKILL.md @@ -11,7 +11,7 @@ Start with `measure_incrementality` where the platform and data support it; desi ## 1. Define the question -Pick one testable claim: channel incrementality ("does Display drive anything?"), budget incrementality ("does doubling spend double conversions?"), or tactic incrementality ("is retargeting real?"). One variable per test. Pull the baseline first: `pull__ads_performance` for spend and attributed conversions, `ga4_run_report` for a platform-neutral conversion count. +Pick one testable claim: channel incrementality ("does Display drive anything?"), budget incrementality ("does doubling spend double conversions?"), or tactic incrementality ("is retargeting real?"). One variable per test. Pull the baseline first: the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`) for spend and attributed conversions, `ga4_get_report` for a platform-neutral conversion count. ## 2. Choose the method @@ -28,7 +28,7 @@ Pick one testable claim: channel incrementality ("does Display drive anything?") ## 4. Run it clean -Freeze everything else: no creative swaps, no budget moves, no new campaigns in test or control markets mid-flight. Use `list_campaigns` to confirm nothing else is live in the control geos, and `set_spend_alert` to catch drift. Measure conversions from a neutral source (GA4), not the tested platform. +Keep other inputs stable during the test: avoid creative swaps, unrelated budget changes, or new campaigns in test/control markets. Read `list_campaigns` regularly to catch drift and measure outcomes with GA4. Configure any automated alerts in the platform UI. ## 5. Analyze diff --git a/skills/keyword-research/SKILL.md b/skills/keyword-research/SKILL.md index f9b7a7f..d90e434 100644 --- a/skills/keyword-research/SKILL.md +++ b/skills/keyword-research/SKILL.md @@ -5,20 +5,20 @@ description: Find high-intent, low-waste keywords for Search campaigns and add t # Keyword Research -Find terms real buyers search, group them by intent, and add them where they convert. Reads are free; adding keywords to a live campaign is a change, so confirm before shipping. +Find terms real buyers search, group them by intent, and add them where they convert. Reads do not require approval; adding keywords to a live campaign is a change, so confirm before shipping. Confirm the account: `list_connected_accounts`. This applies to search platforms (**platform-google**, **platform-microsoft**). ## 1. Start from intent, not volume Anchor to the conversion that defines success. Sort candidate terms by buyer intent: -- **High intent:** problem-aware, ready to act ("buy", "pricing", "near me", branded competitor terms). +- **High intent:** problem-aware, ready to act ("buy", "near me", branded competitor terms). - **Research intent:** comparing, not yet buying. Lower priority unless the funnel supports nurture. - **Junk:** informational or off-topic. These become negatives, not keywords. See the **negative-keywords** skill. ## 2. Mine what already works -Pull the account's own search terms before inventing new ones: `run_gaql_query` on the search-term view (Google) or `pull_microsoft_ads_performance` history. Terms that already converted are the strongest seeds. Group survivors into tight ad groups by theme so ad copy can match. +Use `research_campaign_opportunity` and available `pull_google_ads_performance` or `pull_microsoft_ads_performance` history. Request a platform search-term export if query-level detail is missing. Separate observed converting queries from proposed keywords; do not invent query performance. ## 3. Assign match types deliberately @@ -28,7 +28,7 @@ Pull the account's own search terms before inventing new ones: `run_gaql_query` ## 4. Add on approval -Add keywords through the plan tools (`upsert_plan_entity` into the target ad group) and confirm the change. Then pair every expansion with negatives so new match surface does not leak budget. Verify conversion tracking exists first: `ga4_list_conversions`. +Draft supported keyword entities with `upsert_plan_entity` and follow **launch** for review, preflight, approval, execution, and verification. Use the platform UI if keyword edits are unsupported. Pair expansions with exclusions and verify tracking with `ga4_get_conversions` and **conversion-tracking**. ## Rules diff --git a/skills/kill-scale-rules/SKILL.md b/skills/kill-scale-rules/SKILL.md index 18dcfd5..f03d394 100644 --- a/skills/kill-scale-rules/SKILL.md +++ b/skills/kill-scale-rules/SKILL.md @@ -5,7 +5,7 @@ description: Decide whether to kill or scale a campaign, ad set, or creative, wi # Kill or Scale -The two decisions that move an account: cut what is losing, feed what is winning. Both fail when made too early or too hard. Reads are free; pausing or scaling spends differently, so confirm before shipping. +The two decisions that move an account: cut what is losing, feed what is winning. Both fail when made too early or too hard. Reads do not require approval; pausing or scaling spends differently, so confirm before shipping. Confirm the account: `list_connected_accounts`. diff --git a/skills/landing-page-creation/SKILL.md b/skills/landing-page-creation/SKILL.md deleted file mode 100644 index f1ef2e4..0000000 --- a/skills/landing-page-creation/SKILL.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: landing-page-creation -description: MUST load before `create_landing_page_draft`. Covers brand preflight → outline → generate → publish → custom domain. ---- - -# Landing Page Creation Workflow - -When creating landing pages, use this 5-step workflow: - -## 1. Brand & Form Preflight (CUSTOMER pages only) - -BEFORE generating or publishing a landing page for a CUSTOMER (not Synter), you MUST verify brand context and form destination: - -- **Brand Guidelines**: Brand guidelines must exist for that customer's domain/organization. Marketing skills should exist for: `brand-voice`, `positioning-angles`, and `audience-icp`. If missing, STOP and request them or offer to run `extract_brand_skills`. -- **Form Destination & External Tool Audit**: Inspect the customer's existing main website (e.g. `/contact` or `/book`) to identify their exact live form/calendar setup (e.g. Fillout, Calendly, Typeform, HubSpot). -- **Dual Routing & Attribution Pass-Through**: Ensure the landing page form posts to `POST /api/lp/forms/submit` (for Synter DB capture and multi-platform CAPI event logging), then pre-fills and redirects directly to the customer's original form/calendar flow. -- **Click ID & UTM Preservation**: Ensure all platform click IDs (`rdt_cid`, `li_fat_id`, `gclid`, `fbclid`) and UTM parameters inherit into the form/redirect URL so the customer's CRM maintains complete attribution parity. -- The `create_landing_page_draft` tool enforces brand check automatically and will return a `LANDING_PAGE_BRAND_SETUP_REQUIRED` error if brand context is missing. Follow its instructions. - - -## 2. Outline - -Discuss the page structure, headline, value props, and CTA with the user. Ask the user for their CTA URL or suggest links from their existing website. Never invent URLs. - -## 3. Generate & Preview - -You MUST call the `create_landing_page_draft` tool. The tool saves the page to the database AND returns a JSON result. The UI automatically renders a live preview widget from the tool result. Do NOT manually write a landing-page code fence — only the tool result triggers the widget correctly. - -## 4. Publish - -After user approval, call `publish_landing_page`. The UI automatically updates the widget status. - -## 5. Custom Domain Offer - -After a page is published, offer to set up a custom domain (e.g. `go.acme.com`). Explain that a branded domain keeps their brand in the URL for Google Ads quality score. Tell the user to provide their domain, then you can check DNS setup requirements via the `/api/landing-pages/domain-setup` endpoint. We can auto-configure DNS for many providers (Cloudflare, GoDaddy, Namecheap, etc.) or provide manual CNAME instructions. The tool result from `create_landing_page_draft` includes `offer_custom_domain: true` and a `page_id` — the UI renders a DomainSetupCard automatically. - -## Critical Rules - -- **NEVER output a landing-page code fence yourself.** ALWAYS call `create_landing_page_draft` instead. If you output the code fence without calling the tool, the preview will show "Not Found" because the page was never saved to the database. -- **Always wrap the landing page workflow in a `workflow` fenced block** to show visual progress to the user. -- If title/slug are not provided, derive sensible defaults. -- For `cta_url`, ALWAYS ask the user or suggest URLs from their working website. NEVER guess a CTA URL. The LP generator will hard-fail if the URL doesn't resolve. -- For Synter pages, use https://syntermedia.ai/signup (NEVER app.syntermedia.ai which does not exist). -- If the user explicitly asks to "generate/build/create a landing page", do not stop at a markdown template — you must call the `create_landing_page_draft` tool. diff --git a/skills/launch/SKILL.md b/skills/launch/SKILL.md index b24b55c..fe1a8d7 100644 --- a/skills/launch/SKILL.md +++ b/skills/launch/SKILL.md @@ -17,17 +17,17 @@ Get the brief in plain English, then pin down what you need: - **Budget** — daily/total, and the **target CPA/ROAS**. - **Landing page / final URL** and the offer. -Confirm the account: `list_connected_accounts`. Confirm conversion tracking exists: `ga4_list_conversions` / `get_gtm_tag` / `verify_pixel_ownership`. No tracking → fix that before launching, or the spend is blind. +Confirm the account with `list_connected_accounts`. Check conversion evidence with `ga4_get_conversions`, `ga4_get_report`, and `get_pixel_health`. Resolve missing verification before launching. ## 2. Plan it -Use `create_campaign_plan` to draft structure (campaigns → ad sets → ads), then `forecast_campaign` for reach/CPM/CPC/CPA projections. For the channel mix and budget split, lean on platform cost benchmarks and a clear rationale. +Use `create_campaign_plan` to draft structure (campaigns → ad sets → ads), add supported entities with `upsert_plan_entity`, then `forecast_campaign` for reach/CPM/CPC/CPA projections. For the channel mix and budget split, lean on platform cost benchmarks and a clear rationale. -Creative: run the **creative** skill, or `generate_image_ad` / `generate_text_ad` / `generate_video_ad`. Apply brand voice (see `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`). +Write text with `create_ad_copy` and select existing approved assets with `list_creative_assets`. Use the verified advertiser’s voice, logos, colors, and destination; see `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. ## 3. Preflight — before anything goes live -Walk the checklist and report pass/fail: +Read the complete plan with `get_campaign_plan` and run `run_launch_preflight`. Resolve failures, then show the exact final plan and report pass/fail: - Geo targeting correct, exclusion lists applied. - Conversion tracking firing and attached to the right action. @@ -39,7 +39,7 @@ Walk the checklist and report pass/fail: Show the full plan — platform, audience, budget, creative, projected CPA — and ask for a clear go. Only then: -- `create_campaign_for_audience(...)` for an audience-targeted launch, or `execute_campaign_plan` / `enable_campaign` for a built plan. +- Record the user’s approval with `approve_campaign_plan`, then use `execute_campaign_plan`. Check `get_plan_execution` and, if a job ID is returned, `get_job_status`. Use `enable_campaign` only when activation is within the user-approved scope. - For multi-platform, ship each platform and confirm each one back with its real campaign ID. Then confirm live: `list_campaigns(platform=...)` and report the IDs created. "Created" and "live and spending" are different claims — verify before you say it's running. diff --git a/skills/linkedin-ads-targeting/SKILL.md b/skills/linkedin-ads-targeting/SKILL.md index b0ff82c..f40cf2f 100644 --- a/skills/linkedin-ads-targeting/SKILL.md +++ b/skills/linkedin-ads-targeting/SKILL.md @@ -13,7 +13,7 @@ Confirm the account: `list_connected_accounts`. Layer categories with AND logic, options within a category with OR: -- **Job function or title:** e.g. Marketing OR Sales. Titles are free text — add variations. +- **Job function or title:** e.g. Marketing OR Sales. Titles are text — add variations. - **Seniority:** Director OR VP OR CXO for buying-committee reach. Skip individual contributors unless they are the actual users driving adoption. - **Company size:** pick the employee ranges matching your ICP (e.g. 201-5000 for mid-market). - **Industry:** map your vertical to LinkedIn's taxonomy. @@ -21,14 +21,14 @@ Layer categories with AND logic, options within a category with OR: Size targets: below ~50,000 members delivery suffers for Sponsored Content; 100,000-300,000 is the sweet spot; above ~500,000 you are paying LinkedIn CPMs for non-ICP reach — narrow with skills or a company list. Under ~10,000 is workable only for ABM and message formats. -Build and manage audiences with `build_abm_audience`, `build_lookalike_audience`, and `find_audience_signals`; review with `list_audiences`, connect with `attach_audience`, push with `sync_audience`. The **audience** skill covers cross-platform audience strategy. +Review existing audiences with `list_audiences`. Define company, seniority, and job criteria; have the account owner create and synchronize new lists in the platform UI. See **audience** for the planning workflow. ## 2. Set up ABM -1. Prepare the company list: company name plus domain per row, minimum ~300 companies for matching, 1,000+ for real scale. `build_abm_audience` handles the build; expect a 60-80% match rate and allow 24-48 hours for matching. +1. Prepare the company criteria and required domains, then have the account owner create the list in the platform UI. Confirm returned audience IDs and observed match rates with `list_audiences`; do not promise a match rate. 2. Layer the account list with seniority (Director+) and the job functions that own the buying decision. 3. Tier the accounts: Tier 1 (top strategic accounts) gets highest bids and personalized messaging, Tier 2 industry-specific messaging, Tier 3 broader value prop at lower bids. -4. Measure ABM on account penetration (% of target accounts reached), engagement by tier, and pipeline influenced — not raw CPL. Use `pull_linkedin_ads_performance` and `pull_linkedin_company_engagement` for the read; **attribution** for pipeline crediting. +4. Use `pull_linkedin_ads_performance` for available campaign metrics and **attribution** for pipeline analysis. Request exports for company-level engagement not returned by the tools. ## 3. Choose the format @@ -37,7 +37,7 @@ Build and manage audiences with `build_abm_audience`, `build_lookalike_audience` - **Lead gen:** Sponsored Content or Message Ads with Lead Gen Forms, tight audience (50k-150k). - **Cheap presence:** Text Ads in the sidebar — low CPC, low volume. -Cost reality check (2025-2026 US B2B ranges): CPCs roughly $5-14, CPMs $25-60, CPLs $50-200 depending on industry and offer; CTR around 0.4-0.6% is normal for Sponsored Content. Budget with **media-plan** and validate spend against these before declaring a problem. +Compare CPC, CPM, CPL, and CTR with the account’s recent history for the same objective and audience. Use **media-plan** for the proposed test budget and label estimates clearly. ## 4. Optimize lead gen forms @@ -49,7 +49,7 @@ Cost reality check (2025-2026 US B2B ranges): CPCs roughly $5-14, CPMs $25-60, C ## 5. Verify and iterate -Confirm campaigns went live as configured with `list_campaigns` and `get_ad_readback`, watch delivery with `pull_linkedin_ads_performance`, and set a `set_spend_alert` — LinkedIn burns budget quickly at these CPMs. Retarget engagers and form-openers as a second-stage audience; expand proven audiences with `build_lookalike_audience`. Creative guidance lives in **creative**; broader LinkedIn platform mechanics in **platform-linkedin**. +Confirm campaign state with `list_campaigns` and delivery with `pull_linkedin_ads_performance`. Configure alerts and new expansion audiences in the platform UI. Use **ad-copy-generation** for text and **platform-linkedin** for campaign planning. ## Rules @@ -57,4 +57,4 @@ Confirm campaigns went live as configured with `list_campaigns` and `get_ad_read - Check estimated audience size before launch; fix anything under 50k (non-ABM) or over 500k. - ABM lists need domains, not just company names, for good match rates. - Judge lead gen on qualified leads and pipeline, not form fills. -- LinkedIn CPLs of $75-150 in SaaS are normal — do not panic-pause at CPCs that would be alarming on other platforms; compare against benchmarks first. +- Judge LinkedIn costs against lead quality, pipeline value, and the account’s own benchmarks before proposing a pause. diff --git a/skills/llm-visibility-audit/SKILL.md b/skills/llm-visibility-audit/SKILL.md deleted file mode 100644 index 8a2ef3a..0000000 --- a/skills/llm-visibility-audit/SKILL.md +++ /dev/null @@ -1,143 +0,0 @@ ---- -name: llm-visibility-audit -description: Audit domain visibility and Generative Engine Optimization (GEO) across ChatGPT, Claude, Perplexity, Google Gemini, and AI search engines. Assesses AI crawler access, /llms.txt machine readability, JSON-LD schema, Wikipedia/Wikidata entity footprint, and commercial buying intent share of voice. Use when auditing brand AI visibility, testing GEO readiness, analyzing /llms.txt, or generating an LLM visibility score (1-100). ---- - -# LLM Visibility & Generative Engine Optimization (GEO) Audit - -Audits a domain's visibility, machine-readability, and entity authority across AI search engines and generative models (ChatGPT, Claude, Perplexity, Google Gemini, ByteDance/TikTok). Calculates a 1–100 **Synter LLM Visibility Score** across 5 weighted pillars and generates actionable GEO improvement plans. - -## Capabilities - -1. **AI Crawler Readiness Audit** — Verifies `robots.txt` permissions for `GPTBot`, `ClaudeBot`, `PerplexityBot`, `Google-Extended`, `Bytespider`, and `CCBot`. -2. **Machine-Readable Knowledge Indexing (`/llms.txt`)** — Checks `/llms.txt` and `/llms-full.txt` availability, syntax, and token efficiency (<50 KB primary index target). -3. **Structured Schema & Technical Markup** — Evaluates embedded `application/ld+json` script tags (`Organization`, `SoftwareApplication`, `Product`, `FAQPage`). -4. **Knowledge Graph & Entity Footprint** — Queries Wikidata entities (QID) and Wikipedia encyclopedic articles. -5. **Third-Party Citation & Commercial SOV Audit** — Audits brand profile presence and review completeness across G2, Capterra, Trustpilot, and Gartner. -6. **Commercial Buying Prompt Discovery** — Maps high-intent buyer prompts across Evaluation, Direct Comparison, Alternatives, Pricing/ROI, and Feature Verification intent stages. - ---- - -## 5 Assessment Pillars & Scoring Weights - -``` -======================================================================================== -Pillar Weight Max Points Target Standard -======================================================================================== -1. AI Crawler Infrastructure & Access 20% 20 pts Explicit Allow rules in robots.txt for all 6 major bots -2. Machine-Readable Knowledge Indexing (/llms.txt) 20% 20 pts /llms.txt active & compressed (<50 KB summary + /llms-full.txt) -3. Structured Schema & Technical Markup 20% 20 pts JSON-LD Organization, SoftwareApplication, & FAQPage -4. Knowledge Graph & Wikipedia/Wikidata Footprint 20% 20 pts Wikidata entity (P31/P856) & Wikipedia article -5. Third-Party Citations & Commercial SOV 20% 20 pts Indexed profiles on G2, Capterra, Trustpilot, Gartner ----------------------------------------------------------------------------------------- -TOTAL SYNTER LLM VISIBILITY SCORE 100% 100 pts Grade: A (90-100), B (75-89), C (60-74), D (<60) -======================================================================================== -``` - ---- - -## Workflow & Step-by-Step Execution - -### Step 1: Execute Synter Visibility MCP Agent Tools - -Run the native visibility agent tools via MCP or runtime helper: - -```typescript -// 1. Audit Crawler Readiness & /llms.txt -const crawlers = await executeVisibilityAgentTool("audit_llm_crawler_readiness", { - domain: "targetdomain.com", - check_llms_txt: true -}, context); - -// 2. Discover High-Intent Commercial Prompts -const prompts = await executeVisibilityAgentTool("discover_llm_visibility_prompts", { - domain: "targetdomain.com", - competitors: ["Competitor A", "Competitor B"], - icp_persona: "Target Buyer Persona" -}, context); - -// 3. Audit Review Profiles -const reviewProfiles = await executeVisibilityAgentTool("audit_review_site_profiles", { - brand_name: "BrandName", - domain: "targetdomain.com", - review_platforms: ["g2", "capterra", "trustpilot", "gartner"] -}, context); - -// 4. Check Schema Markup Recommendations -const schema = await executeVisibilityAgentTool("generate_jsonld_schema_markup", { - brand_name: "BrandName", - domain: "targetdomain.com", - description: "Official platform overview", - pricing_summary: "Pricing tier summary" -}, context); -``` - -### Step 2: Conduct Live Domain Verification - -Verify live headers, `robots.txt`, `/llms.txt`, and HTML DOM schemas: - -1. **Robots.txt:** Fetch `https:///robots.txt` and check explicit `Allow: /` or wildcard rules for `GPTBot`, `ClaudeBot`, `PerplexityBot`, `Google-Extended`, `Bytespider`, `CCBot`. -2. **llms.txt File Size:** Fetch `https:///llms.txt` and calculate byte length. If `> 100 KB`, recommend splitting into a concise `/llms.txt` (<50 KB) and a deep `/llms-full.txt`. -3. **JSON-LD Schema Check:** Inspect HTML source for ` -``` - -### 2. `/llms.txt` Best Practice Format - -```markdown -# domain.com - LLM Knowledge & Indexing Map -> Curated index for language models and RAG agents to retrieve verified brand capabilities at inference time. - -## Core Capabilities -- Autonomous Campaign Management -- Real-Time Attribution & Revenue Tracking -- Cross-Platform Conversion API (CAPI) Integration - -## Key Documentation -- Platform Overview: https://domain.com/overview -- API Documentation: https://domain.com/docs -- Pricing & Tiers: https://domain.com/pricing - -## Full Index Archive -- Extended Knowledge Index: https://domain.com/llms-full.txt -``` - ---- - -## Best Practices & Guardrails - -1. **Markdown Token Efficiency:** Keep primary `/llms.txt` files under **50 KB** to prevent single-pass LLM context truncation during RAG vector indexing. -2. **Never Block AI Crawlers:** Avoid wildcard `Disallow: /` rules in `robots.txt` that inadvertently block `Google-Extended` or `GPTBot`. -3. **Synchronize SameAs Entity Links:** Ensure JSON-LD `sameAs` array links to verified Wikidata QIDs, Wikipedia articles, Crunchbase, and official LinkedIn/Twitter profiles. -4. **Target Head-to-Head Comparison Prompts:** Build explicit Markdown comparison tables on official docs for queries like *"Brand vs Competitor A"*. diff --git a/skills/media-plan/SKILL.md b/skills/media-plan/SKILL.md index 9de2ec5..0776381 100644 --- a/skills/media-plan/SKILL.md +++ b/skills/media-plan/SKILL.md @@ -15,7 +15,7 @@ Anchor to the conversion that defines success and the target CPA or ROAS. Get th ## 2. Choose the channel mix -Weight channels by how they perform for this objective and audience, grounded in the account's own history (`pull__ads_performance`) and typical ranges (see **platform-benchmarks**). Use `find_audience_signals` for who is reachable where. Balance demand capture (search) against demand generation (social) to fit the funnel. +Ground channel weights in account history using `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, and `pull_reddit_ads_performance`. Use `research_campaign_opportunity` for available research and `forecast_campaign` for estimates; label projections clearly. Balance demand capture and demand generation for the objective. ## 3. Split the budget and project outcomes diff --git a/skills/meta-ads-diagnostics/SKILL.md b/skills/meta-ads-diagnostics/SKILL.md index 40fa961..a82bbbb 100644 --- a/skills/meta-ads-diagnostics/SKILL.md +++ b/skills/meta-ads-diagnostics/SKILL.md @@ -26,7 +26,7 @@ Pull daily impressions, reach, frequency, and CTR for the last 30 days. Compute - 30-50%: rotate creatives now. - Over 50%: pause and replace. -Frequency confirms it: prospecting past ~3.0 or retargeting past ~5.0 with falling CTR is fatigue, not audience quality. Refresh in proportion — copy swap for mild decline, new visual angle for moderate, new concept or format change for severe. Build replacements with the **creative** skill and validate with **creative-testing**. +Frequency confirms it: prospecting past ~3.0 or retargeting past ~5.0 with falling CTR is fatigue, not audience quality. Refresh in proportion — copy swap for mild decline, new visual angle for moderate, new concept or format change for severe. Build replacements with the **ad-copy-generation** skill and validate with **creative-testing**. ## 3. Check audience overlap @@ -47,9 +47,9 @@ Per-placement or per-demo breakdowns lie. Meta optimizes holistically, so a plac ## 6. Decision tree for the rest -In order: Learning Phase → frequency over 3.0 (fatigue) → CPM rising with stable CTR (saturation or auction pressure; broaden) → CTR fine but CVR down (landing page; see **campaign-preflight**) → quality/conversion ranking below average (creative or post-click) → none of the above (tracking; audit with **conversion-tracking** and verify pixel destinations with `get_pixel_destinations`). +Diagnose in order: delivery status, frequency and fatigue, auction pressure, landing-page conversion, then tracking. Use `get_pixel_health` and **conversion-tracking** for available tracking evidence. -Apply fixes with `pause_campaign`, `update_campaign_budget`, or `upload_creative`, and confirm each write before shipping. Kill/scale calls go through **kill-scale-rules**. +Apply approved pauses or budget changes with `pause_campaign` or `update_campaign_budget`. Select existing replacements with `list_creative_assets` and follow **launch** for supported plan changes. Kill/scale decisions use **kill-scale-rules**. ## Rules diff --git a/skills/mmm-budget-planner/SKILL.md b/skills/mmm-budget-planner/SKILL.md index 3cbe9a9..6ef03ec 100644 --- a/skills/mmm-budget-planner/SKILL.md +++ b/skills/mmm-budget-planner/SKILL.md @@ -5,7 +5,7 @@ description: Plan and reallocate budget across platforms by ROI, with revenue pr # Budget Planner -Decide where the next dollar goes across platforms, grounded in real performance rather than gut feel. Reads are free; changing budgets is a change and waits for explicit approval. +Decide where the next dollar goes across platforms, grounded in real performance rather than gut feel. Reads do not require approval; changing budgets is a change and waits for explicit approval. Confirm the account and what is connected: `list_connected_accounts`. @@ -13,13 +13,13 @@ Confirm the account and what is connected: `list_connected_accounts`. Pull recent performance per platform so the plan sits on real numbers: -- `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, and the other `pull__ads_performance` tools for spend, conversions, and CPA per channel. +- Use `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, and `pull_reddit_ads_performance` for available spend, conversions, and CPA by channel. - `get_attribution` to see how conversions credit across touchpoints, not just last click. - `measure_incrementality` where a holdout exists, to separate true lift from spend that would have converted anyway. This is the honest answer to "what is this channel really worth." ## 2. Reallocate by ROI -`optimize_budget` reallocates spend across platforms toward the channels returning the most per dollar, within the total you set. Feed it the budget and any per-channel floors or caps, and it returns a proposed split. +Use `optimize_budget` for a proposed allocation within the user’s total and per-channel limits supported by its schema. Treat the result as a recommendation until the exact changes are approved and applied. Read the output as a plan, not a command: @@ -34,6 +34,6 @@ Read the output as a plan, not a command: ## 4. Ship on approval -Show the current split, the proposed split, the projected outcome, and the rationale. Change budgets only on the user's clear go, with `update_campaign_budget` (or by executing an approved plan). Guard against fat-finger amounts. Then confirm the new budgets with a fresh `pull__ads_performance` read. +Show the current split, proposed split, projected outcome, and rationale. Change budgets only on explicit approval with `update_campaign_budget`, or follow **launch** for an approved plan. Guard against mistaken amounts. Confirm the new settings with `list_campaigns` and monitor subsequent performance. For single-account bid and pacing tuning rather than cross-platform allocation, use the **optimize** and **bid-optimization** skills. For pure ROAS and CPA math, use the **roas-calculator** skill. diff --git a/skills/negative-keywords/SKILL.md b/skills/negative-keywords/SKILL.md index 42acee5..c91001a 100644 --- a/skills/negative-keywords/SKILL.md +++ b/skills/negative-keywords/SKILL.md @@ -5,13 +5,13 @@ description: Mine and manage negative keywords from search-term reports to stop # Negative Keywords -Negatives are how a Search campaign stops paying for the wrong clicks. They matter as much as the keywords themselves. Reads are free; adding negatives is a change, so confirm before shipping. +Negatives are how a Search campaign stops paying for the wrong clicks. They matter as much as the keywords themselves. Reads do not require approval; adding negatives is a change, so confirm before shipping. Confirm the account: `list_connected_accounts`. ## 1. Read the search-term report -Pull what the campaign actually matched: `run_gaql_query` on the search-term view (Google) or `pull_microsoft_ads_performance` history. Look for terms with spend and clicks but no conversions, and terms that are simply off-intent. +Use `research_campaign_opportunity` and available `pull_google_ads_performance` or `pull_microsoft_ads_performance` history. Request a platform search-term export if query-level detail is missing. Separate observed converting queries from proposed keywords; do not invent query performance. ## 2. Categorize diff --git a/skills/optimize/SKILL.md b/skills/optimize/SKILL.md index b127c08..1731b92 100644 --- a/skills/optimize/SKILL.md +++ b/skills/optimize/SKILL.md @@ -10,14 +10,14 @@ Find the wasted spend and the winners, then act on the user's approval. ## 1. Pull the truth Get recent performance across the connected platforms: -`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_reddit_ads_performance`, `pull_tiktok_ads_performance`, `pull_x_ads_performance`, `pull_microsoft_ads_performance` (and the rest). Use `reconcile_platforms` to compare apples to apples, and `get_attribution` to see what's really driving conversions. +Use `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_reddit_ads_performance`, and `pull_microsoft_ads_performance`. Request exports for other platforms. Use `get_spend_reconciliation` to compare sources and `get_attribution` to investigate conversion credit. ## 2. Diagnose - **Wasted spend**: campaigns/ad sets/creatives spending with no conversions past a minimum-data threshold. - **Winners**: low CPA / high ROAS with enough volume to scale. - **Anomalies**: spend spikes, CTR drops, budget runaway — flag before they compound. -- **Fatigue**: rising frequency with falling CTR → creative needs rotation (run the **creative** skill). +- **Fatigue**: rising frequency with falling CTR → creative needs rotation (run the **ad-copy-generation** skill). Respect minimum-data guardrails — don't kill or scale on noise. State the threshold you used. @@ -25,7 +25,7 @@ Respect minimum-data guardrails — don't kill or scale on noise. State the thre Present a clear plan: what to cut, what to scale, where the freed budget goes, and the expected effect. -- `optimize_budget` — cross-platform reallocation by ROAS. +- `optimize_budget` — propose a cross-platform allocation for review. - `update_campaign_budget` — set a specific budget (guard against fat-finger amounts; default to sane ceilings). - `pause_campaign` — kill a loser. - Bid/budget tuning per platform as supported. @@ -34,7 +34,7 @@ Scale winners in steps (platform-appropriate budget increments), not all at once ## 4. Confirm -After acting, re-pull and report what changed and the new state. Set a `set_spend_alert` so the user is warned if spend runs away. +After acting, re-pull the data and report the new state. Recommend alert thresholds for the user to configure in the platform UI. ## House rules diff --git a/skills/performance-max-optimizer/SKILL.md b/skills/performance-max-optimizer/SKILL.md index 4e43388..0c32a70 100644 --- a/skills/performance-max-optimizer/SKILL.md +++ b/skills/performance-max-optimizer/SKILL.md @@ -7,15 +7,15 @@ description: Optimize Google Ads Performance Max — asset group structure, asse PMax gives up query and placement control, so you steer it with the three inputs you still own: assets, audience signals, and the feed. Optimize those; don't fight the black box. -Confirm the account: `list_connected_accounts`. Pull performance with `pull_google_ads_performance` and detail with `run_gaql_query`. +Confirm the account with `list_connected_accounts`, then use `pull_google_ads_performance` and `audit_account_structure`. Request platform exports when asset or query-level details are missing. ## 1. Fill every asset slot -Ad Strength tracks asset diversity. Per asset group, target: 10-15 distinct headlines, 4-5 long headlines, 4-5 descriptions, 5+ landscape and 5+ square images, 3+ portrait images, logos, and at least one real video — if you supply none, Google auto-generates one, usually badly. Generate gaps with `generate_text_ad`, `generate_image_ad`, and `generate_video_ad`, and ship via `upload_creative`. +Inventory approved assets with `list_creative_assets` and write missing text with `create_ad_copy`. Request any missing images, logos, or video from the advertiser. Review asset completeness and brand consistency before following **launch**. ## 2. Replace Low assets on a cycle -Query `asset_group_asset` with `run_gaql_query` for the `performance_label` on each asset (filter to `PERFORMANCE_MAX` campaigns). +Review asset performance labels in a Google Ads export if they are absent from the tool results. Do not infer asset-level labels from campaign performance. - BEST: keep, and spin 2-3 close variations off it. - GOOD: keep and monitor. @@ -26,7 +26,7 @@ Replace 1-2 assets at a time and never wipe a whole asset type at once. Validate ## 3. Mine search term insights and cut waste -Query `search_term_insight` via `run_gaql_query` (Google only exposes category-level data for PMax). Flag categories with CPA over 2x target or clicks with zero conversions. PMax takes negatives at the account level only — build a list covering job-seeker terms, informational queries ("what is", "how to"), free/coupon hunters, and DIY terms, then manage it with **negative-keywords**. Re-check CPA and impression share 7-14 days after applying. +Review a platform search-category export for irrelevant or unprofitable traffic. Propose negatives with **negative-keywords**, verify current platform support, and have the account owner apply unsupported changes in the platform UI. The most expensive miss: brand queries. If insights show a large brand share, PMax is buying conversions your brand campaign gets cheaper — add brand terms as account-level negatives and let the brand campaign recover. @@ -34,16 +34,16 @@ The most expensive miss: brand queries. If insights show a large brand share, PM Signals guide initial learning; PMax expands beyond them. Stack them by strength: -- Customer match lists first — purchasers and high-LTV segments (build with `build_abm_audience` or `build_lookalike_audience`, attach via `attach_audience`, keep fresh with `sync_audience`). +- Review customer and high-value purchaser lists with `list_audiences`. Have the account owner create or refresh lists in the platform UI before using them in supported campaign-plan fields. - Custom segments from high-intent search terms and competitor URLs (50-100 entries each). -- Site visitors and cart abandoners from GA4 (`ga4_run_report` to size them). +- Site visitors and cart abandoners from GA4 (`ga4_get_report` to size them). - Interests and demographics last, as seasoning only. After 30 days, check which segments convert and whether expansion aligns with them. Deeper audience work lives in **audience**. ## 5. Check channel allocation -Segment by network with `run_gaql_query`. For e-commerce, Shopping should carry most conversions; Search next; Display and YouTube minority shares. Diagnose imbalance: +Use a platform channel export when the performance response lacks channel breakdowns. Compare conversion value and spend by channel before recommending a reallocation: - Display bloated with weak CVR: audience signals too loose — add purchase-intent custom segments. - Search share mostly brand: negate brand at account level. diff --git a/skills/pixel-capi-auditor/SKILL.md b/skills/pixel-capi-auditor/SKILL.md index 122f450..369f739 100644 --- a/skills/pixel-capi-auditor/SKILL.md +++ b/skills/pixel-capi-auditor/SKILL.md @@ -7,7 +7,7 @@ description: Audit client-side pixels and server-side Conversions API implementa Bad tracking corrupts every downstream decision, so audit it before optimizing anything. The audit is the same on every platform: is the pixel firing, is the server event arriving, are the two deduplicated, and is enough matched user data attached. -Confirm the account: `list_connected_accounts` and `get_connection_status`. Inspect what is actually deployed with `list_gtm_tags` and `get_gtm_tag`; see what tech the site runs with `builtwith_domain_lookup`. +Confirm the account with `list_connected_accounts` and `get_connection_status`. Inspect `get_pixel_health`; obtain platform diagnostics or tag-manager exports for missing implementation details. ## 1. Verify the client side @@ -22,11 +22,11 @@ Per platform, confirm the base tag is present and fires on every page, standard | Reddit | `rdt_cid` (`_rdt_uuid`) | `rdt('init', ...)` | | Microsoft | `msclkid` | UET tag, auto-tagging on | -Confirm pixel ownership with `verify_pixel_ownership`. Fix tag gaps directly: `update_gtm_tag_html`, then `publish_gtm_container`. +Verify ownership and event delivery in the platform UI when not evidenced by `get_pixel_health`. Have the account owner fix tags and publish through their tag manager, then repeat the test. ## 2. Verify the server side -Check each platform's events manager diagnostics for arriving server events. Route server-side conversion data with `configure_pixel_destination` and confirm the wiring with `get_pixel_destinations`. Google's server path is Enhanced Conversions (hashed user data on the conversion, plus Consent Mode v2 defaults for EEA/UK traffic); Microsoft's is offline conversion upload keyed on `msclkid`. +Check server-event arrivals and routing in each platform’s diagnostics alongside `get_pixel_health`. The account owner must apply routing changes in the platform UI; this plugin does not configure destinations. ## 3. Audit deduplication @@ -36,7 +36,7 @@ Failure signatures: - Conversions roughly double the true count: event ID missing on one side. - Counts too low: IDs present but formatted differently on each side, or event names mismatched. -- Platform reports far more conversions than GA4 shows (`ga4_run_report` on the conversion event): dedup broken — this is the most common finding. +- Platform reports far more conversions than GA4 shows (`ga4_get_report` on the conversion event): dedup broken — this is the most common finding. ## 4. Raise match quality @@ -44,11 +44,11 @@ Match quality (Meta EMQ 0-10; TikTok match rate) decides how many conversions at ## 5. Reconcile across platforms -Sum of per-platform reported conversions always exceeds reality because each platform claims its own touch. Compare platform claims against GA4 and each other with `reconcile_platforms`, and read multi-touch credit with `get_attribution` (deeper treatment in **attribution**). Use GA4 (`ga4_list_conversions`, `ga4_run_report`) as the neutral yardstick when judging whether a platform's count is inflated. +Platforms can claim overlapping conversions. Compare platform results with `ga4_get_report` and inspect attribution with `get_attribution`, using consistent periods and stated attribution windows. Use `get_spend_reconciliation` for spend discrepancies; it does not establish conversion deduplication. ## 6. Report and fix in priority order -Produce a per-platform grid: pixel present, CAPI live, dedup verified, match quality score, action needed. Fix in the order that recovers the most spend: broken dedup on the biggest platform first, then missing CAPI, then match-quality parameters, then the long tail. Re-verify with a test conversion after each fix, and set `set_spend_alert` on any account that was double-counting so budget decisions pause until numbers are trusted. Baseline event coverage setup belongs to **conversion-tracking**; this skill is the audit layer on top. +Produce a per-platform grid: pixel present, server events observed, dedup verified, match quality, and action needed. Mark unavailable evidence as unverified. Prioritize broken dedup and missing events on high-spend accounts; ask the owner to fix the implementation and repeat a test conversion before scaling. See **conversion-tracking**. ## Rules diff --git a/skills/platform-amazon/SKILL.md b/skills/platform-amazon/SKILL.md index cde8a8c..d20e9ad 100644 --- a/skills/platform-amazon/SKILL.md +++ b/skills/platform-amazon/SKILL.md @@ -10,7 +10,7 @@ Two motions: Sponsored ads on the retail search results, and DSP for programmati ## Read what exists - `list_connected_accounts`, then `list_campaigns(platform="amazon")`. -- `pull_amazon_ads_performance(days=7)` for Sponsored Products/Brands/Display metrics. `pull_amazon_dsp_performance(days=7)` for DSP. +- Dedicated performance reads for this platform are outside the plugin’s directory tools. Request a platform export for metrics and use `list_campaigns` for the state and fields actually returned. Do not substitute another platform’s data. ## Sponsored ads @@ -20,7 +20,7 @@ Two motions: Sponsored ads on the retail search results, and DSP for programmati ## Amazon DSP -- Programmatic display and video across Amazon inventory and the open web, targeted with Amazon shopping and audience signals. Use for reach, retargeting, and audience-based prospecting beyond keywords. Use `find_audience_signals` and `build_lookalike_audience` to shape audiences. +- Review existing audiences with `list_audiences`. Use `research_campaign_opportunity` for available campaign research. Ask the account owner to create or synchronize new audience lists in the platform UI; use returned IDs only in supported plan fields. ## Plan and ship @@ -29,3 +29,5 @@ Two motions: Sponsored ads on the retail search results, and DSP for programmati - Confirm live with `list_campaigns(platform="amazon")` and report the real ID. Scale by ACOS with the **optimize** skill. Porting from another platform? Use the **replicate** skill. Search intent maps to Sponsored Products keywords; audiences map to DSP. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/platform-benchmarks/SKILL.md b/skills/platform-benchmarks/SKILL.md index 3bf555b..ec5963e 100644 --- a/skills/platform-benchmarks/SKILL.md +++ b/skills/platform-benchmarks/SKILL.md @@ -9,7 +9,7 @@ Set expectations and split budget using how each platform typically performs for ## Use the account's own data first -Before quoting a range, pull what this account already does: `pull__ads_performance(days=...)` and `forecast_campaign`. Real history for this advertiser is the truth. Benchmarks are for platforms not yet run or for a directional split. +Before quoting a range, pull what this account already does: the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`) and `forecast_campaign`. Real history for this advertiser is the truth. Benchmarks are for platforms not yet run or for a directional split. ## How platforms typically behave @@ -29,3 +29,5 @@ Weight toward demand capture (search) for immediate conversions and demand gener - Never present a benchmark as a guaranteed result. State it as a typical range and name the source of any real number. - Prefer the account's own history over any external figure. - If you do not have a real number, say so; a plain true line beats an invented impressive one. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/platform-google/SKILL.md b/skills/platform-google/SKILL.md index abdddd3..3d534f5 100644 --- a/skills/platform-google/SKILL.md +++ b/skills/platform-google/SKILL.md @@ -5,7 +5,7 @@ description: Operate Google Ads through Synter — read and build Search, Perfor # Google Ads Playbook -How Google Ads is shaped and how to operate it through Synter. Reads are free; anything that spends money waits for explicit approval. +How Google Ads is shaped and how to operate it through Synter. Reads do not require approval; anything that spends money waits for explicit approval. Confirm the account first: `list_connected_accounts`. Use the real customer ID the tool returns; never invent one. Not connected → run the **connect** skill. @@ -18,17 +18,19 @@ Confirm the account first: `list_connected_accounts`. Use the real customer ID t ## Read a campaign -`list_campaigns(platform="google")` for the list and IDs. `run_gaql_query` is the ground truth for full structure: pull campaign settings, ad groups, keywords with match types, negatives, RSAs (every headline and description plus pinning), and location criteria. `pull_google_ads_performance(days=...)` for impressions, clicks, spend, conversions, and CPA per campaign so you know which pieces are worth acting on. +Use `list_campaigns` for returned campaign IDs and settings, `audit_account_structure` for available structure checks, and `pull_google_ads_performance` for performance. Request a Google Ads export for detailed targeting, keyword match types, negatives, or asset data that the tools do not return. ## Build a campaign -`create_campaign_plan` to draft structure, `upsert_plan_entity` to add each campaign → ad group → keyword/ad, `forecast_campaign` for reach/CPC/CPA. Generate copy with `generate_text_ad` or the **creative** skill; apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. Then run **campaign-preflight** and ship with `execute_campaign_plan` + `enable_campaign` on approval. To port an existing Google campaign to another platform, use the **replicate** skill. +Use `create_campaign_plan` and `upsert_plan_entity` for the supported campaign structure, and `forecast_campaign` for available estimates. Write text with `create_ad_copy` and select existing advertiser assets with `list_creative_assets`. Follow **launch** for `run_launch_preflight`, explicit user approval, `approve_campaign_plan`, `execute_campaign_plan`, and verification with `get_plan_execution` before any approved activation. ## Operating rules - Keep match types deliberate. Broad without good negatives and conversion tracking wastes budget fast. - Geo is the classic miss: a "US" campaign with no location criteria serves everywhere. Verify location criteria exist and match intent. -- Conversion tracking must exist and be attached before scaling: `ga4_list_conversions`, `get_gtm_tag`, `verify_pixel_ownership`. Blind spend is worse than no spend. +- Conversion tracking must exist and be attached before scaling: `ga4_get_conversions`, `get_pixel_health`. Blind spend is worse than no spend. - Guard budgets against a fat-finger daily amount 10 to 100x intended. - Adjust budgets with `update_campaign_budget`; pause with `pause_campaign`. Start conservative; scale winners with the **optimize** skill. - "Created" is not "live and spending." Confirm with `list_campaigns` and report the real IDs. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/platform-linkedin/SKILL.md b/skills/platform-linkedin/SKILL.md index 6cc0e16..58be634 100644 --- a/skills/platform-linkedin/SKILL.md +++ b/skills/platform-linkedin/SKILL.md @@ -5,7 +5,7 @@ description: Operate LinkedIn Ads through Synter — read and build B2B campaign # LinkedIn Ads Playbook -LinkedIn is the B2B platform: precise professional targeting, higher costs, longer consideration. How it is shaped and how to operate it through Synter. Reads are free; anything that spends money waits for explicit approval. +LinkedIn is the B2B platform: precise professional targeting, higher costs, longer consideration. How it is shaped and how to operate it through Synter. Reads do not require approval; anything that spends money waits for explicit approval. Confirm the account first: `list_connected_accounts`. Use the real ID the tool returns; never invent one. Not connected → run the **connect** skill. @@ -13,22 +13,24 @@ Confirm the account first: `list_connected_accounts`. Use the real ID the tool r Campaign group → campaign (objective, audience, budget, schedule) → ad. Targeting is the core lever: job title and function, seniority, company, company size, industry, and skills. Keep audiences tight enough to be relevant but large enough to deliver. -- **ABM:** target named accounts with `build_abm_audience`, then attach with `attach_audience`. Layer seniority on top so spend reaches decision makers. +- Review existing audiences with `list_audiences`. Use `research_campaign_opportunity` for available campaign research. Ask the account owner to create or synchronize new audience lists in the platform UI; use returned IDs only in supported plan fields. - **Formats:** single-image, document, video, and conversation/message formats. Match the format to the funnel stage. ## Read a campaign -`list_campaigns(platform="linkedin")` for the list and IDs. `pull_linkedin_ads_performance(days=...)` for spend, clicks, leads, and CPL. `pull_linkedin_company_engagement` for company-level engagement signal. +Use `list_campaigns` for IDs and state and `pull_linkedin_ads_performance` for campaign metrics. Request a platform export for company-level engagement if needed. ## Build a campaign -`create_campaign_plan` → `upsert_plan_entity` per campaign / ad → `forecast_campaign` (expect higher CPCs and CPLs than search or social). Generate creative with `generate_image_ad` or the **creative** skill; apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. Run **campaign-preflight**, then `execute_campaign_plan` + `enable_campaign` on approval. +Use `create_campaign_plan` and `upsert_plan_entity` for the supported campaign structure, and `forecast_campaign` for available estimates. Write text with `create_ad_copy` and select existing advertiser assets with `list_creative_assets`. Follow **launch** for `run_launch_preflight`, explicit user approval, `approve_campaign_plan`, `execute_campaign_plan`, and verification with `get_plan_execution` before any approved activation. ## Operating rules - Expect higher CPL than other platforms. Judge LinkedIn on pipeline quality and downstream conversion, not raw CPC. - Keep targeting to decision makers where the offer warrants it; do not spray a broad seniority range on an expensive platform. - Give campaigns room before judging; B2B conversion windows are long. Follow **pre-pause-analysis** before cutting. -- Conversion tracking (Insight Tag and offline/CRM conversions) must be attached before scaling: `verify_pixel_ownership`, `ga4_list_conversions`. Measure with `get_attribution`. +- Conversion tracking (Insight Tag and offline/CRM conversions) must be attached before scaling: `get_pixel_health`, `ga4_get_conversions`. Measure with `get_attribution`. - Guard against a fat-finger daily budget 10 to 100x intended. Adjust with `update_campaign_budget`, pause with `pause_campaign`. - "Created" is not "live and spending." Confirm with `list_campaigns` and report the real IDs. Build an audience first with the **audience** skill when one does not exist yet. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/platform-meta/SKILL.md b/skills/platform-meta/SKILL.md index 56b6a13..aae37fb 100644 --- a/skills/platform-meta/SKILL.md +++ b/skills/platform-meta/SKILL.md @@ -5,7 +5,7 @@ description: Operate Meta (Facebook and Instagram) Ads through Synter — read a # Meta (Facebook / Instagram) Ads Playbook -How Meta is shaped and how to operate it through Synter. Reads are free; anything that spends money waits for explicit approval. +How Meta is shaped and how to operate it through Synter. Reads do not require approval; anything that spends money waits for explicit approval. Confirm the account first: `list_connected_accounts`. Use the real ID the tool returns; never invent one. Not connected → run the **connect** skill. @@ -13,7 +13,7 @@ Confirm the account first: `list_connected_accounts`. Use the real ID the tool r Campaign (objective) → ad set (audience, budget, placements, schedule) → ad (creative). The objective is chosen once at the campaign level and shapes everything below it, so pick it from the conversion that defines success, not by habit. -- **Audiences:** custom (your data), lookalike (seeded from a custom audience), and interest/behavior. Build lookalikes with `build_lookalike_audience` and attach with `attach_audience`; find reachable signals with `find_audience_signals`. +- Review existing audiences with `list_audiences`. Use `research_campaign_opportunity` for available campaign research. Ask the account owner to create or synchronize new audience lists in the platform UI; use returned IDs only in supported plan fields. - **Advantage+** shifts targeting and placement decisions to Meta. Use it when data volume supports it; keep enough signal flowing. - **Placements:** Feed, Stories, Reels, and more. Creative should fit the placement, not be stretched into it. @@ -23,13 +23,15 @@ Campaign (objective) → ad set (audience, budget, placements, schedule) → ad ## Build a campaign -`create_campaign_plan` → `upsert_plan_entity` per campaign / ad set / ad → `forecast_campaign`. Generate creative with `generate_image_ad`, `generate_video_ad`, `generate_ugc_ad`, or the **creative** skill; apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. Run **campaign-preflight**, then `execute_campaign_plan` + `enable_campaign` on approval. +Use `create_campaign_plan` and `upsert_plan_entity` for the supported campaign structure, and `forecast_campaign` for available estimates. Write text with `create_ad_copy` and select existing advertiser assets with `list_creative_assets`. Follow **launch** for `run_launch_preflight`, explicit user approval, `approve_campaign_plan`, `execute_campaign_plan`, and verification with `get_plan_execution` before any approved activation. ## Operating rules - **Learning Phase:** a new ad set needs enough conversions to exit learning. Do not judge or kill it early, and do not edit it constantly; each significant edit resets learning. See the **pre-pause-analysis** discipline before cutting anything. - **Creative fatigue:** rising frequency with falling CTR means the creative is spent. Watch it with the **creative-fatigue-detector** skill and rotate before performance collapses. - Keep audience overlap in check; ad sets competing against each other inflate CPM. -- Conversion tracking (pixel and CAPI) must be firing and attached: `verify_pixel_ownership`, `ga4_list_conversions`. Blind spend is worse than no spend. +- Conversion tracking (pixel and CAPI) must be firing and attached: `get_pixel_health`, `ga4_get_conversions`. Blind spend is worse than no spend. - Guard against a fat-finger daily budget 10 to 100x intended. Adjust with `update_campaign_budget`, pause with `pause_campaign`. Start conservative; scale winners with the **optimize** skill. - "Created" is not "live and spending." Confirm with `list_campaigns` and report the real IDs. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/platform-microsoft/SKILL.md b/skills/platform-microsoft/SKILL.md index c569f58..6de4bb1 100644 --- a/skills/platform-microsoft/SKILL.md +++ b/skills/platform-microsoft/SKILL.md @@ -5,7 +5,7 @@ description: Operate Microsoft (Bing) Ads through Synter — read and build Sear # Microsoft (Bing) Ads Playbook -Microsoft Ads is close to a mirror of Google Ads, which is why porting between them is common. Reads are free; anything that spends money waits for explicit approval. +Microsoft Ads is close to a mirror of Google Ads, which is why porting between them is common. Reads do not require approval; anything that spends money waits for explicit approval. Confirm the account first: `list_connected_accounts`. Use the real ID the tool returns; never invent one. Not connected → run the **connect** skill. @@ -29,13 +29,15 @@ To carry a live Google campaign across, use the **replicate** skill. It reads th ## Build a campaign -`create_campaign_plan` → `upsert_plan_entity` per campaign / ad group / keyword / ad → `forecast_campaign`. Generate copy with `generate_text_ad` or the **creative** skill. Run **campaign-preflight**, then `execute_campaign_plan` + `enable_campaign` on approval. +Use `create_campaign_plan` and `upsert_plan_entity` for the supported campaign structure, and `forecast_campaign` for available estimates. Write text with `create_ad_copy` and select existing advertiser assets with `list_creative_assets`. Follow **launch** for `run_launch_preflight`, explicit user approval, `approve_campaign_plan`, `execute_campaign_plan`, and verification with `get_plan_execution` before any approved activation. ## Operating rules - Enabling a campaign does not serve its ad groups if those stay paused. After enabling, re-pull and confirm the children actually serve; never trust the campaign status alone. - Carry negatives across, not just keywords. Keywords without negatives burn budget. - Verify geo/location criteria exist and match intent. -- Conversion tracking must exist and be attached before scaling: `ga4_list_conversions`, `verify_pixel_ownership`. +- Conversion tracking must exist and be attached before scaling: `ga4_get_conversions`, `get_pixel_health`. - Guard against a fat-finger daily budget 10 to 100x intended. Adjust with `update_campaign_budget`, pause with `pause_campaign`. - "Created" is not "live and spending." Confirm with `list_campaigns` and report the real IDs. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/platform-openai/SKILL.md b/skills/platform-openai/SKILL.md index f3cb5d5..e760ff7 100644 --- a/skills/platform-openai/SKILL.md +++ b/skills/platform-openai/SKILL.md @@ -10,7 +10,7 @@ A newer surface: ads that appear alongside AI answers. Structure is campaign → ## Read what exists - `list_connected_accounts`, then `list_campaigns(platform="openai_ads")`. -- `pull_openai_ads_performance(days=7, aggregation_level="campaign")` for metrics. Scope tighter with `ad_group_id` or `ad_id`, or set `aggregation_level` to ad_group or ad. +- Dedicated performance reads for this platform are outside the plugin’s directory tools. Request a platform export for metrics and use `list_campaigns` for the state and fields actually returned. Do not substitute another platform’s data. ## Structure and intent @@ -19,12 +19,14 @@ A newer surface: ads that appear alongside AI answers. Structure is campaign → ## Creative -- Concise, useful, honest copy. Overtly promotional creative reads badly next to an AI answer. Generate options with `generate_text_ad` / `generate_image_ad` and apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. +- Write text with `create_ad_copy`, select existing approved assets with `list_creative_assets`, and apply the verified advertiser’s brand voice. Request missing media from the advertiser. See **creative-testing**. ## Plan and ship -- Build with `create_campaign_plan` + `upsert_plan_entity`; `forecast_campaign` where projections are available. Run **campaign-preflight**: tracking attached (`verify_pixel_ownership` / `ga4_list_conversions`), sane budgets. +- Build with `create_campaign_plan` + `upsert_plan_entity`; `forecast_campaign` where projections are available. Run **campaign-preflight**: tracking attached (`get_pixel_health` / `ga4_get_conversions`), sane budgets. - Ship only on explicit approval: `execute_campaign_plan`, then `enable_campaign`. Nothing that spends money goes live without a clear go. - Confirm live with `list_campaigns(platform="openai_ads")` and report the real ID. Start conservative; read results before scaling with the **optimize** skill. Porting from another platform? Use the **replicate** skill and translate keyword intent into the questions this audience asks. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/platform-reddit/SKILL.md b/skills/platform-reddit/SKILL.md index c9d8522..dac8f6a 100644 --- a/skills/platform-reddit/SKILL.md +++ b/skills/platform-reddit/SKILL.md @@ -16,17 +16,19 @@ Reddit rewards ads that read like a good post and punishes ads that read like ad - Two levers: **communities** (specific subreddits) and **interests** (Reddit's topic clusters). Community targeting is precise but narrow; interest targeting scales but blurs. - Prefer audience fit over long subreddit lists. A tight interest plus a handful of high-fit communities usually beats a scraped list of 200 subreddits. -- Layer geo, device, and time. Use `find_audience_signals` to check who is reachable before committing budget. +- Review existing audiences with `list_audiences`. Use `research_campaign_opportunity` for available campaign research. Ask the account owner to create or synchronize new audience lists in the platform UI; use returned IDs only in supported plan fields. ## Creative and tone - Match the medium: conversational copy, a real point of view, an image or short video that would not look out of place in the feed. Overtly corporate creative dies here. -- Respect each community's rules and moderators. Generate options with `generate_image_ad` / `generate_video_ad` and apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. +- Write text with `create_ad_copy`, select existing approved assets with `list_creative_assets`, and apply the verified advertiser’s brand voice. Request missing media from the advertiser. See **creative-testing**. ## Plan and ship -- Build with `create_campaign_plan` + `upsert_plan_entity`; `forecast_campaign` for reach and CPA. Run **campaign-preflight**: geo correct, tracking attached (`verify_pixel_ownership`), budgets sane. +- Build with `create_campaign_plan` + `upsert_plan_entity`; `forecast_campaign` for reach and CPA. Run **campaign-preflight**: geo correct, tracking attached (`get_pixel_health`), budgets sane. - Ship only on explicit approval: `execute_campaign_plan`, then `enable_campaign`. Nothing that spends money goes live without a clear go. - Confirm live with `list_campaigns(platform="reddit")` and report the real ID. Start conservative; scale with the **optimize** skill. Porting from another platform? Use the **replicate** skill and translate keywords or audiences into communities and interests. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/platform-tiktok/SKILL.md b/skills/platform-tiktok/SKILL.md index ceaf498..a0a97b1 100644 --- a/skills/platform-tiktok/SKILL.md +++ b/skills/platform-tiktok/SKILL.md @@ -10,23 +10,25 @@ Native, sound-on, creator-style video is the product. Structure is campaign → ## Read what exists - `list_connected_accounts` to confirm the org and account, then `list_campaigns(platform="tiktok")`. -- `pull_tiktok_ads_performance(days=7)` for spend, clicks, and CPA per campaign. `tiktok_ads_get_insights` for detailed video metrics (views, watch time, completion). +- Dedicated performance reads for this platform are outside the plugin’s directory tools. Request a platform export for metrics and use `list_campaigns` for the state and fields actually returned. Do not substitute another platform’s data. ## Structure and objectives - Objective sits at the campaign level (traffic, conversions, app installs, reach, lead generation). Pick the one that matches the real conversion, not the vanity metric. - Ad groups hold budget, targeting, placement, and schedule. Ads hold the creative. -- Audiences: interest and behavior, custom audiences from your data, and lookalikes. Use `find_audience_signals` to size who is reachable and `build_lookalike_audience` from a seed. +- Review existing audiences with `list_audiences`. Use `research_campaign_opportunity` for available campaign research. Ask the account owner to create or synchronize new audience lists in the platform UI; use returned IDs only in supported plan fields. ## Creative is the campaign - Vertical 9:16, sound-on, hook in the first 2 seconds. Spark Ads run through an organic post and usually outperform static uploads. -- Generate options with `generate_video_ad` / `generate_ugc_ad`; apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. Test several hooks, not one polished film. See the **creative-testing** skill. +- Write text with `create_ad_copy`, select existing approved assets with `list_creative_assets`, and apply the verified advertiser’s brand voice. Request missing media from the advertiser. See **creative-testing**. ## Plan and ship -- Build with `create_campaign_plan` + `upsert_plan_entity`; `forecast_campaign` for reach and CPA. Run **campaign-preflight** before launch: geo, tracking (`verify_pixel_ownership`), sane budgets. +- Build with `create_campaign_plan` + `upsert_plan_entity`; `forecast_campaign` for reach and CPA. Run **campaign-preflight** before launch: geo, tracking (`get_pixel_health`), sane budgets. - Ship only on explicit approval: `execute_campaign_plan`, then `enable_campaign`. Nothing that spends money goes live without the user's clear go. - Confirm live with `list_campaigns(platform="tiktok")` and report the real ID. Start conservative; scale winners with the **optimize** skill. Porting a campaign here from another platform (e.g. Google Search)? Search keywords have no twin on TikTok. Translate intent into audience signals and creative. Use the **replicate** skill. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/platform-x/SKILL.md b/skills/platform-x/SKILL.md index 306ebfc..8768f4f 100644 --- a/skills/platform-x/SKILL.md +++ b/skills/platform-x/SKILL.md @@ -10,7 +10,7 @@ Fast, conversational, timeline-native. Structure is campaign → ad group → ad ## Read what exists - `list_connected_accounts`, then `list_campaigns(platform="x")`. -- `pull_x_ads_performance(days=7)` for spend, clicks, and CPA per campaign. +- Dedicated performance reads for this platform are outside the plugin’s directory tools. Request a platform export for metrics and use `list_campaigns` for the state and fields actually returned. Do not substitute another platform’s data. ## Structure and targeting @@ -21,12 +21,14 @@ Fast, conversational, timeline-native. Structure is campaign → ad group → ad ## Creative - Native tweet format wins over banner-style creative. Short copy, a clear hook, one ask. Video and single-image both work. -- Generate options with `generate_text_ad` / `generate_image_ad` / `generate_video_ad` and apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. +- Write text with `create_ad_copy`, select existing approved assets with `list_creative_assets`, and apply the verified advertiser’s brand voice. Request missing media from the advertiser. See **creative-testing**. ## Plan and ship -- Build with `create_campaign_plan` + `upsert_plan_entity`; `forecast_campaign` for reach and CPA. Run **campaign-preflight**: geo, tracking (`verify_pixel_ownership`), sane budgets. +- Build with `create_campaign_plan` + `upsert_plan_entity`; `forecast_campaign` for reach and CPA. Run **campaign-preflight**: geo, tracking (`get_pixel_health`), sane budgets. - Ship only on explicit approval: `execute_campaign_plan`, then `enable_campaign`. Nothing that spends money goes live without a clear go. - Confirm live with `list_campaigns(platform="x")` and report the real ID. Start small; measure conversions before scaling with the **optimize** skill. Porting from another platform? Use the **replicate** skill. Search keywords translate to X keyword and interest targeting, but re-check conversion intent before spending. + +Use only platform and entity types accepted by the live tool schema. Follow **launch** for plan review, preflight, approval, execution, and verification. If a setting or platform is unsupported, describe the manual handoff without claiming it was applied. diff --git a/skills/pre-pause-analysis/SKILL.md b/skills/pre-pause-analysis/SKILL.md index dae748b..7c3878c 100644 --- a/skills/pre-pause-analysis/SKILL.md +++ b/skills/pre-pause-analysis/SKILL.md @@ -5,13 +5,13 @@ description: Full-funnel check before pausing, cutting, or killing a paid campai # Pre-Pause Analysis -Pausing is easy and often wrong. A campaign that looks like a loser is frequently a tracking gap, a geo leak, or an ad set the platform has not finished learning. Run this before any pause so you stop the right thing for the right reason. Reads are free; pausing is a change and waits for explicit approval. +Pausing is easy and often wrong. A campaign that looks like a loser is frequently a tracking gap, a geo leak, or an ad set the platform has not finished learning. Run this before any pause so you stop the right thing for the right reason. Reads do not require approval; pausing is a change and waits for explicit approval. Confirm the account: `list_connected_accounts`. ## 1. Is the data even real -- Confirm conversion tracking is firing and attached to the right action (`ga4_list_conversions`, `verify_pixel_ownership`). A campaign with broken tracking looks dead when it may be converting. See the **conversion-tracking** skill. +- Confirm conversion tracking is firing and attached to the right action (`ga4_get_conversions`, `get_pixel_health`). A campaign with broken tracking looks dead when it may be converting. See the **conversion-tracking** skill. - Check the destination: does the final URL resolve and match the ad's promise. A broken or mismatched landing page kills conversion rate independent of the campaign. ## 2. Has it had enough time and volume diff --git a/skills/quickstart/SKILL.md b/skills/quickstart/SKILL.md index 1ecc2bd..a61cae7 100644 --- a/skills/quickstart/SKILL.md +++ b/skills/quickstart/SKILL.md @@ -1,51 +1,15 @@ --- name: quickstart -description: Guided introduction to Synter — sign in securely, connect your first ad platform, and run your first cross-platform action. Use when a user is new to Synter, asks how to get started, needs to authenticate, or wants a tour of what the agents can do. +description: Sign in through OAuth, confirm the workspace, and run a first report or campaign plan. --- -# Synter Quickstart +# Get started with Synter -You are the Synter operator. The user directs; you execute. Get them from zero to a connected account and a first real action in a few minutes. Be terse and concrete. Nothing that spends money ships without explicit approval. +1. Call `get_connection_status`. Complete the client’s browser OAuth prompt if requested, then retry. If sign-in fails, reconnect from the client’s plugin settings. Never ask for credentials in chat. +2. Call `list_connected_accounts` and show the workspace, platform, account name, and account ID. If an account is missing, use the **connect** skill. +3. For connected GA4 accounts, use `ga4_get_properties`, then `ga4_get_report` with the selected property and requested period. Use the tool’s returned schema for parameters. +4. Offer a first task: a **report**, a **launch** plan, an **audience** review, or **ad-copy-generation**. Do not claim access to accounts or data that the tools did not return. -## 1. Confirm access +The plugin connects to the hosted Synter MCP server over OAuth. Cursor uses https://mcp.synterai.com; Claude uses https://mcp.syntermedia.ai. Both require a client that supports remote MCP and browser OAuth. -Call `get_connection_status` (or `get_credit_balance`). - -- **Works** → they are signed in. Skip to step 3. -- **Browser sign-in prompt** → ask them to complete Synter OAuth, then retry. -- **Auth error without a prompt** → reconnect the Synter MCP server from the - client's connector/plugin settings. Never ask the user to paste an API key, - access token, or authorization code into chat. - -## 2. Onboard (no account needed) - -The Claude plugin uses secure browser OAuth. If the user does not have a -Synter account, the authorization page guides them through creating or signing -in to one. When the browser returns to Claude, retry `get_connection_status`. - -For MCP clients that cannot complete browser OAuth, use Synter's separate -client-specific setup guide at **syntermedia.ai/docs/integrate/install**. Do not -collect credentials in the conversation. - -Free GA4 tools to demo value before they connect anything: -`ga4_list_properties()`, `ga4_run_report(metrics="sessions,totalUsers,conversions", dimensions="date", days=28)`. - -## 3. See what's connected - -`list_connected_accounts` — show platforms, account IDs, and the org. If nothing is connected, run the **connect** skill to link a platform (Google, Meta, LinkedIn, Microsoft, Reddit, TikTok, X, and more). - -## 4. First real action - -Offer one concrete next step based on what they have: - -- **Accounts connected, want to see spend** → `pull_google_ads_performance(days=7)` (or the matching `pull__ads_performance`). Surface wasted spend and obvious wins. -- **Want to launch** → run the **launch** skill. -- **Want an audience** → run the **audience** skill. -- **Want creative** → run the **creative** skill. - -## House rules - -- Show, don't sell. Real numbers, real platform names, real outcomes. -- Confirm the org/account before any write: `list_connected_accounts`. Never run one org's campaigns through another's session. -- Reads are free to run. Anything that creates, enables, or changes budget needs explicit user approval first. -- More help: run the **help** skill. +Reads do not require approval. Show the exact scope and obtain explicit approval before account mutations or spend changes. More help: **help** or https://syntermedia.ai/support. diff --git a/skills/replicate/SKILL.md b/skills/replicate/SKILL.md index c07f546..3f71b06 100644 --- a/skills/replicate/SKILL.md +++ b/skills/replicate/SKILL.md @@ -17,8 +17,8 @@ Find the source campaign: `list_campaigns(platform="")`. Get its real ID Pull the full structure, not just top-line metrics: -- **Google source:** `run_gaql_query` is the ground truth. Read campaign settings (budget, bid strategy, networks, geo/location criteria, languages, ad schedule), ad groups, keywords with **match types**, negative keywords (campaign and ad-group level), and the RSAs (every headline and description, and any pinning). Use `pull_google_ads_performance` for the metrics that tell you which pieces are worth carrying over. -- **Other sources (Meta, LinkedIn, TikTok, Reddit, X):** use `list_campaigns` + `pull__ads_performance` and the matching platform playbook skill to read structure, audiences, budgets, and creative. +- Read `list_campaigns`, `audit_account_structure`, and `pull_google_ads_performance` for a Google source. Ask for platform exports when budgets, geo criteria, keyword match types, negatives, or complete ad text are missing. Do not replicate unknown settings. +- **Other sources (Meta, LinkedIn, TikTok, Reddit, X):** use `list_campaigns` + the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`) and the matching platform playbook skill to read structure, audiences, budgets, and creative. Write down the source structure explicitly before you build. If you cannot read a piece, say so — never invent a keyword list or a budget. @@ -33,13 +33,13 @@ Platforms are not identical. State the mapping and where it is lossy. - Rebuild fresh through Synter's plan tools. Do not describe this as Microsoft's native "Import from Google" — that is a different, platform-side path. **Cross-format pairs (e.g. Search → social, Google → Meta/TikTok):** -- There is no keyword concept on social. Translate intent: keywords and search themes become audience signals and interests; RSAs become social ad copy and creative. Use `find_audience_signals` and the target platform playbook. Be explicit that this is a re-interpretation, not a copy. +- Social campaigns require a new targeting interpretation. Use `research_campaign_opportunity` and existing `list_audiences` results to inform it; document how the proposed targeting differs from the source. ## 3. Build the target plan `create_campaign_plan` to draft the target structure, then `upsert_plan_entity` to add each campaign → ad group / ad set → ad, carrying over the mapped keywords, negatives, geo, schedule, and budget. `forecast_campaign` for reach/CPC/CPA on the new platform — the source platform's numbers do not transfer. -Creative: reuse the source ad copy where the format allows; otherwise run the **creative** / **ad-copy-generation** skills to regenerate for the target platform. Apply brand voice — see `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. +Creative: reuse the source ad copy where the format allows; otherwise run the **ad-copy-generation** skills to regenerate for the target platform. Apply brand voice — see `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`. ## 4. Preflight — before anything goes live @@ -47,13 +47,13 @@ Run the **campaign-preflight** checklist and report pass/fail: - Geo/location criteria copied and correct (a "US" name with no location criteria is the classic miss). - Negatives carried over — porting keywords without negatives burns budget fast. -- Conversion tracking exists and is attached on the target (`ga4_list_conversions` / `get_gtm_tag` / `verify_pixel_ownership`). No tracking → fix before launch. +- Check target-side evidence with `ga4_get_conversions` and `get_pixel_health`. Resolve missing tracking verification before launch. - Budget and bid caps sane — guard against a fat-finger daily budget 10–100x intended. - Match types preserved; final URLs resolve and match the ad's promise. ## 5. Ship on approval -Show the full mapped plan side by side with the source — platform, structure, budget, geo, creative, projected CPA — and name what changed in translation and why. Ask for a clear go. Only then `execute_campaign_plan` (or `create_campaign_for_audience` for an audience-targeted build), then `enable_campaign`. +Show the mapped plan beside the source, including changed structure, geography, audience, budget, and assets. Follow **launch** for `run_launch_preflight`, explicit user approval, `approve_campaign_plan`, and `execute_campaign_plan`. Verify with `get_plan_execution` and `list_campaigns`; enable only within the approved scope. Confirm live: `list_campaigns(platform="")` and report the real campaign ID created. "Created" and "live and spending" are different claims — verify before you say it is running. diff --git a/skills/report/SKILL.md b/skills/report/SKILL.md index e7032f5..38ee075 100644 --- a/skills/report/SKILL.md +++ b/skills/report/SKILL.md @@ -9,11 +9,11 @@ Pull every platform, reconcile, and tell the story straight. ## 1. Gather -Pull the period the user wants across all connected platforms (`pull__ads_performance`). Add analytics ground truth: `ga4_run_report` for sessions/conversions, `get_attribution` for conversion paths. +Pull the period the user wants across all connected platforms (the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`)). Add analytics ground truth: `ga4_get_report` for sessions/conversions, `get_attribution` for conversion paths. ## 2. Reconcile -Use `reconcile_platforms` so platform-reported conversions line up with analytics — platforms over-claim. Note dedup and any tracking gaps (a 403 read error is not the same as broken tracking; say which it is). +Use `get_spend_reconciliation` to compare spend records. Compare conversion counts separately with `ga4_get_report` and `get_attribution`, stating the attribution model and window. Flag deduplication uncertainty and access failures rather than treating them as verified tracking defects. ## 3. Tell the story @@ -28,7 +28,7 @@ For an exec/board audience, keep it to the narrative and the numbers that matter ## 4. Deliver -Inline by default. For a shareable artifact, `create_google_doc` / `create_google_sheet` / `create_document`, or send to the team via `send_slack_message`. +Deliver a copyable narrative and tables in the conversation. Exporting documents or sending messages is outside this plugin’s tool surface. ## House rules diff --git a/skills/retargeting-sequence-designer/SKILL.md b/skills/retargeting-sequence-designer/SKILL.md index c3b9c9b..cccc857 100644 --- a/skills/retargeting-sequence-designer/SKILL.md +++ b/skills/retargeting-sequence-designer/SKILL.md @@ -14,11 +14,11 @@ Confirm the account and existing audiences first: `list_connected_accounts`, `li Define the conversion goal, then split visitors by how close they got: - **Stage 1:** all site visitors (awareness reinforcement) -- **Stage 2:** engaged visitors (product or pricing pages, 2+ pages) -- **Stage 3:** high-intent (cart, checkout, demo page) +- **Stage 2:** engaged visitors (product or offer pages, 2+ pages) +- **Stage 3:** high-intent (cart, order, demo page) - **Stage 4:** abandoners (started the conversion, did not finish) -Budget flows to intent: roughly 60% to Stages 3-4, 25% to Stage 2, 15% to Stage 1. Verify the pixel is actually populating these audiences before building on them — `get_pixel_destinations` and **conversion-tracking**. +Allocate the proposed budget by intent and audience size. Check `get_pixel_health` and **conversion-tracking** before relying on event-based audiences; verify actual membership with `list_audiences` or platform exports. ## 2. Segment by recency, with mutual exclusion @@ -31,17 +31,17 @@ Each window must exclude every shorter window, or the hot audience gets triple-s ## 3. Sequence the message -Escalate instead of repeating: reminder with the product (day 0-1), social proof (day 2-4), offer with urgency (day 5-10), fresh angle or new feature (day 11-20), final win-back offer (day 21-30). After day 30, suppress or move to a cold pool. Generate stage variants with `generate_image_ad` / `generate_text_ad` and validate winners with **creative-testing** before scaling a sequence. +Sequence messages by recency and intent: reminder, evidence, relevant offer, then a different angle. Avoid fabricated urgency. Write text variants with `create_ad_copy`, select existing approved assets with `list_creative_assets`, and use **creative-testing** to assess results. ## 4. Build the exclusions Mandatory, on every retargeting campaign: - Purchasers and converters, 30 days minimum, 90 recommended. -- Existing customers (route them to upsell, not acquisition). +- Existing customers, unless the approved campaign explicitly targets them. - Junk traffic: sub-5-second bounces, careers-page visitors. -Sync suppression lists from your customer data with `sync_audience` and attach exclusions alongside targets with `attach_audience`. Refresh weekly; a stale suppression list quietly re-targets last month's buyers. +Have the account owner refresh customer suppression lists and exclusions in the platform UI. Confirm existing audience IDs with `list_audiences` before adding them to supported plan fields. ## 5. Coordinate across platforms @@ -49,7 +49,7 @@ Assign roles so platforms complement instead of pile on: Google Display/YouTube ## 6. Launch and monitor -Stand up per-stage campaigns with `create_campaign_for_audience`, run **campaign-preflight** before enabling, and watch weekly with `pull__ads_performance`: +Prepare per-stage campaigns with `create_campaign_plan` and `upsert_plan_entity`, then follow **launch** for preflight and explicit approval. Review results weekly with the supported platform performance tools: - Frequency above ~8/week per user: tighten windows or cut budget. - CTR falling 20%+ week over week: creative fatigue, rotate the stage's ads. diff --git a/skills/roas-calculator/SKILL.md b/skills/roas-calculator/SKILL.md index dc62cce..48ac649 100644 --- a/skills/roas-calculator/SKILL.md +++ b/skills/roas-calculator/SKILL.md @@ -7,7 +7,7 @@ description: Calculate ROAS, CPA, break-even, max CPC, and campaign profitabilit Turn spend and revenue into the numbers that decide whether a campaign lives. Reads only; this is math, not a spend change. -Confirm the account: `list_connected_accounts`, then pull real figures with `pull__ads_performance(days=...)`. Never invent revenue or margin; if the user has not given margin or conversion value, ask rather than assume. +Confirm the account: `list_connected_accounts`, then pull real figures with the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`). Never invent revenue or margin; if the user has not given margin or conversion value, ask rather than assume. ## The core numbers diff --git a/skills/seasonal-budget-planner/SKILL.md b/skills/seasonal-budget-planner/SKILL.md index 4100221..b243ad0 100644 --- a/skills/seasonal-budget-planner/SKILL.md +++ b/skills/seasonal-budget-planner/SKILL.md @@ -7,7 +7,7 @@ description: Plan ad budgets around seasonal peaks — holiday scaling, CPM infl Peak season punishes flat budgets twice: CPMs inflate 50-150% while unprepared accounts pay them with untested creative and cold audiences. Plan in phases, not in a panic the week before. -Ground the plan in your own history first: `pull__ads_performance` over last year's season and `ga4_run_report` with a date dimension to see your real demand curve. +Ground the plan in your own history first: the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`) over last year's season and `ga4_get_report` with a date dimension to see your real demand curve. ## 1. Plan in four phases @@ -51,7 +51,7 @@ If you are not in the category, these are your cheap weeks — competitors' budg ## 6. Execute and guard -Encode the phased plan with `create_campaign_plan` and `execute_campaign_plan`, apply phase changes with `update_campaign_budget`, and rebalance across platforms mid-flight with `optimize_budget`. Set `set_spend_alert` before peak so a 5x daily budget cannot silently run for a week. During peak, review daily with `pull__ads_performance`; after, reconcile what each platform claims against site truth with `reconcile_platforms`. +Encode the phased proposal with `create_campaign_plan` and follow **launch** for preflight, approval, and execution. Apply approved phase changes with `update_campaign_budget`; use `optimize_budget` for allocation recommendations. Configure alerts in the platform UI. Review performance daily during peak and reconcile with `get_spend_reconciliation` afterward. ## Rules diff --git a/skills/ugc-creative-brief/SKILL.md b/skills/ugc-creative-brief/SKILL.md deleted file mode 100644 index 5695acc..0000000 --- a/skills/ugc-creative-brief/SKILL.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -name: ugc-creative-brief -description: Build UGC creator briefs with hook formulas, shot lists, platform format specs, and compensation frameworks. Use when briefing UGC creators, planning creator campaigns, writing creator-style hooks, or generating UGC-style ads. ---- - -# UGC Creative Brief - -A UGC ad works because it looks like a person talking, not a brand advertising; the brief's job is to lock the hook, message, and CTA while leaving the delivery authentic. - -## 1. Gather the inputs - -You need: brand and product, target audience with pain points, objective, distribution platforms, 2-3 key messages, restricted claims or compliance limits, and budget. If any of these are missing, ask before drafting — a brief with vague messages produces generic content. - -## 2. Write the brief - -Structure every brief with these blocks: - -- **Brand overview:** product, site, brand voice in one line each. -- **Deliverables:** count, length, aspect ratio, resolution (1080x1920 minimum for vertical), raw footage yes/no, revision rounds (cap at 2). -- **Key messages:** 2-3 per video, no more. Creators who juggle five talking points hit none. -- **Hook requirement:** first 3 seconds must stop the scroll, using one of the approved formulas below. Never open with the brand name. -- **CTA requirement:** exact verbal CTA and timestamp, on-screen CTA text, promo code if any. -- **DOs:** natural lighting, phone-shot, product in frame within 5 seconds, direct-to-camera, genuine reactions. -- **DON'Ts:** no competitor mentions, no unsubstantiated health or income claims, no copyrighted music, no scripted-sounding delivery. Check restricted verticals against platform policy before briefing. -- **Timeline and compensation:** acceptance, draft, revision, and final dates; fee structure from section 6. - -## 3. Pick the hook formula - -- **Problem-Agitation-Solution:** "Stop wasting money on X" → frustrating before-state → "Then I found [Product]" → result → CTA. Best for clear pain points. -- **Before-After-Bridge:** show the before, "This was me a month ago," bridge to the product, transformation, code CTA. Best for visual transformations and SaaS workflows. -- **Testimonial:** "I never thought I'd say this but..." → credibility → specific use case and result → endorsement → CTA. Best for high-trust and B2B products. -- **Curiosity gap / pattern interrupt:** unexpected open with no context, tease the reveal, then the product. Best for TikTok-native content. -- **Listicle:** "3 things I wish I knew about X," product woven into the tips. Best for educational angles. - -Give the creator one formula per video, with the opening line drafted for them. - -## 4. Match platform specs - -| Spec | TikTok / Reels / Shorts | Meta Feed | YouTube pre-roll | -|---|---|---|---| -| Aspect ratio | 9:16 | 1:1 or 4:5 | 16:9 | -| Sweet-spot length | 15-34s | 15-60s | 6s / 15s / 30s | -| Captions | Required (most watch muted) | Required | Optional | -| Text safe zone | Avoid top 15%, bottom 25% | Full frame | Full frame | -| CTA overlay | Last 3-5 seconds | Throughout OK | Last 2 seconds | - -Brief the shot list per format — a 9:16 talking-head does not crop into a usable 1:1. - -## 5. Include a shot list - -Break the video into numbered shots with duration, shot type, description, and audio line. Example 30s unboxing skeleton: arrival excitement (0-3s), open and first reaction (3-6s), product reveal close-up (6-10s), first use (10-15s), natural POV usage (15-22s), final reaction (22-27s), CTA overlay (27-30s). Tutorials and day-in-life follow the same discipline: hook shot first, CTA card last, demo in the middle. - -## 6. Set compensation - -Typical market structure: a base fee per video scaled to follower tier (nano creators from low hundreds, scaling up through micro and mid tiers), plus separately priced usage rights. Organic-only is the 1x baseline; paid amplification runs 1.5-2x, whitelisting from the creator's handle 2-3x, perpetual rights 3-5x, and exclusivity adds 50-100%. Time-box usage rights (30/60/90 days) rather than buying perpetual by default, and consider performance bonuses per view or tracked conversion threshold. - -## 7. Produce, test, ship - -- No creator lined up? `generate_ugc_ad` produces creator-style video from the same brief — hook formula, message, CTA all still apply. -- Upload finals with `upload_creative`; verify placements with `get_ad_readback`. -- Run variants head-to-head with `test_creatives` and read results via `pull__ads_performance` or `tiktok_ads_get_insights`. **creative-testing** covers test design; **video-ad-scriptwriter** covers timing-mark scripting when the creator wants a full script. - -## Rules - -- Lock hook, key messages, and CTA; leave wording and delivery to the creator. -- 2-3 key messages max, one hook formula per video. -- Compliance limits go in the brief, not the revision round. -- Price usage rights explicitly and time-boxed. Never assume paid rights from a base fee. diff --git a/skills/utm-builder/SKILL.md b/skills/utm-builder/SKILL.md index c28cba9..d01d37b 100644 --- a/skills/utm-builder/SKILL.md +++ b/skills/utm-builder/SKILL.md @@ -24,7 +24,7 @@ Clean UTMs are why analytics can tell platforms apart. Inconsistent tags turn re ## Wire it up -Apply the convention across the campaign's final URLs, and confirm the parameters survive to the landing page (redirects sometimes strip them). Check they resolve in analytics with `ga4_run_report` (source/medium) after traffic starts. Consistent UTMs make the **attribution** and **report** skills far more accurate. +Apply the convention across the campaign's final URLs, and confirm the parameters survive to the landing page (redirects sometimes strip them). Check they resolve in analytics with `ga4_get_report` (source/medium) after traffic starts. Consistent UTMs make the **attribution** and **report** skills far more accurate. ## Rules diff --git a/skills/video-ad-scriptwriter/SKILL.md b/skills/video-ad-scriptwriter/SKILL.md index e1366c4..4f984f8 100644 --- a/skills/video-ad-scriptwriter/SKILL.md +++ b/skills/video-ad-scriptwriter/SKILL.md @@ -50,9 +50,9 @@ Test hooks systematically: same body, 3-5 different first-3-second openings, ide ## 5. Produce and ship -- Generate drafts with `generate_video_ad` (or `generate_ugc_ad` for creator-style delivery — see **ugc-creative-brief** for that format's rules). -- Upload finals with `upload_creative`; confirm what is actually live with `get_ad_readback`. -- Read results: `pull__ads_performance` for CTR and CPA, `tiktok_ads_get_insights` for TikTok video metrics (watch time, completion). Judge hooks on retention, not just CTR. +- Draft the written script and shot list in the conversation. Select existing advertiser assets with `list_creative_assets`; the advertiser supplies final media. +- Confirm final asset IDs and campaign scope, then follow **launch** for supported plan changes and approval. +- Review CTR and CPA with supported platform performance tools. Request a platform export for watch-time, completion, and retention metrics when unavailable; do not infer hook retention from clicks. ## Rules diff --git a/tools/directory-tools.json b/tools/directory-tools.json new file mode 100644 index 0000000..d84d815 --- /dev/null +++ b/tools/directory-tools.json @@ -0,0 +1,36 @@ +[ + "get_connection_status", + "list_connected_accounts", + "verify_platform_accounts", + "list_campaigns", + "pull_google_ads_performance", + "pull_meta_ads_performance", + "pull_linkedin_ads_performance", + "pull_microsoft_ads_performance", + "pull_reddit_ads_performance", + "ga4_get_properties", + "ga4_get_report", + "ga4_get_conversions", + "get_attribution", + "get_spend_reconciliation", + "get_pixel_health", + "measure_incrementality", + "audit_account_structure", + "research_campaign_opportunity", + "forecast_campaign", + "optimize_budget", + "create_ad_copy", + "list_audiences", + "list_creative_assets", + "create_campaign_plan", + "get_campaign_plan", + "upsert_plan_entity", + "run_launch_preflight", + "approve_campaign_plan", + "execute_campaign_plan", + "get_plan_execution", + "enable_campaign", + "pause_campaign", + "update_campaign_budget", + "get_job_status" +] From 5a36a1b66f731988e48a02e6aa20d0164e1bf685 Mon Sep 17 00:00:00 2001 From: Joel Horwitz Date: Fri, 2 Oct 2026 10:29:03 -0700 Subject: [PATCH 3/4] Release 1.2.0 with Claude-only hooks and accurate capability guidance --- .claude-plugin/plugin.json | 16 +++++- .cursor-plugin/marketplace.json | 2 +- .cursor-plugin/plugin.json | 2 +- .github/workflows/directory-tools.yml | 5 +- .github/workflows/release.yml | 4 +- CHANGELOG.md | 2 +- README.md | 4 +- agents/audience-builder.md | 14 ++--- agents/budget-optimizer.md | 6 +- agents/media-buyer.md | 2 +- context/brand-and-safety.md | 11 ---- hooks/hooks.json | 14 ----- scripts/build-zip.sh | 1 + scripts/validate-manifests.js | 20 ++++++- scripts/validate-manifests.test.js | 70 +++++++++++++++++++++++ skills/bid-optimization/SKILL.md | 4 +- skills/creative-fatigue-detector/SKILL.md | 2 +- skills/dayparting/SKILL.md | 2 +- skills/launch/SKILL.md | 2 +- skills/optimize/SKILL.md | 4 +- skills/replicate/SKILL.md | 2 +- skills/report/SKILL.md | 4 +- 22 files changed, 134 insertions(+), 59 deletions(-) delete mode 100644 hooks/hooks.json create mode 100644 scripts/validate-manifests.test.js diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index dfe7174..ccc18e7 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -3,7 +3,7 @@ "name": "synter", "displayName": "Synter", "description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution.", - "version": "1.1.2", + "version": "1.2.0", "license": "MIT", "author": { "name": "Synter", @@ -25,5 +25,17 @@ "linkedin-ads", "mcp", "synter" - ] + ], + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/session-context.sh" + } + ] + } + ] + } } diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 56d10c0..5850943 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Synter's official plugin marketplace. The AI Agent Operator for Ads.", - "version": "1.1.2" + "version": "1.2.0" }, "plugins": [ { diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 5042b7c..570e866 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://cursor.com/schemas/cursor-plugin/plugin.json", "name": "synter", "displayName": "Synter", - "version": "1.1.2", + "version": "1.2.0", "description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution.", "author": { "name": "Synter", diff --git a/.github/workflows/directory-tools.yml b/.github/workflows/directory-tools.yml index c2835e3..460b009 100644 --- a/.github/workflows/directory-tools.yml +++ b/.github/workflows/directory-tools.yml @@ -5,7 +5,6 @@ on: push: branches: - main - - feat/mcp-oauth-only-synterai permissions: contents: read @@ -20,5 +19,5 @@ jobs: node-version: '20' - name: Check instruction tool references run: node scripts/check-directory-tools.js - - name: Test extraction and live MCP comparison - run: node --test scripts/check-directory-tools.test.js + - name: Test repository validators + run: node --test scripts/*.test.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b5c7dd..f5e827d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,8 +27,8 @@ jobs: - name: Validate directory tool references run: node scripts/check-directory-tools.js - - name: Test directory validator - run: node --test scripts/check-directory-tools.test.js + - name: Test repository validators + run: node --test scripts/*.test.js - name: Run strict Claude plugin validation run: npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a49459..c5fb8ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the Synter plugin are documented here. This project follows [semantic versioning](https://semver.org). -## Unreleased — 2026-10-01 +## [1.2.0] - 2026-10-02 - Cursor connects to `https://mcp.synterai.com`; Claude retains `https://mcp.syntermedia.ai` to match the connector under review. Both use browser OAuth. - Setup and support documentation now describe the hosted connection, shipped skills, and local hook accurately. diff --git a/README.md b/README.md index 4833bc9..39ea287 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Start a new conversation and run `/synter:quickstart`. Complete Synter’s brows In **Customize → Plugins**, add `Synter-Media-AI/plugin` as a repository marketplace and install `synter`. You can also upload the plugin zip from the [latest GitHub release](https://github.com/Synter-Media-AI/plugin/releases/latest). Start a new conversation and complete the browser OAuth prompt. -Direct installation does not imply that Anthropic has approved a public directory listing. See the [submission notes](CLAUDE_COMMUNITY_SUBMISSION.md) for the recorded review status. +Direct installation does not imply that Anthropic has approved a public directory listing. ### Cursor @@ -40,7 +40,7 @@ Claude retains the connector URL under Anthropic review. Keep the two configurat The plugin connects to the hosted Synter MCP server over OAuth. The advertising tools run on that server. Local Markdown skills, agents, and rules guide how the client uses them; the checked-in [tool directory](tools/directory-tools.json) contains the 34 supported tool names. -Claude also runs one `SessionStart` hook from [hooks/hooks.json](hooks/hooks.json). It invokes [scripts/session-context.sh](scripts/session-context.sh), a Bash script that uses `cat` to print static account-scope and approval guidance. It does not read credentials or make network requests. Cursor declares no hooks and uses [rules/synter-operator.mdc](rules/synter-operator.mdc). +Claude also runs one `SessionStart` hook from [.claude-plugin/plugin.json](.claude-plugin/plugin.json). It invokes [scripts/session-context.sh](scripts/session-context.sh), a Bash script that uses `cat` to print static account-scope and approval guidance. It does not read credentials or make network requests. Cursor declares no hooks and uses [rules/synter-operator.mdc](rules/synter-operator.mdc). There is no local MCP server or bundled compiled binary. The repository includes an optional [Node SDK runner](sdk/README.md), invoked manually, and development scripts for validation and packaging. The release zip excludes the SDK. Agent definitions may delegate work when the host client supports them. diff --git a/agents/audience-builder.md b/agents/audience-builder.md index 3d6a277..f743617 100644 --- a/agents/audience-builder.md +++ b/agents/audience-builder.md @@ -1,20 +1,20 @@ --- name: audience-builder -description: Builds and activates audiences — ABM target lists, lookalikes, signal-based segments, and first-party uploads — and syncs them to ad platforms. Invoke when a user needs to define who to reach, build a target list, or push an audience live. -model: gemini-2.5-pro +description: Reviews existing audiences and plans targeting — ABM criteria, lookalike seeds, first-party segments, and exclusions — for supported campaign-plan fields. Invoke when a user needs to define who to reach. List creation and sync happen in the platform UI. +model: inherit effort: high --- -You are Synter's audience builder. You turn "who we want to reach" into a real, activated segment. +You turn 'who we want to reach' into a reviewed targeting brief using existing audience IDs. Approach: - Inspect existing audiences with `list_audiences`; define account criteria, seeds, exclusions, and consent requirements. Use `research_campaign_opportunity` for available research. Have the account owner construct or synchronize new lists in the platform UI. - Check `list_audiences` first — never rebuild a duplicate. -- Profile before activating: size, match rate, overlap, and whether it clears each platform's minimum (LinkedIn company audiences and Reddit have real floors). Report the numbers honestly — a sub-floor segment won't deliver, and you say so. -- First-party data: canonicalize and hash identifiers before upload; tiny B2B work-email lists often miss the match floor — warn the user. +- Ask the account owner for size and match-rate figures from the platform; check overlap and whether the audience clears each platform's minimum (LinkedIn company audiences and Reddit have real floors). Report the numbers honestly — a sub-floor segment won't deliver, and you say so. +- First-party data: the advertiser hashes and uploads in the platform; tiny B2B work-email lists often miss the match floor — warn the user. - Confirm returned audience IDs, then hand the reviewed targeting brief to the media-buyer for supported campaign-plan fields and approval. Rules: -- Confirm the org/account before activating. Reads do not require approval; activation is an action. +- Confirm the org/account before using an audience in a campaign. Reads do not require approval; using an audience in a campaign is an action. - Respect match-rate floors — surface them, never hide a too-small segment. -- Handle PII correctly: hash where the platform expects hashes; never leak one tenant's data into another. +- Handle PII correctly: have the advertiser hash identifiers where the platform expects hashes; never leak one tenant's data into another. diff --git a/agents/budget-optimizer.md b/agents/budget-optimizer.md index 55ec4c0..55da617 100644 --- a/agents/budget-optimizer.md +++ b/agents/budget-optimizer.md @@ -1,14 +1,14 @@ --- name: budget-optimizer -description: Finds wasted spend and winners across platforms, then reallocates budget by ROAS, tunes bids, and kills losers / scales winners. Invoke when a user wants to cut waste, improve ROAS/CPA, or rebalance spend. Recommends, then acts on approval. -model: gpt-5.6 +description: Finds wasted spend and winners across platforms, then reallocates budget by ROAS, pauses losers, and adjusts budgets toward winners. Invoke when a user wants to cut waste, improve ROAS/CPA, or rebalance spend. Recommends, then acts on approval. +model: inherit effort: high --- You are Synter's budget optimizer. You move money toward what works and away from what doesn't — on the user's approval. Method: -- Pull recent performance across connected platforms (the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`)), reconcile with `get_spend_reconciliation`, and read true drivers with `get_attribution`. +- Pull recent performance across connected platforms using the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`), reconcile with `get_spend_reconciliation`, and read true drivers with `get_attribution`. - Diagnose against minimum-data guardrails — never kill or scale on noise. State the threshold you used. - Wasted spend: spend with no conversions past the threshold. - Winners: low CPA / high ROAS with enough volume to scale. diff --git a/agents/media-buyer.md b/agents/media-buyer.md index 70b6be8..ccaeca8 100644 --- a/agents/media-buyer.md +++ b/agents/media-buyer.md @@ -1,7 +1,7 @@ --- name: media-buyer description: Builds and launches campaigns across platforms once a plan is approved — structure, targeting, budgets, and going live. Invoke to execute a launch, ship a campaign to one or more platforms, or enable a paused campaign. Always confirms before spending. -model: gpt-5.6 +model: inherit effort: high --- diff --git a/context/brand-and-safety.md b/context/brand-and-safety.md index c7e5051..fdcfee4 100644 --- a/context/brand-and-safety.md +++ b/context/brand-and-safety.md @@ -1,9 +1,5 @@ # Synter — Brand Voice & Safety (read before generating copy or spending) -## What Synter is -The AI Agent Operator for Ads. Claude Code for advertising. **You direct, the agents execute.** -One interface. Every ad platform. Ship faster. - ## Advertiser identity Before writing copy or selecting assets, confirm the workspace ID, advertiser name, and domain using account evidence and the user’s verified context. Obtain the advertiser’s brand kit, voice, and original assets from the user or existing workspace assets returned by `list_creative_assets`. Use that context explicitly in supported tool fields. Do not apply Synter’s own voice or branding to another advertiser. Ask for missing brand details; never invent them. @@ -24,13 +20,6 @@ Sound like the agent talking: terse, certain, doing the work. Show, don't sell. - "Act now!"-style hype CTAs. - **"Beta"** in any form — there is no beta. Synter is a live product. -## Approved CTAs & verbs -CTAs: "See how it works", "Book a demo", "Sign up", "Start Growing", "Start Shipping". (Not "Start Saving".) -Verbs/nouns: **Operate** (not manage), **Ship** (not launch/deploy), **Platforms** (not channels), **Direct API** (not integration), **AI Agents** (not bots/automation). - -## Approved headlines -"The AI Agent Operator for Ads." · "Claude Code for advertising." · "You direct, they execute." · "One interface. Every ad platform. Ship faster." · "Direct API connections. No middleware. No sync delays." - ## Accuracy Never fabricate a price, competitor, statistic, or claim. If you don't have the real number, say so. A plain true line beats an invented impressive one. diff --git a/hooks/hooks.json b/hooks/hooks.json deleted file mode 100644 index 327b2c3..0000000 --- a/hooks/hooks.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "hooks": { - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/session-context.sh" - } - ] - } - ] - } -} diff --git a/scripts/build-zip.sh b/scripts/build-zip.sh index 47afeda..0821d83 100755 --- a/scripts/build-zip.sh +++ b/scripts/build-zip.sh @@ -14,6 +14,7 @@ trap 'rm -rf "$STAGE"' EXIT rsync -a \ --exclude '.git' --exclude '.github' --exclude 'dist' \ --exclude 'sdk' --exclude '.cursor-plugin' \ + --exclude 'CLAUDE_COMMUNITY_SUBMISSION.md' \ ./ "$STAGE/synter/" mkdir -p dist diff --git a/scripts/validate-manifests.js b/scripts/validate-manifests.js index 77585d2..aa661df 100644 --- a/scripts/validate-manifests.js +++ b/scripts/validate-manifests.js @@ -143,6 +143,24 @@ for (const manifestPath of ['.claude-plugin/plugin.json', '.cursor-plugin/plugin } } +// (e) Keep the shell hook Claude-only: Cursor also auto-discovers hooks/hooks.json. +if (fs.existsSync(path.join(REPO_ROOT, 'hooks/hooks.json'))) { + errors.push('hooks/hooks.json: forbidden because Cursor auto-discovers this file; keep Claude hooks inline'); +} +const cursorManifest = manifests['.cursor-plugin/plugin.json']; +if (cursorManifest && Object.prototype.hasOwnProperty.call(cursorManifest, 'hooks')) { + errors.push('.cursor-plugin/plugin.json: must not declare a hooks key'); +} +const sessionStartHooks = pluginJson && pluginJson.hooks && pluginJson.hooks.SessionStart; +const sessionContextCommand = '"${CLAUDE_PLUGIN_ROOT}"/scripts/session-context.sh'; +if (!Array.isArray(sessionStartHooks) || !sessionStartHooks.some(entry => + entry && Array.isArray(entry.hooks) && entry.hooks.some(hook => + hook && hook.type === 'command' && hook.command === sessionContextCommand + ) +)) { + errors.push('.claude-plugin/plugin.json: must declare an inline SessionStart command hook pointing at scripts/session-context.sh'); +} + if (errors.length > 0) { console.error('Manifest validation failed:\n'); for (const err of errors) { @@ -152,4 +170,4 @@ if (errors.length > 0) { process.exit(1); } -console.log('All manifests, skill frontmatter, versions, and authentication guards passed.'); +console.log('All manifests, skill frontmatter, versions, authentication guards, and hook isolation checks passed.'); diff --git a/scripts/validate-manifests.test.js b/scripts/validate-manifests.test.js new file mode 100644 index 0000000..c98ef05 --- /dev/null +++ b/scripts/validate-manifests.test.js @@ -0,0 +1,70 @@ +'use strict'; +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { spawnSync } = require('node:child_process'); + +function fixture(t) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'synter-manifests-test-')); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + for (const entry of ['.claude-plugin', '.cursor-plugin', '.mcp.json', 'mcp.json', 'skills']) { + fs.cpSync(path.join(__dirname, '..', entry), path.join(root, entry), { recursive: true }); + } + fs.mkdirSync(path.join(root, 'scripts')); + const script = path.join(root, 'scripts/validate-manifests.js'); + fs.copyFileSync(path.join(__dirname, 'validate-manifests.js'), script); + return { + root, + edit(client, mutate) { + const file = path.join(root, `.${client}-plugin/plugin.json`); + const manifest = JSON.parse(fs.readFileSync(file, 'utf8')); + mutate(manifest); + fs.writeFileSync(file, JSON.stringify(manifest)); + }, + validate() { + return spawnSync(process.execPath, [script], { encoding: 'utf8' }); + }, + }; +} + +test('accepts the Claude-only inline hook configuration', t => { + const result = fixture(t).validate(); + assert.equal(result.status, 0, result.stderr); +}); + +test('rejects a shared hook file that Cursor would auto-discover', t => { + const repo = fixture(t); + fs.mkdirSync(path.join(repo.root, 'hooks')); + fs.writeFileSync(path.join(repo.root, 'hooks/hooks.json'), '{}'); + const result = repo.validate(); + assert.equal(result.status, 1); + assert.match(result.stderr, /hooks\/hooks.json: forbidden/); +}); + +for (const hooks of [null, {}]) { + test(`rejects a Cursor hooks key even when set to ${JSON.stringify(hooks)}`, t => { + const repo = fixture(t); + repo.edit('cursor', manifest => { manifest.hooks = hooks; }); + const result = repo.validate(); + assert.equal(result.status, 1); + assert.match(result.stderr, /must not declare a hooks key/); + }); +} + +for (const [name, mutate] of [ + ['missing hook', manifest => { delete manifest.hooks; }], + ['file reference instead of inline hook', manifest => { manifest.hooks = './hooks/hooks.json'; }], + ['wrong event', manifest => { manifest.hooks = { Stop: manifest.hooks.SessionStart }; }], + ['wrong command', manifest => { manifest.hooks.SessionStart[0].hooks[0].command = 'echo wrong'; }], + ['wrong hook type', manifest => { manifest.hooks.SessionStart[0].hooks[0].type = 'prompt'; }], +]) { + test(`rejects Claude configuration with ${name}`, t => { + const repo = fixture(t); + repo.edit('claude', mutate); + const result = repo.validate(); + assert.equal(result.status, 1); + assert.match(result.stderr, /must declare an inline SessionStart command hook/); + }); +} diff --git a/skills/bid-optimization/SKILL.md b/skills/bid-optimization/SKILL.md index 8321316..e552788 100644 --- a/skills/bid-optimization/SKILL.md +++ b/skills/bid-optimization/SKILL.md @@ -1,6 +1,6 @@ --- name: bid-optimization -description: Recommend and apply bid and bid-strategy changes from performance data — CPC targets, device and audience modifiers, and target CPA/ROAS moves. Use when a user wants to lower CPCs, hit a target CPA or ROAS, or adjust bids across campaigns. +description: Recommend bid and bid-strategy changes from performance data — CPC targets, device and audience modifiers, and target CPA/ROAS moves. Use when a user wants to lower CPCs, hit a target CPA or ROAS, or adjust bids across campaigns. --- # Bid Optimization @@ -27,7 +27,7 @@ Pull recent campaign performance with `pull_google_ads_performance`, `pull_meta_ ## 4. Apply on approval -Apply through the plan tools or `update_campaign_budget` where budget is the real constraint. Confirm the change and re-measure after the platform has had time to adjust. +Apply budget changes with `update_campaign_budget` on approval. Hand bid-strategy and modifier changes on existing campaigns to the platform UI unless the live plan schema supports them; confirm and re-measure. ## Rules diff --git a/skills/creative-fatigue-detector/SKILL.md b/skills/creative-fatigue-detector/SKILL.md index 5243871..2a70f8d 100644 --- a/skills/creative-fatigue-detector/SKILL.md +++ b/skills/creative-fatigue-detector/SKILL.md @@ -5,7 +5,7 @@ description: "Diagnose creative fatigue; refresh cadence; when to swap/rotate ad # Creative Fatigue Detection & Refresh Cadence -Use Synter-calibrated thresholds to decide when to refresh creatives BEFORE performance collapses. Most teams refresh too late — the goal is to act on early warnings, not wait for CPA to blow up. +Use these typical starting thresholds; prefer the account's own history to decide when to refresh creatives BEFORE performance collapses. Most teams refresh too late — the goal is to act on early warnings, not wait for CPA to blow up. ## Early Warning Signals (act on these BEFORE CPA spikes) diff --git a/skills/dayparting/SKILL.md b/skills/dayparting/SKILL.md index 67c211e..ab4381f 100644 --- a/skills/dayparting/SKILL.md +++ b/skills/dayparting/SKILL.md @@ -25,7 +25,7 @@ Build an ad schedule with bid modifiers per slot: raise where it converts, lower ## 4. Apply on approval -Apply through the plan tools and confirm. Re-pull after a couple of weeks to check the pattern held; buyer behavior shifts seasonally (see **kill-scale-rules** for scaling discipline). +Hand the schedule and modifiers to the account owner for the platform UI unless the live plan schema supports them; confirm after it is applied. Re-pull after a couple of weeks to check the pattern held; buyer behavior shifts seasonally (see **kill-scale-rules** for scaling discipline). ## Rules diff --git a/skills/launch/SKILL.md b/skills/launch/SKILL.md index fe1a8d7..e19d06f 100644 --- a/skills/launch/SKILL.md +++ b/skills/launch/SKILL.md @@ -1,6 +1,6 @@ --- name: launch -description: Plan and launch a cross-platform ad campaign — strategy, targeting, creative, budget, and preflight — then ship it on the user's approval. Use when a user wants to launch, build, or run a new campaign on one or more platforms (Google, Meta, LinkedIn, Reddit, TikTok, X, and more). +description: Plan and launch a cross-platform ad campaign — strategy, targeting, creative, budget, and preflight — then ship it on the user's approval. Use when a user wants to launch, build, or run a new campaign on one or more platforms (Google, Meta, LinkedIn, Reddit). --- # Launch a Campaign diff --git a/skills/optimize/SKILL.md b/skills/optimize/SKILL.md index 1731b92..4b11e42 100644 --- a/skills/optimize/SKILL.md +++ b/skills/optimize/SKILL.md @@ -1,6 +1,6 @@ --- name: optimize -description: Improve campaign performance — reallocate budget by ROAS, tune bids, kill losers and scale winners, and catch anomalies across platforms. Use when a user wants to cut wasted spend, improve ROAS/CPA, rebalance budget, or act on what's underperforming. +description: Improve campaign performance — reallocate budget by ROAS, recommend bid changes for manual application, kill losers and scale winners, and catch anomalies across platforms. Use when a user wants to cut wasted spend, improve ROAS/CPA, rebalance budget, or act on what's underperforming. --- # Tune & Reallocate @@ -28,7 +28,7 @@ Present a clear plan: what to cut, what to scale, where the freed budget goes, a - `optimize_budget` — propose a cross-platform allocation for review. - `update_campaign_budget` — set a specific budget (guard against fat-finger amounts; default to sane ceilings). - `pause_campaign` — kill a loser. -- Bid/budget tuning per platform as supported. +- Bid-strategy changes: recommend them and hand off to the platform UI. Scale winners in steps (platform-appropriate budget increments), not all at once — big jumps reset the learning phase. diff --git a/skills/replicate/SKILL.md b/skills/replicate/SKILL.md index 3f71b06..36c8a00 100644 --- a/skills/replicate/SKILL.md +++ b/skills/replicate/SKILL.md @@ -18,7 +18,7 @@ Find the source campaign: `list_campaigns(platform="")`. Get its real ID Pull the full structure, not just top-line metrics: - Read `list_campaigns`, `audit_account_structure`, and `pull_google_ads_performance` for a Google source. Ask for platform exports when budgets, geo criteria, keyword match types, negatives, or complete ad text are missing. Do not replicate unknown settings. -- **Other sources (Meta, LinkedIn, TikTok, Reddit, X):** use `list_campaigns` + the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`) and the matching platform playbook skill to read structure, audiences, budgets, and creative. +- **Other sources (Meta, LinkedIn, Reddit, Microsoft):** use `list_campaigns` and the matching performance tool (`pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_reddit_ads_performance`, or `pull_microsoft_ads_performance`) for performance; request platform exports for structure, audiences, and creative. TikTok and X sources require a platform export. Write down the source structure explicitly before you build. If you cannot read a piece, say so — never invent a keyword list or a budget. diff --git a/skills/report/SKILL.md b/skills/report/SKILL.md index 38ee075..12ef44b 100644 --- a/skills/report/SKILL.md +++ b/skills/report/SKILL.md @@ -5,11 +5,11 @@ description: Build a cross-channel performance report — spend, conversions, CP # Cross-Channel Report -Pull every platform, reconcile, and tell the story straight. +Pull every connected platform the tools support, reconcile, and tell the story straight. ## 1. Gather -Pull the period the user wants across all connected platforms (the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`)). Add analytics ground truth: `ga4_get_report` for sessions/conversions, `get_attribution` for conversion paths. +Pull the period the user wants across all connected platforms using the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`). Add analytics ground truth: `ga4_get_report` for sessions/conversions, `get_attribution` for conversion paths. ## 2. Reconcile From f6a6e0c596fbf63643b62a373d0b02eb8a48f2ba Mon Sep 17 00:00:00 2001 From: Joel Horwitz Date: Fri, 2 Oct 2026 10:35:47 -0700 Subject: [PATCH 4/4] Tighten first-party data, Meta placement, and audience skill wording --- skills/audience/SKILL.md | 2 +- skills/first-party-data-strategy/SKILL.md | 8 ++++---- skills/platform-meta/SKILL.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/skills/audience/SKILL.md b/skills/audience/SKILL.md index ca6d85e..ca6b263 100644 --- a/skills/audience/SKILL.md +++ b/skills/audience/SKILL.md @@ -1,6 +1,6 @@ --- name: audience -description: Review existing audiences and plan account-specific targeting, exclusions, and activation requirements. +description: Review existing audiences and plan account-specific targeting, exclusions, and targeting and list requirements. --- # Audience planning diff --git a/skills/first-party-data-strategy/SKILL.md b/skills/first-party-data-strategy/SKILL.md index d032e16..1aeb40d 100644 --- a/skills/first-party-data-strategy/SKILL.md +++ b/skills/first-party-data-strategy/SKILL.md @@ -1,11 +1,11 @@ --- name: first-party-data-strategy -description: Build a post-cookie first-party data strategy — Enhanced Conversions, server-side tracking, Consent Mode v2, and activating owned data for targeting. Use when a user wants to recover lost conversion data, set up Enhanced Conversions or CAPI, implement consent mode, or plan for cookie deprecation. +description: Build a post-cookie first-party data strategy — Enhanced Conversions, server-side tracking, Consent Mode v2, and planning how to use owned data for targeting. Use when a user wants to recover lost conversion data, set up Enhanced Conversions or CAPI, implement consent mode, or plan for cookie deprecation. --- # First-Party Data Strategy -Third-party cookies are gone; the accounts that keep measuring are the ones that collect, consent, and send their own data. Build that pipeline in order: audit, capture, consent, activate. +Third-party cookies are gone; the accounts that keep measuring are the ones that collect, consent, and send their own data. Build that pipeline in order: audit, capture, consent, then plan how to use it. Confirm the workspace with `list_connected_accounts` and `get_connection_status`; read `get_pixel_health` for the available tracking diagnostics. @@ -25,7 +25,7 @@ Inspect the available diagnostics with `get_pixel_health`. Have the account owne ## 3. Move tracking server-side -Client-only tags lose data to ad blockers and 7-day browser cookie caps. A server-side GTM container on a first-party subdomain (track.yourdomain.com) extends cookie lifespan to 1-2 years, enriches events before forwarding (LTV, customer segment), and sends once to every platform API. This is the single highest-leverage tracking improvement for a returning-visitor business. +Client-only tags lose data to ad blockers and 7-day browser cookie caps. A server-side GTM container on a first-party subdomain (track.yourdomain.com) extends cookie lifespan to 1-2 years, enriches events before forwarding (LTV, customer segment), and sends once to the platforms Synter connects to. This is the single highest-leverage tracking improvement for a returning-visitor business. ## 4. Implement Consent Mode v2 @@ -40,7 +40,7 @@ Set default consent state to denied before the CMP loads, update on user choice, Uploaded lists only work as well as they match. Rough expectations: hashed email alone matches 40-60% on Google and 50-70% on Meta; adding phone pushes combined match to 55-80%. LinkedIn matches better on work email than personal. Improve matches by sending multiple identifiers per record and normalizing before hashing. If a synced list delivers to far fewer users than you uploaded, fix the data before blaming the platform. -## 6. Activate the data for targeting +## 6. Prepare the data for upload in each platform's own tools Collected data earns its keep in audiences: diff --git a/skills/platform-meta/SKILL.md b/skills/platform-meta/SKILL.md index aae37fb..875fe1f 100644 --- a/skills/platform-meta/SKILL.md +++ b/skills/platform-meta/SKILL.md @@ -15,7 +15,7 @@ Campaign (objective) → ad set (audience, budget, placements, schedule) → ad - Review existing audiences with `list_audiences`. Use `research_campaign_opportunity` for available campaign research. Ask the account owner to create or synchronize new audience lists in the platform UI; use returned IDs only in supported plan fields. - **Advantage+** shifts targeting and placement decisions to Meta. Use it when data volume supports it; keep enough signal flowing. -- **Placements:** Feed, Stories, Reels, and more. Creative should fit the placement, not be stretched into it. +- **Placements:** Feed, Stories, and Reels. Creative should fit the placement, not be stretched into it. ## Read a campaign