Content Calendar
Build a data-backed content calendar. Scrapes the user's own blog to understand current coverage, profiles competitor topics, pulls keyword gap data from Ahrefs, optionally layers in GSC/GA4 performance signals, then produces a prioritized list of articles to write over the next quarter.
Setup
Recommended:
- Ahrefs MCP — the official remote MCP server at
https://api.ahrefs.com/mcp/mcp. Connect via Claude Code's MCP settings (OAuth, paid Ahrefs plan required). Used here for keyword volume, difficulty, and content-gap data via Keywords Explorer and Site Explorer. FIRECRAWL_API_KEY— crawl own blog and competitor sitemaps.
Optional:
GSC_SERVICE_ACCOUNT_JSON+GSC_SITE_URL— declining pages to refresh, striking-distance queries to push.GA4_SERVICE_ACCOUNT_JSON+GA4_PROPERTY_ID— top-performing existing content to cluster around and repurpose.SE_RANKING_API_KEY— alternative rankings data source.
Inputs
- Own blog URL — root or blog index.
- Competitor list — 3–10 domains (can reuse the list from a prior
competitor-analysisrun if present atcompetitors/<date>-raw.json). - Calendar horizon — one month, quarter, or half year. Default quarter.
- Cadence — posts per week or month the user plans to publish.
- Focus areas — optional thematic pillars (e.g. "sales enablement, RevOps, onboarding"). Inherit from
editorial-guidelines.mdif the user has listed content pillars there. - Include refreshes? — default Y. Reuse GSC declining-pages data if configured.
Use AskUserQuestion for horizon and refreshes Y/N.
Process
1. Inventory the user's own blog
Scrape the user's blog via the Firecrawl → Jina → Playwright chain:
- Pull the sitemap if available; otherwise crawl the blog index.
- For each post, extract: URL, title, H1, publish date, updated date, primary topic/entity cluster, rough word count.
Cluster existing posts into topics using entity overlap. Produce a map of {cluster: [posts]}.
2. Inventory the competitors
Reuse competitors/<latest>-raw.json if present. Otherwise crawl each competitor blog via the same chain and build the same cluster map.
Flag topics where:
- Competitors have ≥3 posts and the user has 0 → white-space topic.
- Competitors have ≥3 posts and the user has 1 → under-covered topic.
- User has ≥3 posts, competitors have 0–1 → user-dominated topic (defend).
3. Keyword gap (Ahrefs MCP)
Call the Site Explorer → content gap tool: own domain vs competitors. Capture keywords all competitors rank for but the user doesn't. Enrich each with volume, KD, and parent topic via Keywords Explorer → overview.
Group keywords into topic clusters. Each cluster becomes a candidate article with:
- Suggested primary keyword (highest-volume with KD in range)
- 5–10 secondary keywords
- Estimated traffic ceiling (sum of volumes × conservative CTR)
4. Performance signals (optional)
GSC:
- Striking distance (pos 8–20): convert each into either a new post or an optimization of an existing one.
- Declining queries: find the landing page, suggest a refresh.
- Impressions but no clicks (high impressions, CTR <1%): meta/title rewrite candidates.
GA4:
- Top 20 pages by engaged sessions in the last 90 days → suggest a cluster expansion around each.
- Pages with high entrance but high exit rate → UX + internal-link fixes.
5. Score and prioritize
For each candidate article, compute a score:
score = (volume × relevance_to_pillars × brand_fit)
/ (kd × effort_estimate)
× traffic_performance_multiplier
Where:
volume— Ahrefs monthly search volumerelevance_to_pillars— 0.5 if off-pillar, 1.0 on-pillar, 1.3 on-pillar + in white-space topicbrand_fit— 1.0 default, 0.7 if the topic doesn't match the brand's ICPkd— Ahrefs difficulty, floored at 5effort_estimate— 1 for comparison pages and listicles, 2 for deep how-to, 3 for original-research piecestraffic_performance_multiplier— 1.2 if there's GSC striking-distance overlap, 1.0 otherwise
Rank candidates by score. Split into:
- Must-write (top N to fill cadence)
- Nice-to-write (next N)
- Refresh queue (existing posts to update)
6. Sequence the calendar
Assign each must-write entry to a week based on:
- Cadence (e.g. 2 posts/week)
- Topic clustering (don't publish two posts from the same cluster in the same week)
- Seasonality if the user flagged any
- Any known product launches or events the user mentioned
7. Pre-seed briefs
For each must-write entry, create a lightweight seed in briefs/<slug>.stub.md with:
- Primary keyword
- Secondary keywords
- Estimated volume and KD
- Topic cluster
- Suggested title
- One-line angle
The user can later run the content-brief skill against each stub to expand it into a full brief.
8. Write the calendar file
Write to calendar/<horizon>-<YYYY-MM>.md:
# Content Calendar — {horizon}
_Generated: {YYYY-MM-DD}_
_Own blog: {url}_
_Competitors: {n}_
_Cadence: {posts}/week_
_Horizon: {start} → {end}_
## Summary
- **New articles planned:** {n}
- **Refreshes planned:** {n}
- **Estimated traffic ceiling:** {n}/month
- **Top topic clusters to win:** {list}
## Must-write
| Week | Date | Title | Primary kw | Volume | KD | Cluster | Score | Brief stub |
|---|---|---|---|---|---|---|---|---|
| W1 | 2026-04-15 | {title} | {kw} | 2,400 | 32 | {cluster} | 48 | `briefs/{slug}.stub.md` |
## Nice-to-write (backlog)
| Title | Primary kw | Volume | KD | Why it didn't make the cut |
|---|---|---|---|---|
## Refresh queue
| Existing URL | Declining query | Current pos | Potential | Recommended change |
|---|---|---|---|---|
## Cluster coverage map
| Cluster | Existing posts | Planned | Competitor posts | Gap |
|---|---|---|---|---|
## Notes
- White-space topics: {list}
- Threats (user-dominated topics where competitors are gaining): {list}
- Sources used: Ahrefs {Y/N}, Firecrawl {Y/N}, GSC {Y/N}, GA4 {Y/N}
Also save raw scoring data to calendar/<horizon>-<YYYY-MM>.raw.json.
9. Print summary
One block: N articles planned, N refreshes queued, top cluster, path to calendar, path to stubs directory.
Fallbacks
- No Ahrefs MCP: skip keyword scoring; rank candidates by (relevance × competitor_coverage_count). Clearly mark the calendar as "content-only prioritization" in the header.
- No GSC / GA4: skip performance signals; rely entirely on gap analysis. The traffic multiplier becomes 1.0 for everything.
- Competitor blog scrapes time out: work with partial competitor coverage; list which competitors were skipped.
- No editorial guidelines pillars: ask the user directly or derive pillars from the cluster analysis of their existing blog.
Verification
- Calendar file exists.
- Must-write table has one row per planned slot in the horizon (no gaps).
- Every must-write entry has a matching stub file under
briefs/. - Raw JSON file present.
- If re-run a month later, diffing the raw files should show what moved from backlog to must-write and which refreshes were completed.