Status: Implemented. This document describes the architecture as built, not a future proposal.
History: The original plan proposed separate repos with a sed/awk build pipeline. A second iteration proposed symlinks. Both were replaced by the current approach: template compilation with conditional blocks. See Appendix A for the archived plans.
The ads-mcp public repo bundles the MCP registry listing plus plugins for 4 platforms: Claude Code (at repo root), Cursor (plugins/cursor/), Codex (plugins/codex/), and OpenClaw (plugins/openclaw/). Skills are ~95% identical across the 3 template-compiled IDEs — only context file names, auth instructions, tool availability, and memory support differ. OpenClaw uses a standalone skill with a different structure.
The problem this solves: ~1,300 lines of skill content were duplicated across IDE plugins. When a new ad platform is added (YouTube Ads, Snapchat Ads) or a workflow changes, the same edit had to be made in 3 places. As skills grow, this drift compounds. Adding a new skill for N IDEs should be O(1) editing, not O(N).
What this repo is NOT: This is a public repo for MCP server registration and client-side plugin distribution. The actual backend — tool implementations, Cloud SQL connections, ad platform integrations, deployments — lives in a private repo.
- Author once in
shared/skills/— templates with{{variables}}and<!-- BEGIN:BLOCK -->conditional blocks - Run
scripts/sync-skills.sh— compiles templates into IDE-specific skill files - Commit both the templates and the generated files
- Validate with
scripts/validate.sh— 39 checks ensure consistency
Every major platform natively supports subdirectories within a single repo:
| Platform | Subdirectory Support | Mechanism |
|---|---|---|
| Cursor | Yes, native | marketplace.json maps plugin names to subdirectories |
| Claude Code | Yes, native | Marketplace source field supports relative paths |
| Codex | Yes, native | Skills are directories with SKILL.md — scans from CWD to repo root |
| MCP Registry | Yes, native | server.json has repository.subfolder field |
| GitHub raw URLs | Yes | Full path-based access to any file in any subdirectory |
An earlier iteration of this plan proposed symlinking plugins/*/skills/ to shared/skills/. This was rejected because:
- GitHub raw URLs don't follow symlinks — install scripts that
curlfromraw.githubusercontent.comwould get the symlink text, not the file content - The IDE differences turned out to be more than variable substitution — Codex needs WebSearch/WebFetch tool references stripped, memory blocks removed, and different auth troubleshooting text. Symlinks can't handle conditional content.
- Generated files are always correct — no risk of broken symlink resolution at clone time or on Windows
| Component | Why it differs | Handling |
|---|---|---|
| Context file name | CLAUDE.md vs BRAND.md vs AGENTS.md |
{{CONTEXT_FILE}} variable |
| Auth troubleshooting | Browser OAuth vs CLI login | {{AUTH_TROUBLESHOOT}} variable |
| Memory support | Cursor + Claude Code have it, Codex doesn't | <!-- BEGIN:HAS_MEMORY --> / <!-- BEGIN:NO_MEMORY --> blocks |
| IDE-specific instructions | Different step-by-step flows | <!-- BEGIN:CURSOR_CLAUDE --> / <!-- BEGIN:CODEX --> blocks |
| WebSearch/WebFetch tools | Cursor + Claude Code have native web tools, Codex doesn't | Sed-based tool name stripping (see Known Tech Debt) |
| Agent definitions | .md (Claude/Cursor) vs .toml (Codex) |
Separate per IDE, not templated |
| Rules | .mdc (Cursor) vs .rules (Codex) |
Separate per IDE, not templated |
| Config manifests | plugin.json vs config.toml |
Separate per IDE, not templated |
| Install scripts | Different target paths and config formats | Separate per IDE |
- All 5 skill SKILL.md files (~1,300 lines total)
- Tool reference documentation and workflow definitions
- Safety rules within skills (paused campaigns, budget guardrails, confirmation requirements)
- Campaign management procedures (research, creation, optimization, reporting)
ads-mcp/
├── shared/ # SOURCE OF TRUTH — edit here
│ └── skills/
│ ├── adspirer-ads/SKILL.md # ~472 lines — main campaign management skill
│ ├── adspirer-setup/SKILL.md # ~154 lines — brand workspace bootstrap
│ ├── adspirer-write-ad-copy/SKILL.md
│ ├── adspirer-wasted-spend/SKILL.md
│ └── adspirer-performance-review/SKILL.md
│
├── scripts/
│ ├── sync-skills.sh # Template compiler (generates IDE-specific skills)
│ └── validate.sh # 39-check validation suite
│
├── [ROOT — Claude Code plugin + MCP registry]
│ ├── .claude-plugin/
│ ├── agents/performance-marketing-agent.md
│ ├── skills/ad-campaign-management/SKILL.md # GENERATED from shared/skills/adspirer-ads
│ ├── commands/ # Claude Code only (setup, wasted-spend, etc.)
│ ├── .mcp.json, server.json, settings.json
│ └── README.md, TERMS.md, PRIVACY.md, etc.
│
├── plugins/
│ ├── cursor/adspirer/
│ │ ├── .cursor/
│ │ │ ├── plugin.json
│ │ │ ├── agents/performance-marketing-agent.md
│ │ │ ├── rules/use-adspirer.mdc, brand-workspace.mdc
│ │ │ └── skills/ # GENERATED — 5 skills
│ │ ├── mcp.json
│ │ ├── install.sh
│ │ └── README.md
│ ├── codex/adspirer/
│ │ ├── .codex/config.toml
│ │ ├── agents/performance-marketing-agent.toml
│ │ ├── rules/campaign-safety.rules
│ │ ├── skills/ # GENERATED — 5 skills
│ │ │ └── adspirer-ads/agents/openai.yaml # Codex-specific extra (preserved by sync)
│ │ ├── install.sh
│ │ └── README.md
│ └── openclaw/
│ ├── claw.json # Registry manifest (standalone, NOT templated)
│ ├── SKILL.md # Self-contained skill (326 lines, NOT generated)
│ └── README.md
│
├── docs/
│ ├── architecture.md # Template syntax, sync workflow
│ ├── adding-platforms.md # How to add YouTube Ads, Snapchat, etc.
│ └── adding-ides.md # How to add Windsurf, Cline, etc.
│
└── .gitignore
Files in plugins/*/skills/ and skills/ad-campaign-management/ are generated — never edit them directly. They will be overwritten by sync-skills.sh.
Files in plugins/*/agents/, plugins/*/rules/, commands/, and config files are IDE-specific — edit them directly. They are not templated because their formats are fundamentally different across IDEs.
Templates use {{VARIABLE}} placeholders replaced at build time:
| Variable | Cursor | Codex | Claude Code |
|---|---|---|---|
{{CONTEXT_FILE}} |
BRAND.md |
AGENTS.md |
CLAUDE.md |
{{AUTH_TROUBLESHOOT}} |
Reconnect via your AI assistant's connector settings | Run `codex mcp login adspirer` to re-authenticate | Reconnect via your AI assistant's connector settings |
Templates use HTML comment markers for IDE-specific sections:
<!-- BEGIN:CURSOR_CLAUDE -->
Content only in Cursor and Claude Code versions.
<!-- END:CURSOR_CLAUDE -->
<!-- BEGIN:CODEX -->
Content only in Codex version.
<!-- END:CODEX -->
<!-- BEGIN:HAS_MEMORY -->
Content only for IDEs with persistent memory (Cursor, Claude Code).
<!-- END:HAS_MEMORY -->
<!-- BEGIN:NO_MEMORY -->
Content only for IDEs without memory (Codex).
<!-- END:NO_MEMORY -->| Parameter | Cursor | Codex | Claude Code |
|---|---|---|---|
context_file |
BRAND.md | AGENTS.md | CLAUDE.md |
keep_block |
CURSOR_CLAUDE | CODEX | CURSOR_CLAUDE |
keep_websearch |
yes | no | yes |
keep_memory |
yes | no | yes |
| Skills generated | All 5 | All 5 | Only adspirer-ads (renamed ad-campaign-management) |
Claude Code's other 4 workflows (setup, performance-review, write-ad-copy, wasted-spend) are implemented as slash commands in commands/, not as skills. This is a deliberate design choice — Claude Code's command system provides a better UX for these single-purpose workflows than the skill system does.
The template compiler. Reads templates from shared/skills/, applies per-IDE configuration, writes generated files to each IDE's skill directory.
./scripts/sync-skills.sh # Generate all IDE-specific skills
./scripts/sync-skills.sh --check # Verify generated files match committed files (CI mode)
./scripts/sync-skills.sh --diff # Show differences between generated and committedComprehensive validation suite with 39 checks:
./scripts/validate.sh # All offline checks
./scripts/validate.sh --live # Also test MCP endpoint connectivityChecks performed:
- Sync consistency — templates generate files matching committed skills
- Frontmatter validation — all skills have
nameanddescriptionfields - Skill inventory — all expected skills present in all IDE targets
- Context file correctness — no cross-contamination (BRAND.md never in Codex, etc.)
- No leaked template markers — no
{{...}}or<!-- BEGIN: -->in generated files - Codex extras preserved —
openai.yamlnot deleted by sync - OpenClaw files present —
claw.jsonandSKILL.mdexist with valid metadata - Shared templates exist — all 5 source templates present
- MCP endpoint reachable (with
--liveflag)
- Edit the template in
shared/skills/<skill>/SKILL.md - Run
./scripts/sync-skills.sh - Run
./scripts/validate.shto verify consistency - Commit all generated files along with the template
- Create
shared/skills/adspirer-youtube-ads/SKILL.mdwith template syntax - Run
./scripts/sync-skills.sh— all 3 IDEs get the new skill automatically - If the skill needs a Claude Code command, add
commands/youtube-ads.md - If the skill needs IDE-specific content, add conditional blocks
- Run
./scripts/validate.sh, commit
Effort: Write once, get 3 IDE versions. This is the core value of the architecture.
- Create
plugins/windsurf/with IDE-specific files (agent, rules, config, install.sh) - Add the new IDE to
sync-skills.sh(copy the Cursor block, change variables) - Add the new IDE to
validate.sh(add inventory checks, context file checks) - If the IDE has unique constraints, add a new conditional block type (e.g.,
<!-- BEGIN:WINDSURF -->) - Run sync + validate, commit
See docs/adding-ides.md for detailed instructions.
The backend team adds new MCP tools. On this repo's side:
- Add the new platform's workflows to
shared/skills/adspirer-ads/SKILL.md - Add platform-specific tools to the tool reference table
- Optionally create a dedicated skill (e.g.,
shared/skills/adspirer-snapchat-ads/SKILL.md) - Run sync + validate, commit
See docs/adding-platforms.md for detailed instructions.
The sync script uses 16 hardcoded sed substitutions to strip WebSearch/WebFetch tool references for Codex (which doesn't have native web tools). These are brittle — if someone rewrites a sentence in the shared skill that mentions WebFetch, the corresponding sed rule silently stops matching.
Current approach (sync-skills.sh:63-86):
# 16 case-specific sed rules like:
sed 's/Use `WebFetch` to crawl/Crawl/g'
sed 's/Use `WebSearch` to search for:/Search for:/g'
# ...etcRecommended fix: Replace these with a <!-- BEGIN:HAS_WEBSEARCH --> conditional block, matching the pattern already used for memory and IDE blocks. This would:
- Eliminate all 16 fragile sed rules
- Make WebSearch-dependent content explicitly visible in templates
- Follow the same pattern contributors already understand
| Platform | Plugin Format | Context File | Agent Format | Skill Format | Status |
|---|---|---|---|---|---|
| Claude Code | .claude-plugin/, agents/, skills/, commands/ |
CLAUDE.md |
.md + YAML frontmatter | SKILL.md | Implemented |
| Cursor | .cursor/plugin.json, agents/, skills/, rules/ |
BRAND.md |
.md + YAML frontmatter | SKILL.md | Implemented |
| Codex | .codex/config.toml, agents/, skills/, rules/ |
AGENTS.md |
.toml | SKILL.md | Implemented |
| OpenClaw | claw.json + SKILL.md | N/A (self-contained) | Embedded in skill | SKILL.md + claw.json | Implemented (standalone) |
OpenClaw uses a single self-contained skill (claw.json + SKILL.md) that combines agent behavior, safety rules, tool reference, pricing, and troubleshooting. Its SKILL.md has a completely different structure — not generated from shared templates. The TypeScript plugin code (OAuth, MCP client, tool registry) is published separately as the openclaw-adspirer npm package and installed via openclaw plugins install openclaw-adspirer. The skill is listed on ClawHub.
Current status: 39/39 checks passing.
-
shared/skills/contains all 5 canonical templates -
scripts/sync-skills.shgenerates IDE-specific skills from templates -
scripts/validate.shruns 39 checks with 0 failures - Cursor skills reference only
BRAND.md - Codex skills reference only
AGENTS.md - Claude Code skill references only
CLAUDE.md - No
{{...}}or<!-- BEGIN: -->markers leak into generated files - Codex
openai.yamlextra preserved after sync - All frontmatter has
nameanddescriptionfields - Claude Code root plugin works (agents, commands, generated skill)
- Install scripts reference generated files (not templates)
- OpenClaw skill (
plugins/openclaw/claw.json+SKILL.md) present - OpenClaw
claw.jsonhas validnamefield
- GitHub Action to run
validate.shon PRs - Replace WebSearch/WebFetch sed rules with
<!-- BEGIN:HAS_WEBSEARCH -->blocks - Clean up stale feature branches
| Aspect | Plan A: Separate Repos | Plan B: Symlinks | Implemented: Template Compilation |
|---|---|---|---|
| Repos | 4 | 1 | 1 |
| Build tooling | build.sh + publish.sh | None | sync-skills.sh (91 lines) |
| Validation | None | None | validate.sh (39 checks) |
| Skill duplication | Eliminated via {{vars}} |
Eliminated via symlinks | Eliminated via templates + conditional blocks |
| IDE-specific content | sed/awk at publish time | Agent handles it at runtime | Conditional blocks at build time |
| WebSearch handling | Template variable | Hope the agent figures it out | Sed stripping (works, but fragile) |
| Memory handling | Template variable | Hope the agent figures it out | <!-- BEGIN:HAS_MEMORY --> blocks |
| GitHub raw URLs | Work (separate repos) | Broken (symlinks not followed) | Work (generated files committed) |
| CI readiness | No | No | Yes (--check mode) |
| Adding a new skill | Edit + rebuild + publish to 3 repos | Edit once | Edit once + run sync |
| Adding a new IDE | New repo + publish pipeline | New dir + symlink | New dir + add to sync script |
Click to expand Plan A: Separate Repos (archived)
Author shared content in one place (ads-mcp), publish each IDE plugin to its own standalone repo for clean installs.
- All major platforms support subdirectories — no need for separate repos
- Shell-based templating (sed/awk) is fragile for multi-line blocks and special characters
- 3 extra repos create maintenance overhead
- publish.sh force-replaces files, destroying standalone git history
- No CI/CD was defined
- No versioning strategy
Click to expand Plan B: Symlinks (archived)
Replace duplicated skill files with symlinks pointing to shared/skills/.
- GitHub raw URLs don't follow symlinks — install scripts that
curlfromraw.githubusercontent.comwould break - IDE differences are more than variable substitution — Codex needs WebSearch/WebFetch references stripped, memory blocks removed, different auth text. Symlinks can't conditionally modify content.
- Generated files are safer — no risk of broken symlink resolution on clone, across platforms (Windows), or in CI
| Variable | Cursor | Codex |
|---|---|---|
{{CONTEXT_FILE}} |
BRAND.md |
AGENTS.md |
{{MEMORY_PATH}} |
.cursor/memory/.../MEMORY.md |
(empty — stripped) |
{{TROUBLESHOOT_AUTH}} |
Reconnect via connector settings |
Run codex mcp login |
{{AUTH_INSTRUCTIONS}} |
(multi-line block) | (multi-line block) |